"use strict"; const crypto = require("crypto"); const timeSource = require("randbytes").timeRandom.getInstance(); let randomSource = {}; if (process.platform !== "win32") randomSource = require("randbytes").urandom.getInstance(); const Encoded = require("./Encoded"); /** * @constructor * @desc Creates and validates hashes. By default, this class uses 30,000 iterations, Hexadecimal encoding scheme, SHA-512 hashing method, and generates a 512-byte key. * @params {Number} iteration - Number of iterations to run * @params {String} encoding - The default encoding scheme to be used * @params {String} hashMethod - The default hashing method to be implemented by PBKDF2 * @params {Number} keyLength - The length of the generated hash in bytes */ function Hasher (iteration, encoding, hashMethod, keyLength) { // Type validation (dotenv gives iteration as string, so just to be careful) if (typeof iteration !== "number") throw Error("Iteration must be number"); this.iter = iteration; this.hashMethod = hashMethod; if (typeof keyLength !== "number") throw Error("Key length must be number"); this.keylen = keyLength; this.encoding = encoding; /** * @type {Number} * @name Hasher~saltByte * @desc Length of the salt in bytes */ // Generate 64-byte salt (256/8 = 64) const saltByte = 64; /** * @method * @instance * @param {Hasher~saltCallback} callback - Handles the randomly generated salt * @param {Boolean} [urand=true] - Determines whether to use <code>/dev/urandom</code> or the time stamp as the random source */ this.generateSalt = function (callback, urand=true) { let source = randomSource; if (!urand) source = timeSource; source.getRandomBytes(saltByte, (salt) => { callback(salt); }); }; /** * This callback takes the randomly generated salt and allows it to be handled appropriately. * @callback Hasher~saltCallback * @param {Buffer} salt - Randomly generated salt */ } /** * @method * @memberof Hasher * @desc Hashes the provided pass-phrase * @param {String} pwd - Password to hash * @param {Hasher~hashCallback} callback - Handles the hashed object * @param {Boolean} [urand=true] - Determines whether to use <code>/dev/urandom</code> or the time stamp as the random source */ Hasher.prototype.hash = function (pwd, callback, urand=true) { this.generateSalt((salt) => { crypto.pbkdf2(pwd, salt, this.iter, this.keylen, this.hashMethod, (err, key) => { if (err) return callback(err); const hashed = new Encoded(key, salt, this.iter); callback(err, hashed); }); }, urand); }; /** * This callback takes the generated hash as an {@link Encoded} object. It also handles any errors from the PBKDF2 process. * @callback Hasher~hashCallback * @param {Object} err * @param {Encoded} hashed - The hashed object * @see Encoded */ /** ******************************************************************************* * DEPRECATED since v2.2.0 in favor of {@link Hasher#verify} ******************************************************************************* * @method * @memberof Hasher * @param {String} new_ - Given pass-phrase * @param {String} old - The encoded hash string to compare to * @param {Hasher-validatorCallback} callback - Handles the validation result * @param {String=} - The encoding scheme to be used (uses <code>hasher.encoding</code> as default) * @example * // Create a hash * hasher.hash("password", (err, hashed) => { * const old = hashed.toString(hasher.encoding); // Generate hash string with default encoding scheme * hasher.validate("password", old, (valid) => { * assert.equal(valid, true); * }); * }); */ Hasher.prototype.validate = function (new_, old, callback, enc) { const oldHash = Encoded.parse(old, enc || this.encoding); crypto.pbkdf2(new_, oldHash.getSalt(), this.iter, this.keylen, this.hashMethod, (err, key) => { let valid = false; if (key.length === oldHash.getKey().length && key.equals(oldHash.getKey())) valid = true; callback(valid); }); }; /** * This callback handles the validation result. * @callback Hasher~validatorCallback * @param {Boolean} valid - Validation test result * @deprecated since v2.2.0 */ /** * @method * @memberof Hasher * @param {String} new_ - Given pass-phrase * @param {String} old - The encoded hash string to compare to * @param {String=} - The encoding scheme to be used (uses <code>hasher.encoding</code> as default) * @param {Hasher-verifierCallback} callback - Handles the verification result * @example * // Create a hash * hasher.hash("password", (err, hashed) => { * const old = hashed.toString(hasher.encoding); // Generate hash string with default encoding scheme * hasher.verify("password", old, (err, valid) => { * if (err) console.error(err); * assert.equal(valid, true); * }); * }); */ Hasher.prototype.verify = function (new_, old, enc, callback) { if (typeof enc === "function") { callback = enc; enc = this.encoding; } if (!enc) { // enc could be null if called in async enc = this.encoding; } const oldHash = Encoded.parse(old, enc); crypto.pbkdf2(new_, oldHash.getSalt(), this.iter, this.keylen, this.hashMethod, (err, key) => { if (err) return callback(err); let valid = false; if (key.length === oldHash.getKey().length && key.equals(oldHash.getKey())) valid = true; callback(null, valid); }); }; /** * This callback handles the verification result. * @callback Hasher~verifierCallback * @param {Object} err * @param {Boolean} valid - verification test result */ module.exports = Hasher;