Security is always top of mind when building software, whether it’s API security with JWT, infrastructure security through firewalls or separate VPCs, or (m)TLS. This post focuses on Layer 7 security and walks through practical examples of how encryption can be implemented on both the client and server.
Technologies / Libraries
- CryptoJS — a JavaScript library for client-side cryptography
- Crypto — Node.js’s built-in cryptography module for server-side use
Symmetric Encryption
An algorithm that uses the same key to both encrypt and decrypt a payload.
When should I use it?
- You need higher runtime performance — encrypting and decrypting with a symmetric key is generally faster than asymmetric encryption due to shorter key lengths and a single key for both operations.
- You’re working with larger payloads — symmetric keys are efficient at encrypting large data. Asymmetric encryption is limited to plaintext payloads under 65 KB.
// Symmetric Encryption
const symmetricKey = "secret key";
const payload = "This is a secret. Please encrypt";
const encryptedPayload = CryptoJS.AES.encrypt(payload, symmetricKey);
// Symmetric Decryption
const decrypted = CryptoJS.AES.decrypt(encryptedPayload.toString(), symmetricKey);
const decryptedPayload = decrypted.toString(CryptoJS.enc.Utf8);
Asymmetric Encryption with Crypto
An algorithm that creates a public and private key pair used to share data securely between parties.
When should I use it?
- You need higher levels of security.
- Runtime performance is not a primary requirement.
First, generate your RSA key pair using OpenSSL in your terminal:
# Generate a 4096-bit private key
openssl genrsa -out rsa_private.pem 4096
# Derive the public key from the private key
openssl rsa -pubout -in rsa_private.pem -out rsa_pub.pem
Now you can use Node’s crypto module to encrypt and decrypt:
const crypto = require("crypto");
// Asymmetric Encryption (with public key)
const publicKey = "-----BEGIN PUBLIC KEY-----\nMIIC....";
const payload = "This is a secret. Please encrypt";
const encryptedPayload = crypto
.publicEncrypt(publicKey, Buffer.from(payload))
.toString("base64");
// Asymmetric Decryption (with private key)
const privateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIB...";
const originalPayload = crypto.privateDecrypt(
privateKey,
Buffer.from(encryptedPayload, "base64")
);
Hybrid Encryption with Crypto and CryptoJS
There may be a time when you need to encrypt large amounts of data but also want the added security of RSA asymmetric encryption. If your payload exceeds 65 KB, simple asymmetric encryption won’t work. The solution is to combine both methods.
The approach:
- Generate a public/private key pair
- Create a symmetric key
- Encrypt the payload with the symmetric key
- Encrypt the symmetric key with the asymmetric public key
- Send both the encrypted key and encrypted payload to the receiver
- Receiver decrypts the symmetric key using their private key
- Receiver decrypts the payload using the recovered symmetric key
const crypto = require("crypto");
// Hybrid Encryption
const symmetricKey = "secret key";
const asymmetricPublicKey = "-----BEGIN PUBLIC KEY-----\nMIIC....";
const payload = "This is a secret. Please encrypt";
// Encrypt payload with symmetric key
const encryptedPayload = CryptoJS.AES.encrypt(payload, symmetricKey);
// Encrypt the symmetric key with the asymmetric public key
const encryptedSymmetricKey = crypto
.publicEncrypt(asymmetricPublicKey, Buffer.from(symmetricKey))
.toString("base64");
// Send message to recipient
// {
// "key": encryptedSymmetricKey,
// "payload": encryptedPayload
// }
// Hybrid Decryption
const privateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIB...";
// Recover symmetric key
const symmetricKey = crypto.privateDecrypt(
privateKey,
Buffer.from(message.key, "base64")
);
// Decrypt payload with recovered symmetric key
const decrypted = CryptoJS.AES.decrypt(message.payload.toString(), symmetricKey.toString());
const decryptedOriginalPayload = decrypted.toString(CryptoJS.enc.Utf8);
Hybrid encryption gives you the best of both worlds — the performance of symmetric encryption for bulk data, and the security of asymmetric encryption to protect the key itself. It’s the same model used by TLS under the hood, and a practical pattern worth having in your toolkit whenever you’re moving sensitive data across trust boundaries.