25 lines
1.3 KiB
TypeScript
25 lines
1.3 KiB
TypeScript
|
|
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:<iv>:<tag>:<ciphertext> (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);
|
||
|
|
}
|