import crypto from "crypto"; import {ENV_CONFIG} from "common/envs/constants"; /** * MASTER_KEY must be a 32-byte Buffer (AES-256). * Load it from a secrets manager at runtime; do NOT hardcode. */ let _MASTER_KEY: Buffer | null = null const getMasterKey = () => { if (_MASTER_KEY) return _MASTER_KEY if (ENV_CONFIG.dbEncryptionKey) { const MASTER_KEY_BASE64 = ENV_CONFIG.dbEncryptionKey _MASTER_KEY = Buffer.from(MASTER_KEY_BASE64, "base64") if (_MASTER_KEY.length !== 32) throw new Error("MASTER_KEY must be 32 bytes") } if (!_MASTER_KEY) throw new Error("MASTER_KEY not set") return _MASTER_KEY } /** * Encrypt a UTF-8 message string into base64 ciphertext + iv + tag. * The IV makes the encryption probabilistic to ensure uniqueness in ciphertexts even when encrypting the same plaintext * multiple times and has therefore no intent of being secret. The authentication tag works similar to a MAC. * It's used to prove the authenticity and integrity of a message */ export function encryptMessage(plaintext: string) { const iv = crypto.randomBytes(12); // 96-bit IV, recommended for AES-GCM const cipher = crypto.createCipheriv("aes-256-gcm", getMasterKey(), iv); const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]); const tag = cipher.getAuthTag(); // console.debug(plaintext, iv, ciphertext, tag) return { ciphertext: ciphertext.toString("base64"), iv: iv.toString("base64"), tag: tag.toString("base64"), }; } /** * Decrypt base64 ciphertext + iv + tag -> plaintext string. * Throws on auth failure. */ export function decryptMessage({ciphertext, iv, tag}: { ciphertext: string; iv: string; tag: string; }) { const ivBuf = Buffer.from(iv, "base64"); const ctBuf = Buffer.from(ciphertext, "base64"); const tagBuf = Buffer.from(tag, "base64"); const decipher = crypto.createDecipheriv("aes-256-gcm", getMasterKey(), ivBuf); decipher.setAuthTag(tagBuf); const plaintext = Buffer.concat([decipher.update(ctBuf), decipher.final()]).toString("utf8"); // console.debug("Decrypted message:", plaintext); return plaintext; }