import crypto from 'crypto'; import Database from 'better-sqlite3'; const ALGORITHM = 'aes-256-gcm'; let cachedKey: Buffer | null = null; /** * Initialize encryption key from env, DB, or generate a new one. * Must be called after DB is initialized. */ export function initEncryptionKey(db: Database.Database): void { // 1. Check env var const envKey = process.env.ENCRYPTION_KEY; if (envKey && envKey !== 'your-64-char-hex-key-here') { cachedKey = Buffer.from(envKey, 'hex'); return; } // 2. Check DB for persisted key const row = db.prepare("SELECT value FROM settings WHERE key = 'encryption_key'").get() as { value: string } | undefined; if (row) { cachedKey = Buffer.from(row.value, 'hex'); return; } // 3. Generate and persist cachedKey = crypto.randomBytes(32); db.prepare("INSERT INTO settings (key, value) VALUES ('encryption_key', ?)").run(cachedKey.toString('hex')); } function getEncryptionKey(): Buffer { if (!cachedKey) { throw new Error('Encryption key not initialized. Call initEncryptionKey() first.'); } return cachedKey; } export function encrypt(text: string): { encrypted: string; iv: string; authTag: string } { const key = getEncryptionKey(); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(ALGORITHM, key, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag().toString('hex'); return { encrypted, iv: iv.toString('hex'), authTag, }; } export function decrypt(encrypted: string, iv: string, authTag: string): string { const key = getEncryptionKey(); const decipher = crypto.createDecipheriv(ALGORITHM, key, Buffer.from(iv, 'hex')); decipher.setAuthTag(Buffer.from(authTag, 'hex')); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } export function maskKey(key: string): string { if (key.length <= 8) return '****' + key.slice(-4); return key.slice(0, 4) + '...' + key.slice(-4); }