Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Convicted202/e26a6878f1259f96f15d22b0a492d965 to your computer and use it in GitHub Desktop.
Save Convicted202/e26a6878f1259f96f15d22b0a492d965 to your computer and use it in GitHub Desktop.
Example of using crypto.pbkdf2 to hash and verify passwords asynchronously, while storing the hash and salt in a single combined buffer along with the original hash settings
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, function(err, verify) {
if (err) {
return callback(err, false);
}
callback(null, verify.toString('binary') === hash);
});
}
exports.hashPassword = hashPassword;
exports.verifyPassword = verifyPassword;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment