Special Offer: My C#/.NET Bootcamp Course is out now. Get 10% OFF using the code FRIENDS10.

Hashing passwords can be complicated. There are algorithms, salts, hashes, and other terms involved.

Also, it should all be secure.


Today, I will explain what BCrypt is, when to use it, and how to use it in your .NET applications.

Avoid Storing Passwords

Before we learn more about BCrypt, let’s quickly mention that if you can avoid storing passwords, you should avoid it. 

One of the advantages, in this case, is that you cannot lose data that you don’t have. 

In most situations, using one of the established authentication providers makes sense. There are Microsoft Active Directory, Azure AD, Auth0, KeyCloak, IdentityServer, and many more.

Never Store a Password in Plain Text

Okay, let’s say you have to store passwords, and you cannot use one of the established authentication solutions.

The most important rule is that we should never store a password in plain text. If someone gets access to the data, they will have all the username/password combinations stored in your application. Maybe the users used the same password elsewhere.

Never do that.

Use Password Hashing

So, what’s the alternative?

The alternative is storing a hash. We can use a hashing algorithm to turn input data into a hash.

Hashing uses a one-way operation. It means that you can turn any input into a hash, but you will never be able to turn a hash back into its original input.

Well, never say never. But good algorithms make it very hard and very expensive.

What is BCrypt?

If you, for whatever reason, cannot use one of the authentication providers, BCrypt is a good choice.

Let’s quickly learn what BCrypt is and how it works. After that, we will write code using a BCrypt implementation for .NET.

BCrypt is a password-hashing function based on the Blowfish cipher and was first presented in 1999. It uses a salt to protect against rainbow table attacks and is adaptive.

Adaptive means that over time, the number of iterations the algorithm performs can be increased to make it slower and account for increased hardware processing power.

That’s enough theory for now. Let’s jump into Visual Studio.

Hashing a Password using BCrypt

I created a new console application. First, we need to install the BCrypt.Net-Next NuGet package containing the BCrypt implementation for .NET.

Now, let’s start by defining a password variable. We assign “Secret Password” as its value.

Next, we define another string variable that will contain the hashed password shortly. We use the BCrypt class within the BCrypt.Net namespace.

string password = "Secret Password";
string passwordHash = BCrypt.Net.BCrypt.EnhancedHashPassword(password, 13);

There is a naming clash because the namespace and the class are both called BCrypt. I will show you later how to fix it in .NET 6 and newer programs. But let’s move on by calling the EnhancedHashPassword method.

As the first method argument, we provide our password. As the second argument, we provide a work factor. The work factor is a number that indicates the level of how many iterations will be performed when calculating the hash.

The Work Factor

As discussed earlier, BCrypt is adaptive. We can set a work factor and change it in the future. The default work factor currently is 11.

For one of my applications, I currently use 13 as the factor. It takes about 800 ms to generate a hash using this work factor which I consider a good mix between security and duration. It means that trying ten passwords will take about 8 seconds making brute force attacks really slow.

On the GitHub page, you’ll see a table that lists the work factor and its associated number of iterations.

| Cost  | Iterations               |
|-------|--------------------------|
|   8   |    256 iterations        |
|   9   |    512 iterations        |
|  10   |  1,024 iterations        |
|  11   |  2,048 iterations        |
|  12   |  4,096 iterations        |
|  13   |  8,192 iterations        |
|  14   | 16,384 iterations        |
|  15   | 32,768 iterations        |
|  16   | 65,536 iterations        |
|  17   | 131,072 iterations       |
|  18   | 262,144 iterations       |
|  19   | 524,288 iterations       |
|  20   | 1,048,576 iterations     |
|  21   | 2,097,152 iterations     |
|  22   | 4,194,304 iterations     |
|  23   | 8,388,608 iterations     |
|  24   | 16,777,216 iterations    |
|  25   | 33,554,432 iterations    |
|  26   | 67,108,864 iterations    |
|  27   | 134,217,728 iterations   |
|  28   | 268,435,456 iterations   |
|  29   | 536,870,912 iterations   |
|  30   | 1,073,741,824 iterations |
|  31   | 2,147,483,648 iterations |

