Salt and Hash Passwords with bcrypt

By Michelle Selzer on Apr. 28th, 2020

No matter how many precautions you take, you can never assume a database is impenetrable. Because cybercriminals use an array of resources in cyber attacks, a key step in password security is salting and hashing. In this guest tutorial by Michelle Selzer (@codingCommander), learn how to salt and hash a password using bcrypt.

It is crucial to keep users' passwords secure to protect against cyber attacks. The first step is storing passwords on a secure database server. However, no matter how many precautions you take, you can never assume the database is impenetrable. Cybercriminals can use an array of resources and may even collaborate with one of your coworkers. That is why you must take a second step to make deciphering any stolen passwords much harder: salting and hashing.

In this tutorial we'll:

  • Learn about hashing
  • Learn about salting
  • Evaluate bcrypt's security
  • Salt and hash a password using bcrypt
  • Compare a password with a hash

By the end of this tutorial, you will know how to use bcrypt to keep user passwords secure.

Goal

Safely store user passwords using bcrypt.

Prerequisites

  • None

Hashing a password

"Hashing" a password refers to taking a plain text password and putting it through a hash algorithm. The hash algorithm takes in a string of any size and outputs a fixed-length string. No matter the size of the original string (i.e., the plain text password), the output (the hash) is always the same length. Since the same process is always applied, the same input always yields the same output.

Say the plain text password is Ralph$467. Every time you pass Ralph$467 into the hash algorithm, the returned hash is the same. If someone else's plain text password is jsu*^7skdl230H98, the length of its hash is the same as for Ralph$467.

Because hash algorithms always produce the same result for a specific password, they are predictable. If you only hash the password, a hacker can figure out the original password. Hashing is not enough.

Salting a password

A salt is a random string. By hashing a plain text password plus a salt, the hash algorithm’s output is no longer predictable. The same password will no longer yield the same hash. The salt gets automatically included with the hash, so you do not need to store it in a database.

Bcrypt's security

Not all hash algorithms are created equal, and there are many options available to NodeJS developers. Many developers are unsure about which algorithm to use. Let's be clear that there is not only one correct choice. We can evaluate available algorithms’ security based on certain requirements. Let's see if bcrypt meets these requirements.

Bcrypt uses the Blowfish cypher. One criterion we want our hash algorithm to meet is speed. You don't want the algorithm to run too fast. We won't get into a detailed discussion about hashing speeds and cyberattacks, but just know that Blowfish is slow enough to prevent certain attacks.

As discussed earlier, hashing a password is not enough. We must also salt the password, and bcrypt requires you to do so. Random bytes get added to the password, and together the salted hash meets security recommendations on length and unpredictability.

Another aspect to look at is longevity versus record. Bcrypt is widely used and has been around for many years (it was created in 1999). Reported issues are scarce, and no one has broken the Blowfish algorithm. It is still essential to stay on top of reported bugs and security issues.

How to salt and hash a password using bcrypt

Step 0: First, install the bcrypt library.

$ npm i bcrypt

Now let's look at the code.

Step 1: Include the bcrypt module

To use bcrypt, we must include the module.

const bcrypt = require ('bcrypt');

Step 2: Set a value for saltRounds

Next, we set the saltRounds value. The higher the saltRounds value, the more time the hashing algorithm takes. You want to select a number that is high enough to prevent attacks, but not slower than potential user patience. In this example, we use the default value, 10.

const saltRounds = 10;

Step 3: Declare a password variable

Next, for simplicity, we hard-code a user password. In real life, this would be a value passed back from a registration form.

var password = "Fkdj^45ci@Jad";

Step 4: Generate a salt

You can salt and hash the password in one function or by using separate functions. In our first example, we separate the two functions. You can also salt and hash synchronously or asynchronously. The recommendation is to do so asynchronously, so that is the method used in this tutorial.

Below is the genSalt function, used to generate a salt.

We pass bcrypt.genSalt() these parameters:

  1. saltRounds
  2. Callback of error and the returned salt:
bcrypt.genSalt(saltRounds, function(err, salt) {
  // returns salt
});

Step 5: Hash the Password

We now add the hash function inside genSalt.

We pass bcrypt.hash() these parameters:

  1. Password
  2. Salt
  3. Callback of error and the returned hash
bcrypt.genSalt(saltRounds, function(err, salt) {
  bcrypt.hash(password, salt, function(err, hash) {
  // returns hash
  console.log(hash);
  });
});

In the above example, we simply console.log the hash. In real life, there would probably be a function there to insert the hash into a database.

bcrypt.genSalt(saltRounds, function(err, salt) {
  bcrypt.hash(password, salt, function(err, hash) {
  // Store hash in database here
  });
});

When we put all the steps together, we get:

