Python Friday #227: Hash a Password With Bcrypt

When it comes to authentication, we often find some form of username and password as the backbone of the solution. Before we dive into those examples, I like to make a detour and first explain how we can securely hash passwords in Python. While it is possible to just store passwords as plaintext for an example application, such hacks tend to get into production and that would be a disaster waiting to happen. Let us do it the right way directly from the start and always hash our passwords.

This post is part of my journey to learn Python. You find the code for this post in my PythonFriday repository on GitHub.

 

Install bcrypt

There are a few libraries who implement the Bcrypt hashing algorithm in Python. Unfortunately, many are not in active development, and we have to be careful to pick an implementation that still got updates. At the time of writing this post, the library bcrypt got regular updates on GitHub. We can install it with this command:

 

Why not passlib?

The official tutorial for FastAPI uses passlib for password hashing, and the soon to be removed crypt module of Python suggests to use passlib instead. My problem with passlib is that the last commit was in 2020 and the attempt to restart the active development did not get any traction.

Since I only want to use Bcrypt, I decided to go a different way. If you need to integrate your application with another framework that hashed the passwords, you still may need to use passlib.

 

Create a hash

To create a hash of a password with bcrypt, we need two parts:

  1. The password as a string in binary format.
  2. A random seed that should be different for each password we hash and also be in binary form.

We create these two input parts for the hashpw() method with this code snippet:

The hash looks something like this:

$2b$14$J05Ywc7DepL61x4bgwBTMuqDL5IrKtd6.0gnE.Z9DVwhqYnvzSox.

The $2b$ shows that we use the current version of the Bcrypt algorithm, while 14$ shows us that we have chosen 14 rounds to slow down the hash generation.

 

Verify the hash

Since we use a different seed for each password, we cannot reuse the hashpw() method to verify if the hash we have matches the password. Instead, we need to use the checkpw() method:

 

Increase the work

When we create the seed, we can specify how many rounds we want to use. When we increase the round by one, we double the work needed to compute the hash and to verify it. The longer that takes, the harder it will be for an attacker to guess the password. Unfortunately, we cannot set the round value too high, then our users want to login without waiting for many minutes until their password matches the stored hash. We need to find a middle ground between reasonable slow and not too slow.

To get an idea what the effect of the increased rounds has on my machine, I wrote this little script:

When I run it, I get this output:

The runtime will be different on your machine, but the time to hash a password and to verify it should be roughly the same.

 

Next

With the Bcrypt algorithm we can turn a password into a hash and store that to increase the security of our application. With a good (= high) value for the rounds, we can slow down an attacker and make it much harder to gain access to our application. Next week we put that knowledge into action and use it with HTTP basic authentication in FastAPI.

1 thought on “Python Friday #227: Hash a Password With Bcrypt”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.