Introduction
Implementing authentication (i.e. user login) in a system sounds simple. Just compare username and password with what is stored in the database, right?
Actually, it’s not nearly as simple as it looks to the user logging in. Storing and checking user credentials is a process with different levels of security (and I’m no expert, so there may be more):
- Store passwords in plain text. Very bad idea.
- Hash the passwords. They won’t be easily readable, but can be cracked by rainbow tables.
- Salt and hash the passwords. Protects against rainbow tables as well, but still vulnerable to high-performance brute force attacks.
- Use a slow hash algorithm to limit the effectiveness of brute force attacks.
My article “Securing Passwords by Salting and Hashing” covers the first three items. This article will deal with the fourth item, and introduce BCrypt, which gives you all four.
The source code for this article is available at the Gigi Labs BitBucket repository.
Authentication with BCrypt
Before we discuss the merits of using BCrypt, let’s see how to use it.
You first need to include the BCrypt library in your project. You can do this via NuGet:
The functionality we need to use is all in a class called BCryptHelper
, which you get access to by including the following namespace:
using DevOne.Security.Cryptography.BCrypt;
With that, it is very easy to generate a salt, hash a password with it, and validate the password against its salted hashed version:
static void Main(string[] args) { Console.Title = "BCryptTest"; string password = "My$ecureP@$sW0Rd"; string salt = BCryptHelper.GenerateSalt(); string hashedPassword = BCryptHelper.HashPassword(password, salt); bool valid = BCryptHelper.CheckPassword(password, hashedPassword); Console.WriteLine("Salt: {0}", salt); Console.WriteLine("Hashed Password: {0}", hashedPassword); Console.WriteLine("Valid: {0}", valid); Console.ReadLine(); }
Here’s the output of this little program:
The BCrypt hashed password is typically a 60-byte string. As you can see, the salt is actually embedded within the hashed password (this StackOverflow answer explains more about how this works). This means you don’t need to store the salt separately.
Why BCrypt?
The functionality we have seen in the previous section doesn’t really give us anything more than hashing and salting with any other reasonably strong hash function. So why use BCrypt?
In many programming situations, writing code that executes fast is a good thing. Authentication is not one of those. If the algorithms you use to authenticate your users are fast, that means that brute force attacks may attempt large amounts of combinations per second – more so with modern hardware and GPUs.
Algorithms such as PBKDF2 and BCrypt differ from traditional hash algorithms such as MD5 or SHA256 in that they take a work factor as an input. That is, you can decide how fast or slow the algorithm runs. So if, for instance, you set up your algorithm to take 1 second to validate a password, that greatly limits the possibilities of brute force attacks when compared to algorithms that can run several hundreds or thousands of times per second. Read more about why BCrypt is badass at this Security StackExchange answer.
In BCrypt, the GenerateSalt()
method takes an optional logRounds
parameter that affects the performance of subsequent hash operations. It has a default value of 10 and can be set to a number between 4 and 31. The algorithm will run 2 to the power of logRounds
times, making it run exponentially slower. To get an idea of this, I wrote some simple benchmarking code with the help of my trusted ScopedTimer class (from “Scope Bound Resource Management in C#“):
static void GenerateSaltBenchmarks(string password) { for (int i = 10; i < 16; i++) { using (var scopedTimer = new ScopedTimer($"GenerateSalt({i})")) { string salt = BCryptHelper.GenerateSalt(i); string hashedPassword = BCryptHelper.HashPassword(password, salt); } } }
Here are the results:
Summary
Use BCrypt to securely store and validate your passwords. It’s easy to use, easy to store, and hard to break. Also importantly, you can make it as slow as you like.