pbkdf2-password-hash
Version:
hash password with pbkdf2
82 lines (69 loc) • 2.5 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var crypto = require('crypto');
var util = require('util');
var timingSafeEqual = require('compare-timing-safe');
const pbkdf2 = util.promisify(crypto.pbkdf2);
const randomBytes = util.promisify(crypto.randomBytes);
const SEP = '$';
const OPTIONS = {
iterations: 120000, // ~ 10hashes/sec @ 2GHz
keylen: 64, // 512 bits
digest: 'sha512',
saltlen: 64
};
/**
* Generate a new password hash for `password` using PBKDF2
* Safety is obtained by large number of iterations and large key length for PBKDF2
* @param {String} password
* @param {String} [salt] - optional salt
* @param {Object} [opts]
* @param {Number} [opts.iterations=120000] - PBKDF2 number of iterations (~10 hashes/sec @ 2GHz)
* @param {String} [opts.digest=sha512] - PBKDF2 digest
* @param {Number} [opts.keylen=64] - PBKDF2 key length
* @param {Number} [opts.saltlen=64] - salt length in case `salt` is not defined
* @return {Promise} hashed password in `<digest>$<iterations>$<keylen>$<salt>$<hash>` notation
*/
function hash (password, salt, opts) {
if (typeof salt === 'object') {
opts = salt;
salt = undefined;
}
const _opts = Object.assign({}, OPTIONS, opts);
const { digest, iterations, keylen } = _opts;
let promise;
if (!salt) {
promise = randomBytes(_opts.saltlen);
} else {
promise = new Promise((resolve) => resolve(Buffer.from(salt || '', 'base64')));
}
return promise
.then((saltBuf) => {
salt = saltBuf.toString('base64');
return pbkdf2(String(password), saltBuf, iterations, keylen, digest)
})
.then((buf) => {
const hash = buf.toString('base64');
const str = [digest, iterations, keylen, salt, hash].join(SEP);
return str
})
}
/**
* validate `password` against `passwordHash`
* @param {String} password - plain-text password
* @param {String} passwordHash - hashed password
* @return {Promise} true if hash matches password
*/
function compare (password, passwordHash) {
const [digest, _iterations, _keylen, salt] = passwordHash.split(SEP); // eslint-disable-line no-unused-vars
const iterations = parseInt(_iterations, 10);
const keylen = parseInt(_keylen, 10);
return hash(password, salt, { digest, iterations, keylen })
.then((hash) =>
timingSafeEqual(hash, passwordHash)
)
}
const passwordHash = { hash, compare };
exports.default = passwordHash;
exports.passwordHash = passwordHash;
module.exports = exports["default"];