import { createCipheriv, createDecipheriv, createHmac, randomBytes } from 'crypto' const KEY = Buffer.from(process.env.ENCRYPTION_KEY ?? '', 'hex') // 32 bytes const HMAC = process.env.HMAC_KEY ?? '' if (process.env.NODE_ENV !== 'test' && (KEY.length !== 32 || !HMAC)) { throw new Error('ENCRYPTION_KEY (64 hex chars) and HMAC_KEY must be set in environment') } // ── Encrypt ─────────────────────────────────────────────────────────────────── // Format: hex(iv 12b) : hex(authTag 16b) : hex(ciphertext) export function encrypt(plaintext: string): string { const iv = randomBytes(12) const cipher = createCipheriv('aes-256-gcm', KEY, iv) const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]) const tag = cipher.getAuthTag() return `${iv.toString('hex')}:${tag.toString('hex')}:${enc.toString('hex')}` } // ── Decrypt ─────────────────────────────────────────────────────────────────── export function decrypt(data: string): string { const parts = data.split(':') if (parts.length !== 3) return data // не зашифровано — вернуть как есть const [ivHex, tagHex, encHex] = parts if (ivHex.length !== 24) return data // не наш формат const iv = Buffer.from(ivHex, 'hex') const authTag = Buffer.from(tagHex, 'hex') const enc = Buffer.from(encHex, 'hex') const decipher = createDecipheriv('aes-256-gcm', KEY, iv) decipher.setAuthTag(authTag) return Buffer.concat([decipher.update(enc), decipher.final()]).toString('utf8') } // ── Null-safe helpers ───────────────────────────────────────────────────────── export function encryptField(val: string | null | undefined): string | null { if (!val) return null return encrypt(val) } export function decryptField(val: string | null | undefined): string | null { if (!val) return null try { return decrypt(val) } catch { return val } // старые незашифрованные данные } // ── Passport search token — HMAC(series+number) ─────────────────────────────── // Хранится рядом с зашифрованными полями для точного поиска без расшифровки export function passportToken(seriesAndNumber: string): string { return createHmac('sha256', HMAC) .update(seriesAndNumber.toLowerCase().replace(/\s/g, '')) .digest('hex') }