import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto' const PASSWORD_HASH_PREFIX = 'scrypt' const DERIVED_KEY_LENGTH = 64 /** * Gera hash de senha com scrypt e salt aleatório. * * @param password Senha em texto puro. * @returns Hash no formato `scrypt$salt$hash`. */ export function hashPassword(password: string): string { const salt = randomBytes(16).toString('base64url') const derivedKey = scryptSync(password, salt, DERIVED_KEY_LENGTH).toString('base64url') return `${PASSWORD_HASH_PREFIX}$${salt}$${derivedKey}` } /** * Compara senha em texto puro com hash salvo no banco. * * @param password Senha informada no login. * @param encodedHash Hash armazenado no banco. * @returns `true` quando a senha confere; caso contrário, `false`. */ export function verifyPassword(password: string, encodedHash: string): boolean { const [prefix, salt, storedHash] = encodedHash.split('$') if (prefix !== PASSWORD_HASH_PREFIX || !salt || !storedHash) { return false } const derivedKey = scryptSync(password, salt, DERIVED_KEY_LENGTH) const storedKeyBuffer = Buffer.from(storedHash, 'base64url') if (derivedKey.length !== storedKeyBuffer.length) { return false } return timingSafeEqual(derivedKey, storedKeyBuffer) }