Skip to main content

Encryption Helpers

Client-side encryption utilities for secure data transmission in the WorkPayCore frontend application.

Overview

The encryption helpers provide RSA + AES hybrid encryption for secure API communication, matching PHP's openssl_encrypt behavior for backend compatibility.

Core Functions

encryptData<T>(data)

Encrypts data using hybrid RSA + AES encryption for secure transmission.

Parameters:

  • data (T): The data to encrypt (any serializable type)

Returns:

  • Promise&lt;any&gt;: Encrypted payload object or original data if encryption unavailable

Example:

import { encryptData } from '@/utils/helpers/encryption';

// Encrypt sensitive form data
const sensitiveData = {
ssn: '123-45-6789',
bankAccount: '1234567890',
salary: 75000,
};

const encryptedPayload = await encryptData(sensitiveData);
console.log(encryptedPayload);
// Returns: {
// x: "base64-encrypted-data",
// y: "rsa-encrypted-aes-key",
// z: "base64-initialization-vector"
// }

// Send to API
const response = await fetch('/api/secure-endpoint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(encryptedPayload),
});

Use Cases:

  • Sensitive form data transmission
  • Payment information encryption
  • Personal data protection
  • Secure API communication

Utility Functions

base64ToArrayBuffer(base64)

Converts a base64 string to ArrayBuffer for cryptographic operations.

Parameters:

  • base64 (string): Base64 encoded string

Returns:

  • ArrayBuffer: Binary data representation

Example:

import { base64ToArrayBuffer } from '@/utils/helpers/encryption';

const base64String = 'SGVsbG8gV29ybGQ=';
const buffer = base64ToArrayBuffer(base64String);
console.log(buffer); // ArrayBuffer(11) {}

arrayBufferToBase64(buffer)

Converts an ArrayBuffer to base64 string for transmission.

Parameters:

  • buffer (ArrayBuffer): Binary data to convert

Returns:

  • string: Base64 encoded string

Example:

import { arrayBufferToBase64 } from '@/utils/helpers/encryption';

const buffer = new ArrayBuffer(8);
const base64 = arrayBufferToBase64(buffer);
console.log(base64); // "AAAAAAAAAAA="

getRandomBytes(length)

Generates cryptographically secure random bytes.

Parameters:

  • length (number): Number of bytes to generate

Returns:

  • Promise&lt;Uint8Array&gt;: Random byte array

Example:

import { getRandomBytes } from '@/utils/helpers/encryption';

// Generate 32 random bytes for AES-256 key
const keyBytes = await getRandomBytes(32);
console.log(keyBytes); // Uint8Array(32) [...]

// Generate 16 random bytes for AES IV
const ivBytes = await getRandomBytes(16);
console.log(ivBytes); // Uint8Array(16) [...]

Encryption Process

How Hybrid Encryption Works

  1. Generate Random Keys: Creates 256-bit AES key and 128-bit IV
  2. AES Encryption: Encrypts data using AES-CBC with PKCS#7 padding
  3. RSA Encryption: Encrypts the AES key using RSA public key
  4. Package Response: Returns encrypted data, encrypted key, and IV

Data Flow

// Original data
const originalData = { sensitive: 'information' };

// Step 1: Generate random AES key and IV
const aesKey = await getRandomBytes(32); // 256-bit key
const iv = await getRandomBytes(16); // 128-bit IV

// Step 2: Encrypt data with AES
const jsonString = JSON.stringify(originalData);
const encryptedData = await encryptAesManual(aesKey, iv, jsonString);

// Step 3: Encrypt AES key with RSA
const aesKeyBase64 = arrayBufferToBase64(aesKey);
const encryptedKey = jsEncrypt.encrypt(aesKeyBase64);

// Step 4: Package for transmission
const payload = {
x: encryptedData, // AES encrypted data
y: encryptedKey, // RSA encrypted AES key
z: arrayBufferToBase64(iv), // Base64 IV
};

Advanced Usage Examples

Secure Form Submission

import { encryptData } from '@/utils/helpers/encryption';

const SecureForm = () => {
const [formData, setFormData] = useState({
ssn: '',
bankAccount: '',
routingNumber: '',
});

const handleSubmit = async e => {
e.preventDefault();

try {
// Encrypt sensitive data
const encryptedPayload = await encryptData(formData);

// Submit encrypted data
const response = await fetch('/api/employee/banking', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify(encryptedPayload),
});

if (response.ok) {
alert('Banking information saved securely');
}
} catch (error) {
console.error('Encryption failed:', error);
alert('Failed to secure data. Please try again.');
}
};

