90 lines
2.8 KiB
JavaScript
90 lines
2.8 KiB
JavaScript
var crypto = require('crypto');
|
|
|
|
// larger numbers mean better security, less
|
|
var config = {
|
|
// size of the generated hash
|
|
hashBytes: 32,
|
|
// larger salt means hashed passwords are more resistant to rainbow table, but
|
|
// you get diminishing returns pretty fast
|
|
saltBytes: 16,
|
|
// more iterations means an attacker has to take longer to brute force an
|
|
// individual password, so larger is better. however, larger also means longer
|
|
// to hash the password. tune so that hashing the password takes about a
|
|
// second
|
|
iterations: 872791
|
|
};
|
|
|
|
/**
|
|
* Hash a password using Node's asynchronous pbkdf2 (key derivation) function.
|
|
*
|
|
* Returns a self-contained buffer which can be arbitrarily encoded for storage
|
|
* that contains all the data needed to verify a password.
|
|
*
|
|
* @param {!String} password
|
|
* @param {!function(?Error, ?Buffer=)} callback
|
|
*/
|
|
function hashPassword(password, callback) {
|
|
// generate a salt for pbkdf2
|
|
crypto.randomBytes(config.saltBytes, function(err, salt) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
crypto.pbkdf2(password, salt, config.iterations, config.hashBytes,
|
|
function(err, hash) {
|
|
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
var combined = new Buffer(hash.length + salt.length + 8);
|
|
|
|
// include the size of the salt so that we can, during verification,
|
|
// figure out how much of the hash is salt
|
|
combined.writeUInt32BE(salt.length, 0, true);
|
|
// similarly, include the iteration count
|
|
combined.writeUInt32BE(config.iterations, 4, true);
|
|
|
|
salt.copy(combined, 8);
|
|
hash.copy(combined, salt.length + 8);
|
|
callback(null, combined);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Verify a password using Node's asynchronous pbkdf2 (key derivation) function.
|
|
*
|
|
* Accepts a hash and salt generated by hashPassword, and returns whether the
|
|
* hash matched the password (as a boolean).
|
|
*
|
|
* @param {!String} password
|
|
* @param {!Buffer} combined Buffer containing hash and salt as generated by
|
|
* hashPassword.
|
|
* @param {!function(?Error, !boolean)}
|
|
*/
|
|
function verifyPassword(password, combined, callback) {
|
|
// extract the salt and hash from the combined buffer
|
|
var saltBytes = combined.readUInt32BE(0);
|
|
var hashBytes = combined.length - saltBytes - 8;
|
|
var iterations = combined.readUInt32BE(4);
|
|
var salt = combined.slice(8, saltBytes + 8);
|
|
var hash = combined.toString('binary', saltBytes + 8);
|
|
|
|
// verify the salt and hash against the password
|
|
crypto.pbkdf2(password, salt, iterations, hashBytes, 'sha1', function(err, verify) {
|
|
if (err) {
|
|
return callback(err, false);
|
|
}
|
|
|
|
callback(null, verify.toString('binary') === hash);
|
|
});
|
|
}
|
|
|
|
function createVerificationCode() {
|
|
return crypto.randomBytes(8).toString("hex");
|
|
}
|
|
|
|
exports.hashPassword = hashPassword;
|
|
exports.verifyPassword = verifyPassword;
|
|
exports.createVerificationCode = createVerificationCode; |