Skip to content

Instantly share code, notes, and snippets.

@skeggse
Last active May 21, 2024 09:21
Show Gist options
  • Save skeggse/52672ddee97c8efec269 to your computer and use it in GitHub Desktop.
Save skeggse/52672ddee97c8efec269 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;
@spimou
Copy link

spimou commented May 26, 2018

Hi there, just using your example in node v8.11.1, express v4.16.3. You have to update your code to include
digest : 'crypt'
in the config and also include it as an argument in the pbkdf2
crypto.pbkdf2(password, salt, config.iterations, config.hashBytes, config.digest,(err, hash) =>{
otherwise you get TypeError: The "digest" argument is required and must not be undefined

Thanks

@afkhalid
Copy link

Hi there, just using your example in node v8.11.1, express v4.16.3. You have to update your code to include
digest : 'crypt'
in the config and also include it as an argument in the pbkdf2
crypto.pbkdf2(password, salt, config.iterations, config.hashBytes, config.digest,(err, hash) =>{
otherwise you get TypeError: The "digest" argument is required and must not be undefined

Thanks

Thanks but it's 'crypto' not 'crypt'

@petazeta
Copy link

I like this way for storing the salt and other config data.
*** 2021: new Buffer() as constructor is deprecated, you can use Buffer.alloc ***

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment