Password hashing
Password hashing in Python with Bcrypt
Using the bcrypt library to securely hash and check hashed passwords with Python
The security of users and user data should always be a priority any developer, especially when it comes to personal information such as passwords.
Stroring passwords as plain text, as we all know is extremely dangerous and just plain silly. If a database gets breached and plain text passwords are leaked, it's game over.
In this article, we're going to be exploring password hashing with bcrypt
, a popular, well tested and secure hashing library/algorithm available for Python.
Note - This isn't going to be a detailed guide on the inner working of hashing, more of a high level overview to introduce you to some of the concepts and best practices.
What is hashing
Not to be confused with encryption, hashing is the irriversable, one way process of taking a string and turning into a fixed length of seemingly random characters.
For example, here's the word encyclopedia
hashed using bcrypt
:
Unlike encryption which can be decoded and reversed into its original form, hashing is irriversable, meaning there's no way to retrieve the original string, making it ideal for passwords and authentication.
Comparing passwords
If we run the same bcrypt
hashing function again on the same word encyclopedia
, we get:
Which is clearly quite different from our first hash... ðĪ
If you're not familiar with hashing, you may be thinking at this point "So how do I compare a password with a hashed password?, especially when they're totally different"... and that's a great question.
bcrypt
comes with a function to allow us to check an unhashed string against a previously hashed string, meaning we can do things like logging a user into their account based on a plain text password they provide if it matches their hashed password stored in a database.
Cracking passwords
Hashed passwords can be cracked, quite easily really.
Here's a link to a great video featuring Dr Mike Pound over at Nottingham university explaining and demonstrating some of the techniques for cracking hashed passwords - https://www.youtube.com/watch?v=7U-RbOKanYs
Dr Pound will do a better job at explaining password cracking than I, but to summarize some of the techiques used:
Brute force attacks
Brute force attacks work by trying every different combination of characters for a given length, for example:
Until every possible combination of 5 characters have been hashed and compared.
However most modern cracking algorithms have moved onto something more advanced.
Dictionary attacks
A dictionary attack is the process of taking a list of words (Previously cracked passwords, commonly used words, randomly generated words etc..), hashing and then comparing them to the hashed passwords.
The dictionary cracking algorithms will try dirrerent combinations of words which are much more likely to be a real password as many of the word lists include actual passwords, typically aquired from big database breaches.
For example, theres a password list called "Rock you" containing somewhere around 14 million leaked passwords.
Just to put the speed of these cracking algorithms into perspective, a good quality graphics card can hash and compare somewhere around 4 - 10 billion words per second... PER SECOND!
The good news is that the chances of this can be dramatically reduced by following some best practices:
Securing your database
Using a reputable hashing function
Requiring users to provide longer passwords (At least 9 characters, ideally more)
Requiring a mixture of uppercase, lowercase, numbers and non aplhanumerical characters
Using uncommon words in a seemingly random order
The obvious one here is not letting your database fall into the hands of malicious actors, followed by requiring your users to create passwords of a suitable length, non alphanumeric characters and random uncommon words.
Installing bcrypt
Installing bcrypt is a simple pip
install away:
We're going to create a virtual environment and install bcrypt
with pip
:
Hashing passwords
To use bcrypt
, you'll need to import it:
Hashing passwords or any other string is incredibly simple using the bcrypt.hashpw()
function.
bcrypt.hashpw()
takes 2 arguments:
A string (bytes)
Salt
Salt is random data used in the hashing function and the randomness of it is important. We're not going to cover salt in this article but feel free to read this Wikipedia article for more information.
Fortunately for us, bcrypt
also provides a function to generate salt for us - bcrypt.gensalt()
.
Let's hash a password and print it:
This snippet prints:
And took 0.19132542610168457
ms (which will become more relevant later)
We used the b
prefix on the password
string to create a byte string, however if you were taking input from a user, you may want to call the .encode("utf-8")
method on the string:
Checking passwords
bcrypt
also comes with a function to check plain text passwords against hashed passwords, returning True
if the passwords match, else returning False
.
bcrypt.checkpw()
takes 2 arguments:
The plain text password (Must be bytes)
The hashed password
Let's hash a password and check it:
As expected, we got Password match!
.
Again, if you're taking user data (On a login page for example), you'll need to call .encode("utf-8")
on the user input password before checking it using bcrypt.checkpw()
Work factor
The "work factor" of a cryptographic system is the amount of time and resource required to break the system or its process.
bcrypt
features an adjustable work factor which we can pass to bcrypt.gensalt()
using the rounds
argument and providing an integer (The default is 12
).
Note - Whilst adjusting the work factor will make your passwords slower to crack, it will also consume resources on your machine.
Another important note - Do not set the rounds
argument too high! Your system may hang or just take too long to return a response.
For reference, we'll run bcrypt.hashpw()
and pass a few different values as rounds
to bcrypt.gensalt()
and time the results:
No argument (Default) -
0.192
ms:
Work factor of 14 -
0.755
ms:
Work factor of 15 -
1.522
ms:
Work factor of 16 -
3.018
ms:
Work factor of 18 -
12.258
ms:
As you'll see, rounds=15
takes considerably long to hash, so use wisely.
Consider thinking about what might be an acceptable time for your user to wait whilst you hash their password and adjust the work factor accordingly, also being sure to consider your server performance/hardware specs.
Wrapping up
Whilst this article wasn't designed to be an in depth guide on hashing algorithms, I hope it's shown you how easy it can be to hash and check passwords with Python using bcrypt
, along with pointing out some of the dangers of not hashing passwords or enforcing users to provide a complex password.
Happy hashing!Last modified · 14 Mar 2019 Reference : https://pythonise.com/categories/python/python-password-hashing-bcrypt
Last updated