Files
Compass/backend/shared/src/encryption.ts
2025-10-24 15:18:04 +02:00

58 lines
2.1 KiB
TypeScript

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;
}