import "server-only"; import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto"; // scrypt-Parameter fest verankert statt "der aktuelle Node-Default" zu // nutzen, damit ein künftiges Node-Upgrade die Hash-Stärke nicht // stillschweigend ändert. Speicherbedarf = 128 * N * r = 16 MiB. const N = 16384; const r = 8; const p = 1; const KEY_LEN = 64; const MAX_MEM = 32 * 1024 * 1024; // Selbstbeschreibendes Format "scrypt$N$r$p$salt$hash" (hex), damit die // Parameter später erhöht werden können, ohne bestehende Hashes zu // invalidieren. export function hashPassword(plain: string): string { const salt = randomBytes(16); const hash = scryptSync(plain, salt, KEY_LEN, { N, r, p, maxmem: MAX_MEM }); return `scrypt$${N}$${r}$${p}$${salt.toString("hex")}$${hash.toString("hex")}`; } export function verifyPassword(plain: string, stored: string): boolean { const parts = stored.split("$"); if (parts.length !== 6 || parts[0] !== "scrypt") return false; const [, nStr, rStr, pStr, saltHex, hashHex] = parts; const salt = Buffer.from(saltHex, "hex"); const expected = Buffer.from(hashHex, "hex"); const actual = scryptSync(plain, salt, expected.length, { N: Number(nStr), r: Number(rStr), p: Number(pStr), maxmem: MAX_MEM, }); return actual.length === expected.length && timingSafeEqual(actual, expected); }