SPB Git

Repository Public

password.ts 1.3 KB 26 lines typescript

1import { randomBytes, scrypt, timingSafeEqual } from "node:crypto";23const N = 1 << 15, R = 8, P = 1, KEYLEN = 32;4const derive = (password: string, salt: Buffer, n = N) => new Promise<Buffer>((res, rej) =>5  scrypt(password.normalize("NFKC"), salt, KEYLEN, { N: n, r: R, p: P, maxmem: 128 * n * R * 2 }, (e, k) => (e ? rej(e) : res(k))));67export async function hashPassword(password: string): Promise<string> {8  const salt = randomBytes(16);9  return `scrypt$${N}$${salt.toString("base64")}$${(await derive(password, salt)).toString("base64")}`;10}11export async function verifyPassword(password: string, stored: string): Promise<boolean> {12  const [alg, n, salt, hash] = stored.split("$");13  if (alg !== "scrypt" || !salt || !hash) return false;14  const expected = Buffer.from(hash, "base64");15  const got = await derive(password, Buffer.from(salt, "base64"), Number(n));16  return got.length === expected.length && timingSafeEqual(got, expected);17}18/** Passwords must be long; no composition rules (NIST 800-63B). */19export function passwordProblem(pw: string): string | null {20  if (pw.length < 12) return "Use at least 12 characters";21  if (pw.length > 200) return "Use at most 200 characters";22  if (/^(.)\1+$/.test(pw)) return "Choose a less predictable password";23  return null;24}25export const randomPassword = () => randomBytes(18).toString("base64url");26
Files main
Menu

Theme