Source: https://github.com/BcryptNet/bcrypt.net

Work factor 11 uses 2048 iterations, while work factor 13 uses 8192 iterations. So, in simple terms, by adding 1 to the work factor, we cause double the iterations.

That’s enough about the work factor.

Exploring the BCrypt Generated Hash

Next, we print the generated hash to the console.

Let’s start the program and take a look at the generated hash.

$2a$13$HbS9OCDLo4ppKvFbqNEcKOhXOS09PF.9AzZX0bFsPa1KXEHmKmVLi

The hash contains 60 characters and starts with an information block before the actual password hash. The 13 we can see after the second $ sign equals the work factor we used to generate the password hash.

It allows us to later increase the work factor for new passwords but still be able to verify the hashes of passwords created with a different work factor.

One thing to mention here is that the hash also contains a salt. We can see that if we restart the application. We get an entirely different hash although we use the same password to generate the string.

Verifying a Hashed Password Using Bcrypt

The salt being part of the hash means that we cannot hash a user input and compare it with a previously generated hash to find out if the input was correct.

Instead, we need to use the VerifyEnhanced method and provide the user input as well as the stored hash.

string password = "Secret Password";
string passwordHash = BCrypt.Net.BCrypt.EnhancedHashPassword(password, 13);

Console.WriteLine(passwordHash);
Console.WriteLine(BCrypt.Net.BCrypt.EnhancedVerify("Secret Password", passwordHash));

Let’s add another Console.WriteLine method call that prints whether the input is correct. We use the EnhancedVerify method of BCrypt and provide the user input as the first and the password hash as the second argument.

Let’s start the program.

$2a$13$cZ8vFkfATUDppPe9IeYpPuOesEVlw8t9WiPxZiiNXJceehkmXG2ry
True

As you can see, it prints true as expected. We provided the same password for the generation of the hash as well as the verification.

Now, let’s alter the input for the EnhancedVerify method.

Console.WriteLine(BCrypt.Net.BCrypt.EnhancedVerify("Secret Password2", passwordHash));

When we start the program again, we see False, meaning the password verification detected that the user input doesn’t match the input used to generate the hash.

The salt being part of the hashed password might be a little confusing at first. Especially, when you previously used other algorithms and needed to handle the salt yourself. 

Personally, I like how BCrypt handles the salting. It makes it really simple to use, and it also prevents you from not salting your passwords.

If you, for whatever reason, want to have more control over the salt generation, you can use the GenerateSalt method and provide the salt to the EnhancedHashPassword method. See the documentation for more details on that.

Resolving the Namespace and Class Naming Clash

As discussed earlier, the fact that BCrypt is the name of the namespace and the name of the class is unfortunate.

However, with .NET 6 and newer, we can resolve the issue with a simple line in the project file (*.csproj).

<ItemGroup>
    <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
    <Using Include="BCrypt.Net.BCrypt" Alias="BC" />
</ItemGroup>

We add a “using” definition and provide an alias for the namespace.

We can now change the code and use the alias to call the static methods EnhancedHashPassword and EnhancedVerify.

string password = "Secret Password";
string passwordHash = BC.EnhancedHashPassword(password, 13);

Console.WriteLine(passwordHash);
Console.WriteLine(BC.EnhancedVerify("Secret Password", passwordHash));

Conclusion

Let’s quickly summarize what we learned.

Whenever possible, we should use one of the existing authentication providers. If that’s not possible, we should make sure to use proper hashing and never store passwords in plain text.

BCrypt offers an adaptable algorithm, and the BCrypt.Net-Next NuGet package offers a simple-to-use .NET implementation.

The fact that the salt and the work factor are included in the generated hash makes it simple and secure to work with BCrypt for hashing passwords using .NET.

Don’t forget to subscribe to my YouTube channel if you haven’t already, and I will see you in the next video.

 

Claudio Bernasconi

I'm an enthusiastic Software Engineer with a passion for teaching .NET development on YouTube, writing articles about my journey on my blog, and making people smile.