bcrypt-strong-password-hasher
Version:
A secure, zero-dependency password hasher using native Node.js crypto
59 lines (47 loc) • 1.55 kB
JavaScript
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);
}