import crypto from 'crypto'; import fs from 'fs/promises'; import path from 'path'; const ALGORITHM = 'aes-256-gcm'; const KEY_LENGTH = 32; // 256 bits const IV_LENGTH = 16; // 128 bits for GCM const AUTH_TAG_LENGTH = 16; // 128 bits // Derive key from environment variable function getKey() { const keyEnv = process.env.DATA_ENCRYPTION_KEY; if (!keyEnv) throw new Error('DATA_ENCRYPTION_KEY environment variable not set'); // Use SHA-256 to derive a 32-byte key from the env var return crypto.createHash('sha256').update(keyEnv).digest(); } export function encryptJson(data) { const key = getKey(); const iv = crypto.randomBytes(IV_LENGTH); const cipher = crypto.createCipher(ALGORITHM, key); cipher.setAAD(Buffer.from('')); // Optional AAD let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); return { iv: iv.toString('hex'), encrypted, authTag: authTag.toString('hex'), }; } export function decryptJson(encryptedData) { const key = getKey(); const { iv, encrypted, authTag } = encryptedData; const decipher = crypto.createDecipher(ALGORITHM, key); decipher.setAuthTag(Buffer.from(authTag, 'hex')); decipher.setAAD(Buffer.from('')); // Match AAD let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return JSON.parse(decrypted); } export async function saveEncryptedJson(filePath, data) { const encrypted = encryptJson(data); await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, JSON.stringify(encrypted)); } export async function loadEncryptedJson(filePath) { try { const content = await fs.readFile(filePath, 'utf8'); const encrypted = JSON.parse(content); return decryptJson(encrypted); } catch (err) { if (err.code === 'ENOENT') return null; // File not found throw err; } }