Explore the future of Web Scraping. Request a free invite to ScrapeCon 2024

Store Passwords Safely in Python

How to hash passwords using the bcrypt library

image

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.

Installing bcrypt

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.

How to Create and Manage Virtual Environments in Python

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 password .

First of all, we should create the __init__ method.

class PasswordDatabase:
  def __init__(self):
    self.data = dict()

Here 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:

  1. Check if the user is already registered and if this is the case return False . Otherwise, it should:
  2. Hash the password;
  3. add the user and hashed password to the dictionary;
  4. return True .

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:

import bcrypt

Now we can create a method hash_password .

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 PasswordDatabase :

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.

Conclusion

Thank you for reading through to the end! If you want to learn more about the bcrypt library, here are some useful links:

Python bcrypt

Hashing Passwords In Python: Bcrypt Tutorial with Examples | HackerNoon




Continue Learning