UNPKG

bcrypt-strong-password-hasher

Version:

A secure, zero-dependency password hasher using native Node.js crypto

59 lines (47 loc) 1.55 kB
import crypto from 'crypto'; const DEFAULT_ALGO = 'sha512'; const DEFAULT_ITERATIONS = 150000; const DEFAULT_KEYLEN = 64; export function generateSalt(length = 16) { return crypto.randomBytes(length).toString('hex'); } export async function hashPassword(password, { salt = generateSalt(), pepper = '', iterations = DEFAULT_ITERATIONS, keylen = DEFAULT_KEYLEN, digest = DEFAULT_ALGO } = {}) { const finalInput = password + pepper; const derivedKey = await pbkdf2Async(finalInput, salt, iterations, keylen, digest); const hash = derivedKey.toString('hex'); return JSON.stringify({ algo: digest, iterations, keylen, salt, hash, version: 1 }); } export async function verifyPassword(password, storedHashJSON, pepper = '') { const { algo, iterations, keylen, salt, hash } = JSON.parse(storedHashJSON); const finalInput = password + pepper; const derived = await pbkdf2Async(finalInput, salt, iterations, keylen, algo); const derivedHex = derived.toString('hex'); return secureCompare(hash, derivedHex); } function pbkdf2Async(password, salt, iterations, keylen, digest) { return new Promise((resolve, reject) => { crypto.pbkdf2(password, salt, iterations, keylen, digest, (err, key) => { if (err) reject(err); else resolve(key); }); }); } function secureCompare(a, b) { const bufferA = Buffer.from(a, 'hex'); const bufferB = Buffer.from(b, 'hex'); if (bufferA.length !== bufferB.length) return false; return crypto.timingSafeEqual(bufferA, bufferB); }