How to use the JavaScript bcrypt library

By

Learn how to securely hash and verify passwords in JavaScript with bcrypt, using bcrypt.hash() with salt rounds and bcrypt.compare() to check a match.

~~~

The bcrypt npm package is one of the most used packages to work with passwords in JavaScript.

Install it with npm install bcrypt. bcrypt 6 needs Node 18 or later, and it ships prebuilt binaries for macOS, Linux and Windows, so most of the time there’s nothing to compile. On any other platform npm builds it from source with node-gyp, which needs Python and a C++ compiler. If you’re stuck on an old project, upgrade to at least version 5: older versions had bugs that silently weakened some passwords (passwords of 255 characters or more, and ones containing a NUL character).

This is security 101, but it’s worth mentioning for new developers: you never store a password in plain text in the database or in any other place. You just don’t.

What you do instead is, you generate a hash from the password, and you store that.

In this way:

import bcrypt from 'bcrypt'
// or
// const bcrypt = require('bcrypt')

const password = 'oe3im3io2r3o2'
const rounds = 10

bcrypt.hash(password, rounds, (err, hash) => {
  if (err) {
    console.error(err)
    return
  }
  console.log(hash)
})

You pass a number as second argument and the bigger that is, the more secure the hash is. But also the longer it takes to generate it.

The library README has an old rough benchmark for a 2GHz core. A modern CPU is faster, so take the numbers as a guide. What stays true is that each extra round doubles the time:

rounds=8 : ~40 hashes/sec
rounds=9 : ~20 hashes/sec
rounds=10: ~10 hashes/sec
rounds=11: ~5  hashes/sec
rounds=12: 2-3 hashes/sec
rounds=13: ~1 sec/hash
rounds=14: ~1.5 sec/hash
rounds=15: ~3 sec/hash
rounds=25: ~1 hour/hash
rounds=31: 2-3 days/hash

I built a free bcrypt cost calculator that helps you pick the right number of rounds for your hardware.

If you run bcrypt.hash() multiple times, the result will keep changing. This is key because there is no way to reconstruct the original password from a hash.

Given the same password and a hash it’s possible to find out if the hash was built from that password, using the bcrypt.compare() function:

bcrypt.compare(password, hash, (err, res) => {
  if (err) {
    console.error(err)
    return
  }
  console.log(res) //true or false
})

If so, the password matches the hash and for example we can let a user log in successfully and start a session.

You can use the bcrypt library with its promise-based API too, instead of callbacks:

const hashPassword = async () => {
  const hash = await bcrypt.hash(password, rounds)
  console.log(hash)
  console.log(await bcrypt.compare(password, hash))
}

hashPassword()

One thing to know: bcrypt only uses the first 72 bytes of a password. Anything after that is ignored when you compare. That’s bytes, not characters, so a password full of emoji reaches the limit sooner.

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about js: