import { createCipheriv, createDecipheriv, createHash, randomBytes, timingSafeEqual } from 'node:crypto'; import { config } from './config.js'; export const sha256 = (s: string): string => createHash('sha256').update(s).digest('hex'); export const randomToken = (bytes = 32): string => randomBytes(bytes).toString('base64url'); /** AES-256-GCM, Format v1::: (base64url). Key-ID im Präfix ermöglicht spätere Rotation. */ export function encrypt(plain: string): string { const iv = randomBytes(12); const c = createCipheriv('aes-256-gcm', config.secretKey, iv); const ct = Buffer.concat([c.update(plain, 'utf8'), c.final()]); return ['v1', iv.toString('base64url'), c.getAuthTag().toString('base64url'), ct.toString('base64url')].join(':'); } export function decrypt(blob: string): string { const [v, iv, tag, ct] = blob.split(':'); if (v !== 'v1' || !iv || !tag || !ct) throw new Error('Unbekanntes Verschlüsselungsformat'); const d = createDecipheriv('aes-256-gcm', config.secretKey, Buffer.from(iv, 'base64url')); d.setAuthTag(Buffer.from(tag, 'base64url')); return Buffer.concat([d.update(Buffer.from(ct, 'base64url')), d.final()]).toString('utf8'); } export function safeEqual(a: string, b: string): boolean { const x = Buffer.from(a), y = Buffer.from(b); return x.length === y.length && timingSafeEqual(x, y); }