Sharing notes from my ongoing learning journey — what I build, break and understand along the way.
Cryptography – Post 2: Hashing Passwords — The Right Way
Learning Cryptography – Post 2: Hashing Passwords — The Right Way
Hashing is a powerful tool — but when it comes to storing passwords securely, it’s not enough by itself.
After digging deeper, here’s what I learned about the risks of using sha256(password)
and how libraries like bcrypt
solve those problems
Problem 1: Same input = same hash
Hash functions like SHA-256 are deterministic:
import hashlib
password = "123456"
hashed = hashlib.sha256(password.encode()).hexdigest()
print(hashed)
No matter how many times you run it, it will always return the same result.
This means:
- Two users with the same password will have the same hash.
- Attackers can use rainbow tables (precomputed hash dictionaries) to guess common passwords instantly.
Step 1: Add a Salt
A salt is a unique, random string added to the password before hashing:
salt = "g3n#x9v!"
combined = password + salt
hashed = hashlib.sha256(combined.encode()).hexdigest()
This makes each password hash unique — even if the password is the same.
But there’s a catch:
SHA-256 is still very fast — attackers can try millions of guesses per second.
Problem 2: Speed is a threat
Fast hash functions are great for integrity checks, but terrible for password security.
Why?
- Attackers can try billions of hashes per day.
- Even with salt, brute-force is still viable unless we slow things down.
Solution: Use bcrypt
bcrypt
is a password hashing algorithm designed to be:
- Slow on purpose
- Resistant to brute-force attacks
- Automatically salted
- Cost-adjustable (you can define how slow it is)
bcrypt in Python
import bcrypt
password = "supersecret".encode()
# Hashing (salt is included internally)
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
# Verifying
bcrypt.checkpw(password, hashed) # returns True
Every time you hash the same password, you get a different result — because bcrypt.gensalt()
adds randomness.
Yet checkpw()
still works because:
- It extracts the salt from the stored hash
- It rehashes the input using the same salt
- It compares the results securely
What does a bcrypt hash contain?
Example:
$2b$12$KMnqWD9TcHOxI0n97Bz0s.8z9OAiK9AYIxB23zP1BdZZ3aqhtidXW
$2b$
→ version12
→ cost (2¹² = 4096 rounds)- rest → salt and hash
All the info needed to verify the password is inside this single string.
What I took away from this:
- Hashing with SHA-256 is not enough for passwords.
- Salting prevents rainbow table attacks.
- But speed matters — hash functions should be slow.
bcrypt
is purpose-built for password security.- One hash string contains everything needed: algorithm, salt, cost, digest.