fh-wfm-user
Version:
102 lines (92 loc) • 3.3 kB
JavaScript
var crypto = require('crypto');
var semver = require('semver');
var SALT_LENGTH = 64;
var HASH_ITERATIONS = 10000;
var HASH_LENGTH = 256;
var CIPHER = 'sha1';
var SEPARATOR = ':';
function genSalt() {
return crypto.randomBytes(SALT_LENGTH).toString('hex');
}
/**
* Wrapper around node.js' pbkdf2 implementation.
*
* The PBKDF2 algorithm applies a hashing function to a password and salt a large number of times
* in order to generate a secure hash that requires high computational power to brute-force against.
*
* In node versions <0.12 the only supported hashing function is sha1, and the cipher parameter is ignored.
*/
function pbkdf2(pwd, salt, iterations, length, cipher, cb) {
if (semver.satisfies(process.version, '>=0.12.0')) {
crypto.pbkdf2(pwd, salt, iterations, length, cipher, cb);
} else {
crypto.pbkdf2(pwd, salt, iterations, length, cb);
}
}
/**
* @callback saltAndHashCallback
* @param {Error}
* @param {String} responseMessage
*/
/**
* Generates a random salt and hashes the given password, supplying it to a node-style callback.
* The hashed password follows the format {cipher}:{iterations}:{hash}:{salt}, in which:
*
* cipher: The algorithm used to form the hashed password
* iterations: The number of iterations of the algorithm run by {@link https://nodejs.org/api/crypto.html#crypto_crypto_pbkdf2_password_salt_iterations_keylen_digest_callback | pbkdf2}
* hash: The hashed password
* salt: The random salt for that hash
* @param {String} pwd Password to be encrypted
* @param {saltAndHashCallback} cb Node-style callback
*/
exports.saltAndHash = function(pwd, cb) {
var salt = genSalt();
pbkdf2(pwd, salt, HASH_ITERATIONS, HASH_LENGTH, CIPHER, function(err, hashed) {
if (err) {
return cb(err);
}
var finalHash = [CIPHER, HASH_ITERATIONS, hashed.toString('hex'), salt].join(SEPARATOR);
cb(null, finalHash);
});
};
/**
* @callback verifyCallback
* @param {Error}
* @param {Boolean} Whether the password corresponds to the supplied hashed string
*/
/**
* Verifies a given password against a hashed version of it.
* The hashed string should follow the same format as in {@link saltAndHash}
*
* In node <0.12 the cipher information is ignored and the default sha1 is used,
* so hashes using other algorithms will not match.
*
* @param {String} pwd Password to be verified
* @param {String} hashed Hash generated by {@link exports.saltAndHash}
* @param {verifyCallback} cb Node-style callback
*/
exports.verify = function(pwd, hashed, cb) {
var split = hashed.split(SEPARATOR);
if (!split || split.length !== 4) {
return cb(new Error('Hash string should be in {cipher}:{iterations}:{hash}:{salt} format'));
}
var cipher = split[0];
var iterations = Number(split[1]);
var hash = split[2];
var salt = split[3];
pbkdf2(pwd, salt, iterations, HASH_LENGTH, cipher, function(err, verify) {
// available on node 6.0+
if (crypto.timingSafeEqual) {
var hashBuf;
try {
hashBuf = Buffer.from(hash, 'hex');
} catch (e) {
// invalid hex string
return cb(err);
}
return cb(err, crypto.timingSafeEqual(hashBuf, verify));
}
cb(err, hash === verify.toString('hex'));
});
};
exports.separator = SEPARATOR;