DEV Community

Cover image for The One Hash Function You Need, BCrypt
CoddingAddicts
CoddingAddicts

Posted on

The One Hash Function You Need, BCrypt

Whether you are building authentication for your app or designing a communication scheme, one thing is sure, you need hashes. And we got you the best kind. The scalable kind with a pinch of salt!.

bcrypt is a password-hashing function designed by Niels Provos and David Mazières, based on the Blowfish cipher and presented at USENIX in 1999.

Technicalities aside, this algorithm has two very important properties.

One, it employs a salt-mechanism to mitigate Rainbow Table attacks. And two, it introduces control over computation-time cost to scale with processing power and keep Dictionary attacks within practically-infeasable range.

This exposition targets JS users. But keep in mind that there is a Python implementation.

To get you started, you can install the package by typing,

npm install bcrypt
Enter fullscreen mode Exit fullscreen mode

From there you can import the library using,

// ES5 syntax
const bcrypt = require('bcrypt')
// ES6 syntax
import bcrypt from 'bcrypt'
Enter fullscreen mode Exit fullscreen mode

The package is relatively small and has only essential features. Exposed to us in two flavor, both synchronous and asynchronous, these are,

1. Salt Generation

// Sync
const salt = bcrypt.genSaltSync(rounds)

// Async
bcrypt.genSalt(rounds).then(function(salt){
        // Use the salt
})

Enter fullscreen mode Exit fullscreen mode

2. Hashing

// Sync
const hash = bcrypt.hashSync(plainMessage, salt)
/* Hash with auto-generated salt */
const hash = bcrypt.hashSync(plainMessage, saltRounds)

// Async
bcrypt.hash(plainMessage, saltRounds).then(function(hash) {
    // Use the hash
})
Enter fullscreen mode Exit fullscreen mode

3. Verification

// Sync
bcrypt.compareSync(testMessage, hash)

// Async
bcrypt.compare(testMessage, hash).then(function(result) {
    // result == true or false
})
Enter fullscreen mode Exit fullscreen mode

It might be surprising but this is all there is to Bcrypt.js. Pretty simple, huh!


Now to help you better see this in action. Here is a quick example of how a simple authentication scheme using Express.js and Mongoose.js would look like.

We will make a simple Node.JS backend. Express will handle the requests while, Mongoose will be used to store the user data. And before any of that, make sure you created a npm project and have both packages installed along with Bcrypt(as shown above).

From here, it is a 3+0-step work.

Step ZERO

We lay the structure of our app, by setting up an Express server with two POST routes to handle both register and login actions.

/* filename: ./index.js */

const Express = require("express");
const bcrypt = require("bcrypt");

// Import our User Model
const User = require("./model");

// Connection to Mongo DB
const Connect = require("./Connectdb");

const app = Express();

// CONSTANTS (these can be put in a .env file)
const SALT_ROUNDS = 10
const PORT = 3000

// Middleware for sending JSON type messages
app.use(express.json());

// Handling Registration for New Users
app.post('/register',(req, res)=>{
  // CODE for handling Registration will go here ...
})

// Handling Login 
app.post('/login',(req, res)=>{
  // CODE for handling Login will go here ...
})

// Server Launch
app.listen(PORT, () => {
  console.log(`Sever online and accessible via localhost:${PORT}.`);
});
Enter fullscreen mode Exit fullscreen mode

Note: for more information on how to use the .env utility, see this article.

Step ONE

For this step we need to make two things. One, is to write the code that will enable the connection to our Mongo DB.

const mongoose = require("mongoose");

const ConnectDb = (url) => {
  return mongoose.connect(url, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  });
};

module.exports = ConnectDb;
Enter fullscreen mode Exit fullscreen mode

Two, we create a user model. We keep it simple; our model will only have email, password, and username fields.

const mongoose = require("mongoose");

const UserSchema = new mongoose.Schema({
  username: String,
  password: String,
  email: String,
});

