BCrypt: Hash Passwords Correctly
Matthew James Davis on

On 11 Jan 2016, Milq was hacked. Their database was stolen, the user table dumped to a text file, and shared to the internet. In fact, this is a very common occurrence, with a very simple solution: BCrypt.

Bcrypt has the best kind of repute that can be achieved for a cryptographic algorithm: it has been around for quite some time, used quite widely, “attracted attention”, and yet remains unbroken to date.

Implementing the BCrypt algorithm is basically done for you; there are libraries in almost every language that handle everything for you. Let’s look at how to use them.

.NET (C#)

There are a few out BCrypt libraries in NuGet, and I personally use BCrypt.NET. You can install this through Visual Studio by

  1. right clicking your solution,
  2. clicking “Manage NuGet Packages for Solution…”
  3. clicking the “Browse” tab in the NuGet window
  4. setting “Package source” in the upper right to “nuget.org”
  5. searching for “bcrypt”
  6. selecting BCrypt-Official
  7. ticking the checkbox of the project you to install it in on the right
  8. and clicking install.

Now you’re ready to use it. Here’s an example of how to use it in code. Easy.

// hash and save a password
hashedPassword = BCrypt.Net.BCrypt.HashPassword(submittedPassword);

// check a password
bool validPassword = BCrypt.Net.BCrypt.Verify(submittedPassword, hashedPassword);

node.js (javascript)

There are a few BCrypt libraries in npm as well, and I personally use bcryptjs. You can install this through npm by running npm install bcryptjs --save from your project’s root directory.

Here’s an example of how to use it in code. Again, easy.

// first, require it in
var bcrypt = require('bcryptjs');

// hash and save a password
hashedPassword = bcrypt.hashSync(submittedPassword, 10);

// check a password
bool validPassword = bcrypt.compareSync(submittedPassword, hashedPassword)

python

Python has only one widely used BCrypt library that I know of, aptly named bcrypt. You can install this by running pip install bcrypt.

Here’s an example of how to use it in code. This is a bit different from the previous two examples, but still straightforward.

# first, import it
import bcrypt

# hash and save a password
hashedPassword = bcrypt.hashpw(submittedPassword, bcrypt.gensalt(10))

# check a password
validPassword = bcrypt.hashpw(password, hashed) == hashed

Migrating to BCrypt

Okay, so implementing BCrypt is very easy. But your mature application, like Milq, is using a very primitive hashing algorithm, and you can’t exactly recover the passwords to BCryptify them. But there are options, and if you’ve ever receieved an email from a website urging you to log in for “security reasons,” you’ve probably seen them in practice.

The strategy here is to assume everything is already BCrypt; when BCrypt libraries find non-BCrypt passwords, they throw errors. By migrating your current password code into the catch block of such an error, you can check for valid passwords using your old algorithm and update the passwords on the fly to BCrypt. Here’s an example in C#:

bool validPassword = false;

try
{
	// check the password using bcrypt
    validPassword = BCrypt.Net.BCrypt.Verify(submittedPassword, hashedPassword);
}

// if a salt parse exception is thrown, the password has not been updated
// check against the old algorithm and update if the password is valid
catch (BCrypt.Net.SaltParseException)
{
    validPassword = OldCheckPasswordFunction(submittedPassword, hashedPassword))
    if (validPassword) 
    {
        user.Password = BCrypt.Net.BCrypt.HashPassword(login.Password);
    }
}

As easy as that, your database will record-by-record be updated to BCrypt as users log in, with your users none the wiser.

The Cost Parameter

In the node.js and python examples above, you may have noticed the number 10 in a few spots. This number is the cost parameter. The cost parameter is used to increase the time required to complete one hash. If the cost parameter is very high, it will take longer to complete a hash. For a user with a single password, a wait of 500 ms is not noticeable; for an attacker with billions or more of passwords, cracking the system will become impossible.

Since computers become faster over time, the cost parameter was introduced as a variable. What might have taken 500ms in 2001 might require only 5ms today. Therefore, this number can be increased to fit your application’s particular needs. Here’s how to do it:

c#

int costParameter = 12;
hashedPassword = BCrypt.Net.BCrypt.HashPassword(login.Password, costParameter);

node.js

var costParameter = 12;
hashedPassword = bcrypt.hashSync(submittedPassword, costParameter);

python

costParameter = 12;
hashedPassword = bcrypt.hashpw(submittedPassword, bcrypt.gensalt(costParameter));

Why is BCrypt better?

A BCrypt Hash

BCrypt is not the best algorithm out there; however, it is sufficient for the large majority of use cases, and it is just as easy to implement, if not easier, than the basic hash-and-salt method. What sets BCrypt apart is that instead of the more typical SHA-* algorithm, it leverages the Blowfish algorithm, which has the advantage of being much slower in parallel. Since users log in one-at-a-time, this makes it much harder for attackers, who will test numerous passwords, to beat the algorithm.

Links

OWASP Password Storage Cheat Sheat
In-Depth Explanation of BCrypt
60% of the Milq passwords were deciphered