When storing passwords, one of the greatest risks is that someone may steal the database and be able to decrypt them. To avoid this, you should always hash the passwords before storing them.
In this article, I will show you how to do this in Python using the bcrypt library.
First of all, we need to install bcrpyt using pip.
pip install bcrypt
It is good practice (although not required) to create a new virtual environment for this project. If you want to learn more about this, check the following article.
What is Password Hashing?
First of all, it's important to know the theory behind hashing.
A hashing function is a function that takes a string of bytes, and “transform” it to another string of bytes, from which it is impossible to determine the initial string. Furthermore, there aren't two different inputs that give the same output.
This means that if we store the hashed password, even if someone stole the database they would not be able to determine what are the plain text passwords.
Now suppose that a user has registered with the password
"HelloWorld" . Then to execute the login, we need to check if the password written by the user is the same as the stored one.
To do so, we can just hash the password used for the login, and then check if this hash corresponds to the stored one. Since by the definition of hash function there aren't two different inputs that give the same output, the two hashes will be equal only if the password written by the user is the same as the one used during registration.
The only weakness is that if the password is short, an attacker may try to hash all possible passwords until he finds the correct one. However, this is unlikely if the password is long enough since there are too many combinations. But how to make sure that the password is long?
Usually, before hashing a password we will add a salt, i.e. a random sequence of characters. In this way, we know that even if the user uses a short password, it will be still secure.
Create a Password Database
Now that we have seen the idea behind hashing, let's see how this works in Python. We will create a class
PasswordDatabase , which will implement a method
register(user, password) to add a new user and
login(user, password) which returns true if the user
user is registered with a password
First of all, we should create the
class PasswordDatabase: def __init__(self): self.data = dict()
data is the dictionary which stores usernames and passwords: the usernames are used as keys of the dictionary.
Registering a New User
Now let's create the function to register a new user. It will:
- Check if the user is already registered and if this is the case return
False. Otherwise, it should:
- Hash the password;
- add the user and hashed password to the dictionary;
The first step is easy to implement: we just need to check if the username is already present in the dictionary.
def register(self, user, password): if user in self.data: return False
Now we need to hash the password. Here is where bcrypt comes to our help.
First of all, we need to import it:
Now we can create a method
def hash_password(self, password): pwd_bytes = password.encode("utf-8") salt = bcrypt.gensalt() return bcrypt.hashpw(pwd_bytes, salt)
The first line is to convert the password (which is a string) into a sequence of bytes. In fact, bcrypt functions will operate on sequences of bytes, not on strings.
Then we create a salt for the password using
bcrypt.gensalt() . Finally, we can create the hash from the password bytes and the salt and return it.
Let's go back to the
register function. Here we need to call the
hash_password method we just created, and then save the username and the hash into the dictionary. The function returns True because a new user has successfully been saved.
def register(self, user, password): if user in self.data: return False pwd_hash = self.hash_password(password) self.data[user] = pwd_hash return True
Log In a User
Another method needed by the
Passworddatabase class is the one for executing login. This will take as input a username and a password, and then check if a user with the given username and password is registered.
def login(self, user, password): if user not in self.data: return False pwd_bytes = password.encode("utf-8") return bcrypt.checkpw(pwd_bytes, self.data[user])
First of all, we need to return false if the username is not present in the database.
Then we can check if the password given as input corresponds to the hash saved in the database. As before, we need to convert the password string into bytes using the
encode("utf-8") function. Then we can call the function
bcrypt.checkpw . This takes as argument the password (in bytes) and the hash. Then it returns true if and only if the password is correct.
Testing The Code
Here is the complete code of our
import bcrypt class PasswordDatabase: def __init__(self): self.data = dict() def register(self, user, password): if user in self.data: return False pwd_hash = self.hash_password(password) self.data[user] = pwd_hash return True def login(self, user, password): if user not in self.data: return False pwd_bytes = password.encode("utf-8") return bcrypt.checkpw(pwd_bytes, self.data[user]) def hash_password(self, password): pwd_bytes = password.encode("utf-8") salt = bcrypt.gensalt() return bcrypt.hashpw(pwd_bytes, salt)
We can test it with the following lines of code:
db = PasswordDatabase() print("Registering users") print(db.register("john", "password")) print(db.register("Seth", "HelloWorld")) print(db.register("john", "myname")) print("Login") print(db.login("abc", "password")) print(db.login("john", "pwd")) print(db.login("john", "password"))
If everything works correctly, the last register function should return false, because a user named “john” already exists.
Then when logging in the users, only the last call should return true: it is the only one where both username and password are correct.
Thank you for reading through to the end! If you want to learn more about the bcrypt library, here are some useful links: