import { randomBytes, scrypt as scryptCallback, timingSafeEqual } from 'node:crypto' import { promisify } from 'node:util' import { decodeBase64Url, encodeBase64Url } from './base64url' const scrypt = promisify(scryptCallback) const SCRYPT_COST = 16_384 const SCRYPT_BLOCK_SIZE = 8 const SCRYPT_PARALLELIZATION = 1 const KEY_LENGTH = 64 export async function hashPassword(password: string): Promise { const salt = encodeBase64Url(randomBytes(16)) const derivedKey = await scrypt(password, salt, KEY_LENGTH, { N: SCRYPT_COST, r: SCRYPT_BLOCK_SIZE, p: SCRYPT_PARALLELIZATION }) as Buffer return [ 'scrypt', SCRYPT_COST, SCRYPT_BLOCK_SIZE, SCRYPT_PARALLELIZATION, salt, encodeBase64Url(derivedKey) ].join('$') } export async function verifyPassword(password: string, storedHash: string): Promise { const [algorithm, cost, blockSize, parallelization, salt, key] = storedHash.split('$') if ( algorithm !== 'scrypt' || !cost || !blockSize || !parallelization || !salt || !key ) { return false } const expectedKey = decodeBase64Url(key) const derivedKey = await scrypt(password, salt, expectedKey.length, { N: Number(cost), r: Number(blockSize), p: Number(parallelization) }) as Buffer return timingSafeEqual(expectedKey, derivedKey) }