return (
<form onSubmit={handleSubmit}>
<input
type='text'
placeholder='SSN'
value={formData.ssn}
onChange={e => setFormData({ ...formData, ssn: e.target.value })}
/>
<input
type='text'
placeholder='Bank Account'
value={formData.bankAccount}
onChange={e =>
setFormData({ ...formData, bankAccount: e.target.value })
}
/>
<input
type='text'
placeholder='Routing Number'
value={formData.routingNumber}
onChange={e =>
setFormData({ ...formData, routingNumber: e.target.value })
}
/>
<button type='submit'>Save Securely</button>
</form>
);
};

Payment Processing

import { encryptData } from '@/utils/helpers/encryption';

const PaymentForm = () => {
const processPayment = async paymentData => {
try {
// Encrypt payment information
const encryptedPayment = await encryptData({
cardNumber: paymentData.cardNumber,
cvv: paymentData.cvv,
expiryDate: paymentData.expiryDate,
amount: paymentData.amount,
});

// Process payment with encrypted data
const response = await fetch('/api/payments/process', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...encryptedPayment,
merchantId: 'your-merchant-id',
timestamp: Date.now(),
}),
});

return response.json();
} catch (error) {
throw new Error('Payment encryption failed');
}
};

return <CreditCardForm onSubmit={processPayment} />;
};

Conditional Encryption Hook

import { encryptData } from '@/utils/helpers/encryption';

const useSecureSubmission = () => {
const [isEncrypting, setIsEncrypting] = useState(false);

const secureSubmit = async (data, endpoint, options = {}) => {
setIsEncrypting(true);

try {
let payload = data;

// Only encrypt if RSA public key is available
if (import.meta.env.VITE_RSA_PUBLIC_KEY) {
payload = await encryptData(data);
} else {
console.warn('No RSA public key found. Sending unencrypted data.');
}

const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...options.headers,
},
body: JSON.stringify(payload),
});

return response.json();
} finally {
setIsEncrypting(false);
}
};

return { secureSubmit, isEncrypting };
};

// Usage
const UserProfileForm = () => {
const { secureSubmit, isEncrypting } = useSecureSubmission();

const handleSave = async profileData => {
const result = await secureSubmit(profileData, '/api/profile/update');

if (result.success) {
alert('Profile updated securely');
}
};

return (
<form onSubmit={handleSave}>
{/* form fields */}
<button type='submit' disabled={isEncrypting}>
{isEncrypting ? 'Encrypting...' : 'Save Profile'}
</button>
</form>
);
};

Environment Configuration

Required Environment Variables

# .env file
VITE_RSA_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----"

Environment-Specific Behavior

// Development vs Production
const isDevelopment = import.meta.env.DEV;

if (isDevelopment && !import.meta.env.VITE_RSA_PUBLIC_KEY) {
console.warn('RSA public key not configured. Encryption disabled.');
}

// The encryptData function automatically handles missing keys
const result = await encryptData(sensitiveData);
// Returns original data if no public key is configured

Security Considerations

Best Practices

  1. Key Management: Never store private keys in frontend code
  2. Environment Variables: Use secure environment variable management
  3. HTTPS Only: Always use HTTPS in production for encrypted payloads
  4. Key Rotation: Regularly rotate RSA key pairs
  5. Validation: Validate encrypted payloads on the backend

Security Features

  • AES-256-CBC: Strong symmetric encryption
  • RSA-2048+: Asymmetric key encryption
  • PKCS#7 Padding: Standard padding for AES blocks
  • Random IVs: Each encryption uses a unique initialization vector
  • Base64 Encoding: Safe transport encoding

Error Handling

import { encryptData } from '@/utils/helpers/encryption';

const safeEncryption = async data => {
try {
return await encryptData(data);
} catch (error) {
console.error('Encryption failed:', error);

// Decide whether to proceed without encryption
if (isProductionEnvironment()) {
throw new Error('Encryption required but failed');
}

// In development, might proceed without encryption
console.warn('Proceeding without encryption in development mode');
return data;
}
};

Performance Considerations

  • Async Operations: All encryption is asynchronous
  • Memory Usage: Large data sets consume more memory during encryption
  • Processing Time: Encryption adds latency to requests
  • Browser Support: Uses modern Web Crypto API


TypeScript Definitions

export function base64ToArrayBuffer(base64: string): ArrayBuffer;
export function arrayBufferToBase64(buffer: ArrayBuffer): string;
export function getRandomBytes(length: number): Promise&lt;Uint8Array&gt;;
export function encryptData&lt;T&gt;(data: T): Promise&lt;any&gt;;

Dependencies

  • jsencrypt: RSA encryption library
  • Web Crypto API: Browser native cryptographic functions
  • Environment Variables: For RSA public key configuration