```js
const bcrypt = require ('bcrypt');

const saltRounds = 10;
var password = "Fkdj^45ci@Jad";

bcrypt.genSalt(saltRounds, function(err, salt) {
  bcrypt.hash(password, salt, function(err, hash) {
            // Store hash in database here
   });
});

The variable hash is returned, and we can store it in our database.

Combining salt and hash functions

Instead of writing the salt and hash functions separately, we can combine them into one function.

bcrypt.hash(password, saltRounds, function(err, hash) {
  // Store hash in database here
});

The above example gives the same result as the code below

bcrypt.genSalt(saltRounds, function(err, salt) {  
  bcrypt.hash(password, salt, function(err, hash) {
    // Store hash in database here
  });
});

Compare a password to a hash

Now that we've safely secured the hash in our database, when a user attempts to log in, we have to compare the plain text password to the hash. We do so by using the bcrypt compare function.

We pass bcrypt.compare() these parameters:

  1. The password we are comparing
  2. The hash that is stored in the database
  3. Callback of error and the result: If the password matches the hash, the result equals true. If it is not a match, the result equals false.
bcrypt.compare(password, hash, function(err, result) {
  // returns result
});

In the example below, we hard-coded a password to compare to the hash. If the password matches, we print "It matches!" and if not, we print "Invalid password!".

var password2 = "djlfhjd(456";
bcrypt.compare(password2, hash, function(err, result) {
  if (result) {
    console.log("It matches!")
  }
  else {
    console.log("Invalid password!");
  }
});

If we want to check to make sure the code works before querying the hash value, we can put the code inside the callback of our original hash function.

const bcrypt = require ('bcrypt'); // require bcrypt

const saltRounds = 10;  //  Data processing speed
var password = "Fkdj^45ci@Jad";  // Original Password
var password2 = "djlfhjd(456";
bcrypt.hash(password, saltRounds, function(err, hash) { // Salt + Hash
  bcrypt.compare(password2, hash, function(err, result) {  // Compare
    // if passwords match
    if (result) {
          console.log("It matches!")
    }
    // if passwords do not match
    else {
          console.log("Invalid password!");
    }
  });
});

If we run the code in the example above, the output is: Invalid password!

In real life, the compare function would not execute inside the hash callback. The example is just for learning and testing purposes. Also, instead of printing a statement, the user would either authenticate or not authenticate. If the passwords match, the user can log in. If they do not match, the user is denied access.

bcrypt.compare(password2, hash, function(err, result) {
  if (result) {
    // log in
  }
  else {
    // access denied
  }
});

Applied example: Storing a hash

In this example, we salt and hash the password, then store it in a PostgreSQL database.

const db = require('./db.js'); // connection variable
const bcrypt = require ('bcrypt'); // bcrypt

const saltRounds = 10; // data processing time

var username = "starbuck";
var password = "ldfgkj78%^&appdKO039*";

// query statement to store hash
var statement = "UPDATE user_table SET password = $1 WHERE username = $2";

// salt, hash, and store
bcrypt.hash(password, saltRounds, function(err, hash) {
  let values = [hash, username]; // query values
  // store hash in database
  db.query(statement, values, function(err,res) {
    if (err) throw err;
    else {
            console.log("stored!");
        }
  });
});

Applied example: Compare password with hash

In the example below, we query a stored hash from a PostgreSQL database. Once we retrieve that value, we compare it to a password and evaluate if the user can log in.

const db = require('./db.js'); // connection variable
const bcrypt = require ('bcrypt'); // bcrypt

var username = "starbuck";
var password = "ldfgkj78%^&appdKO039*";
// statement to query the user’s password
var statement = "select password from user_table where username = $1";
var values = [username]; // query values

// function to log in
function hasAccess(result){
  if (result) {
    // insert login code here
    console.log("Access Granted!");
  }
  else {
    // insert access denied code here
    console.log("Access Denied!");
  }
}

// query database for user's password
db.query(statement, values, function(err, res) {
  if (err) throw err;
  else {
    var hash = res.rows[0].password;
    // compare hash and password
    bcrypt.compare(password, hash, function(err, result) {
      // execute code to test for access and login
      hasAccess(result);
    });
  }
});

Recap

For security purposes, it is essential to salt and hash passwords before storing them in a secure database. Hashing algorithms turn a plain text password into a new fixed-length string called a hash. Before hashing a password, we apply a salt. A salt is a random string that makes the hash unpredictable.

Bcrypt is a popular and trusted method for salt and hashing passwords. You have learned how to use bcrypt's NodeJS library to salt and hash a password before storing it in a database. You have also learned how to use the bcrypt compare function to compare a password to a hash, which is necessary for authentication.

Further your understanding

  • What saltRounds value is appropriate for your current project? Why?
  • If the hash is genuinely unpredictable, how does the compare function work?
  • How much space do you need in the database for a hash?

Additional resources

Previous post:

We've now made access to all tutorials on Hey Node free. Anyone who signs up with an account can access our full course, Data Brokering with Node.js.

By Addison Berry on Apr. 27th, 2020
Next post:

The modular system in Node.js allows developers to keep their code organized and clean, and promotes reusability of code and adoption of best practices across Node.js applications. In this guest blog post by @sunnyenotick, learn about the different types of modules, and how to use different modules in your own Node project.

By Anna Mykhailova on May. 6th, 2020