UNPKG

fh-wfm-user

Version:
102 lines (92 loc) 3.3 kB
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;