var crypto = require('crypto'); // larger numbers mean better security, less var config = { digest: 'sha512', // 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: 24, // 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: 233335 }; /** * 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, config.digest, function (err, hash) { if (err) { return callback(err); } var combined = Buffer.alloc((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, config.digest, function(err, verify) { if (err && typeof callback === 'function') { return callback(err, false); } else if (err) { return false; } if (typeof callback === 'function') { callback(null, verify.toString('binary') === hash); } else { return true; } }); } exports.hashPassword = hashPassword; exports.verifyPassword = verifyPassword;