SPB Git

Repository Public

crypto.ts 1.7 KB 31 lines typescript

1/** Small crypto primitives: random tokens, hashing, constant-time compare, AES-256-GCM sealing. */2import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';34export const randomToken = (bytes = 32): string => randomBytes(bytes).toString('base64url');5export const sha256Hex = (value: string | Buffer): string => createHash('sha256').update(value).digest('hex');67/** Constant-time string equality (compares fixed-length HMAC digests, so length does not leak). */8export function safeEqual(a: string, b: string): boolean {9  const key = randomBytes(32);10  const da = createHmac('sha256', key).update(a).digest();11  const db = createHmac('sha256', key).update(b).digest();12  return timingSafeEqual(da, db);13}1415/** AES-256-GCM. Output: `v1.<iv>.<tag>.<ciphertext>` (base64url). `aad` binds the value to its row. */16export function seal(key: Buffer, plaintext: string, aad = ''): string {17  const iv = randomBytes(12);18  const cipher = createCipheriv('aes-256-gcm', key, iv);19  cipher.setAAD(Buffer.from(aad));20  const ct = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);21  return ['v1', iv.toString('base64url'), cipher.getAuthTag().toString('base64url'), ct.toString('base64url')].join('.');22}23export function unseal(key: Buffer, sealed: string, aad = ''): string {24  const [v, iv, tag, ct] = sealed.split('.');25  if (v !== 'v1' || !iv || !tag || ct === undefined) throw new Error('bad sealed value');26  const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(iv, 'base64url'));27  decipher.setAAD(Buffer.from(aad));28  decipher.setAuthTag(Buffer.from(tag, 'base64url'));29  return Buffer.concat([decipher.update(Buffer.from(ct, 'base64url')), decipher.final()]).toString('utf8');30}31
Files main
Menu

Theme