import crypto from "node:crypto"; const ALGORITHM = "aes-256-gcm"; const IV_BYTES = 12; const AUTH_TAG_BYTES = 16; export type EncryptedPayrollFile = { ciphertext: Buffer; ivBase64: string; authTagBase64: string; }; /** * Lit la clé dédiée aux bulletins. La clé ne transite jamais vers le client et * doit être une valeur Base64 représentant précisément 32 octets AES-256. */ function getPayrollEncryptionKey(): Buffer { const encodedKey = process.env.PAYROLL_ENCRYPTION_KEY?.trim(); if (!encodedKey) throw new Error("Clé de chiffrement des bulletins absente"); const key = Buffer.from(encodedKey, "base64"); if (key.length !== 32) { throw new Error("La clé de chiffrement des bulletins doit contenir 32 octets"); } return key; } /** Sondage sans divulgation de la clé, destiné aux contrôles administrateur. */ export function getPayrollEncryptionStatus() { try { getPayrollEncryptionKey(); return { ready: true as const, algorithm: "AES-256-GCM" }; } catch { return { ready: false as const, algorithm: "AES-256-GCM" }; } } /** Chiffre un PDF avant son envoi vers le stockage persistant. */ export function encryptPayrollPdf(plaintext: Buffer): EncryptedPayrollFile { const key = getPayrollEncryptionKey(); const iv = crypto.randomBytes(IV_BYTES); const cipher = crypto.createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_BYTES }); const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]); return { ciphertext, ivBase64: iv.toString("base64"), authTagBase64: cipher.getAuthTag().toString("base64"), }; } /** Déchiffre le document uniquement côté serveur, après contrôle des droits. */ export function decryptPayrollPdf(encrypted: EncryptedPayrollFile): Buffer { const key = getPayrollEncryptionKey(); const iv = Buffer.from(encrypted.ivBase64, "base64"); const authTag = Buffer.from(encrypted.authTagBase64, "base64"); if (iv.length !== IV_BYTES || authTag.length !== AUTH_TAG_BYTES) { throw new Error("Paramètres de chiffrement de bulletin invalides"); } const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_BYTES }); decipher.setAuthTag(authTag); return Buffer.concat([decipher.update(encrypted.ciphertext), decipher.final()]); }