In this Tech Tip I'm going to discuss how to store passwords securely in a mongoDB database utilizing the crypto methods included with Node.js.
Background
I'm going to simulate a database of user info that includes the user's ID and password. The password will be stored as a hash utilizing the 'key strengthening' algorithm PBKDF2. I'll use the crypto functions included in the node.js distribution to implement the password hashing function. A really nice discussion of password hashing is here.
Implementation
PBKDF2 utilizes a salt and multiple iterations of a hash function to create a cryptographically strong hash. Below I set up some variables for a 512-bit salt, 10000 iterations, and a 512-bit resulting key length.
var saltLengthBytes = 64;
var hashIterations = 10000;
var keyLengthBytes = 64;
Below is the main body of the demo code. Steps that occur:
- Initiate the MongoClient connection with a callback that initiates the remainder of the functions.
- Create a user record with an id of '1000' and a password of 'userPa$$word'.
- Perform a series of 3 authentication tests. The first test uses a valid username and password. The second uses an invalid username. The third uses a valid username, but invalid password.
- Delete the user record.
- Close the database.
mongodb.MongoClient.connect('mongodb://localhost:27017/configDb', function(err, db){
if (!err && db)
{
userColl = db.collection('users');
createUser({_id : 1000, name : 'user1000', password : 'userPa$$word'}, function(result1){
if (!(result1 instanceof Error))
{
console.log('Authenticating valid user name (1000) and password (userPa$$word)')
authenticateUser(1000, 'userPa$$word', function(result2){
if (!(result2 instanceof Error) && result2 == true)
console.log('Results: User authenticated');
else
console.log('Results: User name or password does not match');
console.log('\n'+ 'Authenticating invalid user name (1111)')
authenticateUser(1111, 'userPa$$word', function(result3){
if (!(result3 instanceof Error) && result3 == true)
console.log('Results: User authenticated');
else
console.log('Results: User name or password does not match');
console.log('\n' + 'Authenticating valid user name (1000) but invalid password (userPassword)')
authenticateUser(1000, 'userPassword', function(result4){
if (!(result4 instanceof Error) && result4 == true)
console.log('Results: User authenticated');
else
console.log('Results: User name or password does not match');
deleteUser(1000, function(result5){
db.close();
});
});
});
});
}
});
}
});
Below is a snippet of the createUser function with the password hashing portions depicted. Of note, I'm using node's built-in strong random number generator for the salt and its PBKDF2 hash function (node uses SHA-1 for the underlying hash). I also use Mongo's Binary object for storing the resulting salt and password hash into the database.
function createUser(doc, callback)
{
try
{
crypto.randomBytes(saltLengthBytes, function(err1, buf) { //create the salt for the hash function
if (err1)
{
console.log(err1.message);
if (callback)
callback(err1);
}
else
{
doc.salt = new Binary(buf); //put salt result in a mongodb Binary object
//Invoke hash function with salt object
crypto.pbkdf2(doc.password, doc.salt.read(0, doc.salt.length()), hashIterations, keyLengthBytes, function(err2, key){
Below is the authenticate function. It simply applies the hashing steps again to the user-supplied credentials and then compares them to what is in the database. If the username and hashed password match, the user is authenticated. Otherwise, authentication fails.
function authenticateUser(id, password, callback)
{
try
{
readUser(id, function(result) {
if (result instanceof Error)
{
console(result.message);
if (callback)
callback(result);
}
else
{
if (result)
{
crypto.pbkdf2(password, result.salt.read(0, result.salt.length()), hashIterations, keyLengthBytes, function(err, key){
Output
Authenticating valid user name (1000) and password (userPa$$word)
Results: User authenticated
Authenticating invalid user name (1111)
Results: User name or password does not match
Authenticating valid user name (1000) but invalid password (userPassword)
Results: User name or password does not match
Full source code here.
Copyright ©1993-2024 Joey E Whelan, All rights reserved.