module.exports = mongoose.model("User", UserSchema);
Enter fullscreen mode Exit fullscreen mode

Step TWO

Now that everything is ready, it is time to hash password and add the salt!

To make the register controller, we need to:

  1. - Verify if the user already exists.
  2. - Generate a salt then, hash the password.
  3. - Save the user with the hashed password.
  4. - Return the User object.
//Register Route
app.post("/register", async (req, res) => {

  // deconstruct the body sent via request
  const { username, password, email } = req.body;

  // check if all the information is filled properly
  if (!username || !password || !email) {
    res.status(400).json({ msg: "Please provide valid information." });
  }

  // Generate the salt
  const salt = await bcrypt.genSalt(SALT_ROUNDS);

  // Hash The password
  const passwordHash = await bcrypt.hash(password, salt);

  // Check if the user already exits in our Database
  const IsExist = await User.findOne({ email });
  if (IsExist) {
    res.status(400).json({ msg: "User already exists." });
  }

  try {
    const savedUser = await User.create({
      username,
      email,
      password: passwordHash,
    });

    res.status(200).json({ msg: "Successfully added user:", savedUser });
  } catch (error) {
    res.status(500).json({ error });
  }
});
Enter fullscreen mode Exit fullscreen mode

To test this route, we can use _VS Code'_s extension, Thunder Client. We make an API request to our server with email and username and password in the body like so,

a post request to an the register/login route with bcrypt as a hashing algorithm for our passwords and safely stored in db

and as you can see, the response contains the hash of our password. It is important to note that the returned hash embeds information about its computation.

$2b$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
 |  |  |                     |
 |  |  |                     hash-value = K0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
 |  |  |
 |  |  salt = nOUIs5kJ7naTuTFkBy1veu
 |  |
 |  cost-factor => 10 = 2^10 rounds
 |
 hash-algorithm identifier => 2b = BCrypt
Enter fullscreen mode Exit fullscreen mode

Taken from the package's official npm page.

Step THREE

The elaboration of the login controller is much more concise than the registration process. All we need to do is,

  1. - Check if the user is actually registered.
  2. - Verify the password.
  3. - Return the User object.
// Login Route
app.post("/login", async (req, res) => {

  // deconstruct the request body
  const { email, password } = req.body;

  // check if all the information is filled properly
  if (!password || !email) {
    res.status(400).json({ msg: "Please provide valid information." });
  }

  // check if user already exists
  const userExists = await User.findOne({ email });
  console.log(userExists);

  if (!userExists) {
    res.status(400).json({ msg: "Please Register." });
  }

  // verify the given password 
  const isPassword = await bcrypt.compare(password, userExists.password);

  // if incorrect
  if (!isPassword) {
    res.status(400).json({ msg: "Email or password incorect." });
  }

  //if correct
  res.status(200).json({ userExists });
});

Enter fullscreen mode Exit fullscreen mode

We again use Thunder to test the route.

thunder request to login route using bcrypt to compare the passwords

The response object do contain the user in our database and since the password is correct, the hash will match and the user can be logged safely without the need of storing sensitive data.

Please acknowledge that this is not a production-ready application. We wrote this article with the aim of showcasing the use of Bcrypt.js in authentication.


If you are a developer who doesn’t want to the headaches of cryptographic technicalities and just wants a default go-to utility. Bcrypt.js is what you need for all your hash matters. Don’t get me wrong, I am saying it is perfect in all regards but at least it mitigates the most obvious attacks.

To back up this claim. We share with you a benchmark test done with our potato PC. It shows computation-time cost per number of rounds.

Salt Rounds vs. Time

As you can see, the number of rounds controls how much time is needed for computation. And for data transmission, any choice under 10 rounds wouldn’t put too much strain on the speed of your communications. The implementation wouldn't be too far from the register/login example.

Overall Bcrypt.js is simple and versatile. Let us know in the comment what do you think!

Find the code at CoddingAddicts/BcryptDoc

This was the Codding Addicts and until next time.

Oldest comments (0)