Password HashingΒΆ
π― What Youβll LearnΒΆ
- Why password hashing is essential for security
- How hashing differs from encryption
- How bcrypt protects against brute-force attacks
- How to implement password hashing and verification in FastAPI using Passlib
π Why Hash Passwords?ΒΆ
When users sign up, they provide a password. If you store that password directly (plaintext), anyone with access to your database can see it β including attackers.
Hashing solves this by transforming the password into a one-way, irreversible string. Even if the database is leaked, the original password cannot be recovered.
π Hashing vs EncryptionΒΆ
| Feature | Hashing | Encryption |
|---|---|---|
| Direction | One-way | Two-way (encrypt/decrypt) |
| Purpose | Verify identity | Protect readable data |
| Reversible | β No | β Yes |
| Use case | Passwords | Messages, files, tokens |
Hashing is ideal for passwords because you never need to decrypt β you only need to compare.
π§ Why bcrypt?ΒΆ
bcrypt is a hashing algorithm designed specifically for passwords. Itβs slow and salted by default, which makes it resistant to:
- Brute-force attacks: Trying millions of passwords per second
- Rainbow table attacks: Precomputed hash lookups
bcrypt ensures that:
- Identical passwords produce different hashes (thanks to salt)
- Hashing takes time, making mass guessing impractical
π οΈ Step-by-Step: Implementing bcrypt in FastAPIΒΆ
π¦ Step 1: Install Passlib with bcrypt supportΒΆ
pip install passlib[bcrypt]
Passlib is a Python library that wraps bcrypt and other secure hashing algorithms.
π Step 2: Create a Password Utility ModuleΒΆ
π core/security.py
from passlib.context import CryptContext
# Create a password hashing context
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
"""Hash a plaintext password using bcrypt."""
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""Verify a plaintext password against a hashed password."""
return pwd_context.verify(plain_password, hashed_password)
π Whatβs happening here?ΒΆ
CryptContextmanages hashing schemes (we use bcrypt)hash_password()takes a raw password and returns a hashed versionverify_password()checks if a raw password matches a stored hash
π§ͺ Example: Hashing and VerifyingΒΆ
raw = "mysecret123"
hashed = hash_password(raw)
print(hashed)
# $2b$12$... (bcrypt hash)
print(verify_password("mysecret123", hashed)) # β
True
print(verify_password("wrongpass", hashed)) # β False
You never store or compare raw passwords. You only store the hash and use verify_password() during login.
π Where to Use This in FastAPIΒΆ
- Signup: Hash the password before saving the user
- Login: Verify the raw password against the stored hash
π routers/auth.py (signup snippet)
user.hashed_password = hash_password(user.hashed_password)
session.add(user)
π routers/auth.py (login snippet)
if not verify_password(form_data.password, user.hashed_password):
raise HTTPException(status_code=401, detail="Invalid credentials")
π§ RecapΒΆ
Password hashing is:
- One-way and irreversible
- Essential for protecting user credentials
- Best done with bcrypt via Passlib
You now have secure utilities to:
- Hash passwords during signup
- Verify passwords during login
π§ͺ Practice ChallengeΒΆ
Create a UserCreate model with a password field.
In your signup endpoint:
- Accept
UserCreate - Hash the password
- Store it as
hashed_passwordin the database