Data Encryption
Rules
- TLS everywhere: redirect HTTP to HTTPS, set HSTS headers, never allow unencrypted connections
- Encrypt PII columns at the application level with AES-256-GCM before storing in the database
- Hash emails with SHA-256 for lookup, encrypt with AES-256-GCM for display — enables search without exposing data
- Passwords: bcrypt or argon2id only — never SHA, MD5, or reversible encryption
- Encrypt database backups and store encryption keys separately from the data
- Use
crypto.subtle for browser-side encryption, node:crypto for server-side
- Store encryption keys in a secrets manager, never in code or alongside encrypted data
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
const ENCRYPTION_KEY = Buffer.from(process.env.ENCRYPTION_KEY!, "hex");
function encrypt(plaintext: string): string {
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", ENCRYPTION_KEY, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();
return Buffer.concat([iv, tag, encrypted]).toString("base64");
}
function decrypt(ciphertext: string): string {
const buf = Buffer.from(ciphertext, "base64");
const iv = buf.subarray(0, 12);
const tag = buf.subarray(12, 28);
const encrypted = buf.subarray(28);
const decipher = createDecipheriv("aes-256-gcm", ENCRYPTION_KEY, iv);
decipher.setAuthTag(tag);
return decipher.update(encrypted) + decipher.final("utf8");
}
Avoid
- Storing PII in plaintext — one database breach exposes everything
- Using ECB mode or AES without authentication (CBC without HMAC) — use GCM
- MD5 or SHA for password hashing — they are not password hashing algorithms
- Hardcoding encryption keys in source code — use environment variables or a KMS
- Sending sensitive data over HTTP, even internally — use TLS for all connections