"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;