SPB Git

Repository Public

crypto.ts 1.6 KB 33 lines typescript

1import { createCipheriv, createDecipheriv, createHash, createHmac, hkdfSync, randomBytes, timingSafeEqual } from "node:crypto";2import { env } from "./env";34export const sha256 = (s: string) => createHash("sha256").update(s).digest("hex");5export const randomToken = (bytes = 32) => randomBytes(bytes).toString("base64url");6export const hmac = (purpose: string, value: string) => createHmac("sha256", env.appSecret).update(`${purpose}\n${value}`).digest("base64url");78export function safeEqual(a: string, b: string): boolean {9  const x = Buffer.from(a), y = Buffer.from(b);10  return x.length === y.length && timingSafeEqual(x, y);11}1213const key = (purpose: string) => Buffer.from(hkdfSync("sha256", env.appSecret, "sp-calendar", purpose, 32));1415/** AES-256-GCM. Output: 12-byte IV ‖ 16-byte tag ‖ ciphertext. */16export function encrypt(purpose: string, plaintext: string): Buffer {17  const iv = randomBytes(12);18  const c = createCipheriv("aes-256-gcm", key(purpose), iv);19  const body = Buffer.concat([c.update(plaintext, "utf8"), c.final()]);20  return Buffer.concat([iv, c.getAuthTag(), body]);21}22export function decrypt(purpose: string, blob: Buffer): string {23  const d = createDecipheriv("aes-256-gcm", key(purpose), blob.subarray(0, 12));24  d.setAuthTag(blob.subarray(12, 28));25  return Buffer.concat([d.update(blob.subarray(28)), d.final()]).toString("utf8");26}2728/**29 * Visitor management token. Derived from the token row's id with the server secret, so the raw value can be30 * rebuilt when an e-mail is rendered, while the database only ever holds its SHA-256.31 */32export const manageTokenFor = (tokenId: string) => hmac("manage-token", tokenId);33
Files main
Menu

Theme