Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Node.js and Password Storage With Bcrypt

DZone's Guide to

Node.js and Password Storage With Bcrypt

Are you securely storing your users' passwords in your web application? You can safely and securely store those passwords in your web app with Node.js.

· Security Zone
Free Resource

Address your unique security needs at every stage of the software development life cycle. Brought to you in partnership with Synopsys.

Every year we continue to see news articles and pastebins about data breaches where user accounts were stored either in plaintext (seriously!) or using an inferior hashing algorithm. This practice left thousands and sometimes millions of users vulnerable, not only on the original site in question but on any additional sites on which the same credentials were used.

When working with Node.js applications (whether it's a vanilla Javascript application, a web server like express.js or connect, or anything in between), there’s a secure way we can store user credentials. However, it’s not just using the right hashing algorithm. I’ve talked extensively about how the right tool also includes the necessary ingredient of “time” as part of the password hashing algorithm and what it means for the attacker who’s trying to crack passwords through brute-force.

In short, while strong hashing algorithms are important, password crackers can generate an enormous amount of password guesses in order to find the right needle in the haystack. Cycling a user’s password through N number of hashing rounds forces a cracker to also go through the same number of hashing rounds to get the same output. A supercomputer could have generated 63,000,000,000 tries per second, but when forcing a time constraint under the same algorithm, it can only generate 71,000.

We’ve looked at other password-based key derivation functions such as pbkdf2 in the past. Today, I want to introduce you to bcrypt and specifically the NPM (Node Package Manager) package you can use in our next Node.js-based web application.

Implementation

What I want to show you is more than just how you utilize bcrypt to provide cryptographic hashes of user passwords. I also want to show you what it provides out of the box in the way of future-proofing and an example how we can hook into the functionality and flow of a Node.js application.

Installing

We can get started by installing the bcrypt package:

npm install —save —save-exact bcrypt@0.8.7

Wondering about the “—save-exact”? Unfortunately, the default behavior of installing an NPM package is to prefix the package version in your package.json file with a caret (^).

e.g. “bcrypt”: “^0.8.7"

Either one of the NPM package version prefixes using the caret (^) or tilde (~) states that you blindly accept patches or minor updates to said package.

If you’re publishing NPM packages, blindly accepting updates to downstream dependencies is a dangerous proposition. This can potentially, cause a chain-reaction to consumers of your package when an error occurs in a dependency. However, that’s not the problem I am thinking of. When working with any 3rd party software, especially security based software, blinding accepting updates that could potentially introduce security holes before you or the community has a chance to vet them could be lethal.

By using the —save-exact and specifying a particular version, we won’t inadvertently update that package when someone runs an npm update.

Overview

Bcrypt’s API has a number of functions, but the two we are going to focus on and the main purposes of the package are to generate cryptographically strong hashes of user passwords over the course of a number of hashing rounds and perform a comparison of a submitted password guess.

Hashing

bcrypt.hash(password, rounds, callback);

The Hash API call follows the standard node.js asynchronous programming style, allowing you to pass in a callback. It also allows you to specify a certain number of rounds. However, these rounds aren’t exactly what you might envision based on what we have seen with other libraries (such as PBKDF2, where 1=1).

A round of bcrypt is actually 2^n. Therefore, 16.5 would actually be 92,681 rounds of hashing that would be performed (2^16.5 = 92,681).  

More importantly, there are two unseen actions that bcrypt takes into account.

  1. Using the async hash() function also generates the high level of entropy salt that will be used with the hashing of the password.
  2. The outcome hash contains the necessary external information such as the salt and round count necessary for comparison. What this means is that if at some point, there’s a need to dial-up the number of rounds of hashing, it won’t disturb existing user password hashes that were generated with a different number of hash rounds.

Comparing

bcrypt.compare(guess, actual, callback)

This should be close to self-explanatory. We can provide the password guess along with the correct value and a callback. Bcrypt will in turn, distinguish the rounds, salt and hash from the actual value and put the submitted password guess through the specified number of hashing rounds before finally comparing the results for a match.  

Implementation

The above is all fine and dandy, except, where do we use it? Every application is different, including storage, middleware, possible inclusion of an ORM… the list goes on. Therefore, below is an example of using bcrypt to hash newly registered users’ passwords.

In this particular application, I’m using the object data modeling (ODM) tool Mongoose for building validation layers for a MongoDB database collections. A mongoose schema is a definition of a collection object that will be saved in MongoDB. Mongoose provides a number of hooks that you can tie in. Here is an example of utilizing the save pre-hook function for firing off the hashing of a user’s password before saving the user.

Creating a New User

Below is a Mongoose schema called UserSchema which provides the definition of the fields we expect in a saved user document in MongoDB. We can define a middleware function to be called before the “save” function of a mongoose schema is ultimately called.

Note: The bcrypt.hashAsync function is a result of promising the bcrypt module using bluebird’s promisifying function first:

import mongoose         from "mongoose";
import Promise          from "bluebird";

const bcrypt = Promise.promisifyAll(require("bcrypt"));

UserSchema.pre("save", async function (next) {
    if (!this.isModified("password")) {
        return next();
    }

    try {
        const hash = await bcrypt.hashAsync(this.password, 16.5);

        this.password = hash;
        next();

    } catch (err) {
        next(err);
    }
});

Here we are defining the middleware to run before (“pre”) the “save” function is called for a mongoose UserSchema object. In return, we will generate a hash of the user submitted password and that value will be stored in the “password” field of the user document instead of the actual submitted plaintext password.

Below is a view of the actual defined UserSchema. Other than the referenced “password” field above in the pre “save” hook middleware, none of the schemas directly affect what we’re doing here. I still wanted to show you for completion.

Mongoose Schema

JavaScript

const UserSchema = new Schema({

   firstName: String,
   lastName: String,
   username: {
       type: String,
       index: {
           unique: true
       }
   },
   password: {
       type: String,
       required: true,
       match: /(?=.*[a-zA-Z])(?=.*[0-9]+).*/,
       minlength: 12
   },
   email: {
       type: String,
       require: true,
       match: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i
   },
   created: {
       type: Date,
       required: true,
       default: new Date()
   }
});

Therefore, when creating a new user such as the following block and calling save on the UserSchema instance (User), the predefined middleware that we defined will be called to hash the new user’s submitted password.

Note: “User” is a returned mongoose model that is defined by a Mongoose schema that is directly bound to a specific database connection. I have left this out since it is irrelevant, but I want to show the mongoose “Save” function being called that would trigger the above middleware that we defined.

const submittedUser = {
   firstName: firstName,
   lastName: lastName,
   username: email,
   email: email,
   password: password,
   created: Date.now()
};

const user = new User(submittedUser);

await user.save()
   .then(function (doc) {
       if (doc) {
           //log user creation
       }
   })
   .catch(function (err) {
      //handle error
   });

User Login (Comparing)

Finally, in the case where we have a user logging in or re-authenticating when accessing a key access area of our application, we’re going to want to compare a submitted password guess to a user’s existing stored password hash.

Again, we’re using Mongoose as a hook into the process. Mongoose provides a way to define instance methods of its schemas, which we’ll use as an example for being able to validate a submitted password-guess to a valid user.

UserSchema.methods.passwordIsValid = function (password) {
   try {
       return bcrypt.compareAsync(password, this.password);
   }
   catch (err) {
       throw err;
   }
};

Again, we’ve defined a promise-returned version of the async bcrypt method “compare” to return a promise to the caller. Note: That’s not required, and you can simply call bcrypt.compare() and provide a callback as a third argument.

Upon Finding a User

const existingUser = await User.findById(id);

if (await existingUser.passwordIsValid(password)){…}

This “existingUser” is an instance of the User Schema provided through the mongoose “User” model.  We can call the “passwordIsValid” instance method that’s defined above, which will return a promise that, when resolved, will provide a boolean of whether it was a match.

Conclusion

As hardware gets faster, the need to raise the hashing rounds proverbial bar increases. We can do so without jeopardizing the current existing users.  

Bcrypt doesn’t have to be utilized in this fashion either, nor is it a module for use in strictly web-based applications. However, it is a well-vetted, well-maintained, and widely distributed NPM package that can be used to securely store our users’ password hashes.

In a future post, we’ll look at how we can utilize some other NPM packages to further safeguard our user’s accounts, specifically, the highly overlooked ingredient of our user’s password composition.

Find out how Synopsys can help you build security and quality into your SDLC and supply chain. We offer application testing and remediation expertise, guidance for structuring a software security initiative, training, and professional services for a proactive approach to application security.

Topics:
security ,node.js ,password storage ,bcrypt

Published at DZone with permission of Max McCarty, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}