Password Hashing and Salting Explained

When users sign up for your application with an username and password, it’s dangerous to store their passwords in your database as plaintext. If attackers breach your database, they have a treasure trove of usernames and passwords ready to exploit. Password hashing and salting helps store passwords in a secure manner and is the the most basic concept for authentication implementations. Today, most authentication providers will handle this for you.

What is Password Hashing?

Password hashing is a security technique used to transform a plaintext password into a unique string of characters known as a hash. Hash functions are one-way operations, meaning that once a password is hashed, it cannot be reversed or deciphered to reveal the original password. This characteristic makes hashing a powerful tool for securing passwords because even if an attacker gains access to the hash, they cannot easily retrieve the user’s actual password.

Here’s the magic:

  • Deterministic: The same password will always produce the same hash. This is essential for checking if a user’s entered password matches the stored one.
  • Irreversible: You can’t easily turn a hash back into the original password. This makes it much harder for attackers to recover passwords if they get their hands on hashed data.

Why Plain Hashing Isn’t Enough

While better than plain text, simple hashing still has vulnerabilities:

  • Rainbow Tables: Pre-computed lists of hashes for common passwords make cracking easy.
  • Identical Passwords: Two users with the same password will have identical hashes, making brute-force attacks more efficient.

This is why we also need password salting.

Enter Password Salting

Salting is like adding a pinch of, well, salt to the hashing process. The salt ensures that even if two users have the same password, their hashes will be different due to the unique salts. Furthermore, it makes precomputed rainbow tables ineffective, as the attacker would need a specific table for each salt, which is impractical to create or store.

Here’s how it works:

  1. Generate a Salt: A long, random string of data is created for each user.
  2. Combine with Password: The salt is appended (or prepended) to the user’s password.
  3. Hash the Combo: The salted password is fed into the hashing function.
  4. Store Both: The resulting hash AND the unique salt are stored in your database. They don’t need to be kept secret like passwords; their role is to ensure uniqueness and complexity.

Implementation

  1. Choosing a Strong Hash Function: Opt for secure, slow hash functions designed for password hashing, like bcrypt, Argon2, or PBKDF2. These algorithms are resistant to brute-force attacks and can be configured to be computationally intensive, making them difficult to crack.
  2. Generating a Unique Salt for Each User: Every time a password is created or changed, generate a new, random salt. This can be accomplished using secure cryptographic functions available in most programming languages or libraries.
  3. Storing Hashes and Salts: Store the resulting hash and salt securely in your user database. Ensure that your database is protected and access is tightly controlled.
  4. Verifying User Logins: When a user attempts to log in, retrieve the salt from the database, append it to the entered password, hash this combination, and compare it to the stored hash. If they match, the login is successful.

Here’s a short code example to make it clearer on an implementation level:

Python
import bcrypt

def hash_password(password):
    """
    Hashes a password using bcrypt, including salting. Returns the hashed password.
    """
    return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())

def verify_password(stored_password_hash, provided_password):
    """
    Verifies a provided password against a stored hashed password.
    """
    return bcrypt.checkpw(provided_password.encode('utf-8'), stored_password_hash)

# Example Usage

# Simulating a user signing up and creating a password
user_password = "hunter2"
hashed_password = hash_password(user_password)  # Hash and salt the password for storage

# Later on, verifying the password provided by the user at login
login_attempt_password = "hunter2"
verification_result = verify_password(hashed_password, login_attempt_password)

# Outputs
print("Hashed Password:", hashed_password)
print("Verification Result:", verification_result)

In the Real-World

Most programming languages have libraries for password hashing and salting. You don’t need to implement the low-level math yourself!

Today, most developers won’t even have to write their own password hashing and salting functions, as authentication solutions will handle this for them. For example, Firebase Auth is a popular, free authentication solution that comes with comprehensive APIs to implement secure auth easily. Some details about how Firebase Auth’s password hashing and salting can be found here: Firebase Authentication Password Hashing.

Topics