Flask Password Hashing
Password hashing converts a plain-text password into a fixed-length string that cannot be reversed. Flask applications use Werkzeug's built-in hashing utilities to store passwords safely. Even if attackers steal your database, hashed passwords give them nothing usable.
Why Plain-Text Passwords Are Dangerous
Database breach without hashing: users table → password = 'hunter2' Attacker reads it instantly. Game over. Database breach with hashing: users table → password_hash = '$pbkdf2-sha256$29000$...$...' Attacker cannot reverse this. Account stays safe.
Hashing vs Encryption
| Property | Hashing | Encryption |
|---|---|---|
| Reversible? | No (one-way) | Yes (with the key) |
| Use for passwords? | Yes | No |
| Verification | Hash the input and compare | Decrypt and compare |
Passwords should be hashed, not encrypted. If you can decrypt a password, so can an attacker who steals your encryption key.
Werkzeug's Password Hashing Functions
from werkzeug.security import generate_password_hash, check_password_hash
# Hashing a password
hashed = generate_password_hash('mypassword123')
# '$pbkdf2-sha256$29000$...' — a long, unique string every time
# Verifying a password
result = check_password_hash(hashed, 'mypassword123') # True
result = check_password_hash(hashed, 'wrongpassword') # FalseHow generate_password_hash Works
Plain password: 'mypassword123'
│
+ Random salt (unique per hash)
│
+ PBKDF2 algorithm (many iterations)
│
Output: '$pbkdf2-sha256$29000$SALT$HASH'
The salt is a random string added to the password before hashing. It ensures two users with the same password get completely different hashes. This defeats rainbow table attacks (pre-computed hash lookups).
Salting in Action
hash1 = generate_password_hash('password123')
hash2 = generate_password_hash('password123')
print(hash1 == hash2) # False — different salts produce different hashes
print(check_password_hash(hash1, 'password123')) # True
print(check_password_hash(hash2, 'password123')) # TrueChoosing a Hashing Method
Werkzeug defaults to PBKDF2 with SHA-256. You can specify the method:
# PBKDF2 (default, widely supported)
generate_password_hash(password, method='pbkdf2:sha256')
# scrypt (stronger, recommended for new apps)
generate_password_hash(password, method='scrypt')
# Increase iteration count for stronger hashing (slower but safer)
generate_password_hash(password, method='pbkdf2:sha256:600000')More iterations means more CPU time per login — which is intentional. This slows down brute-force attacks without meaningfully affecting your users' login experience.
Integrating Password Hashing into a User Model
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(256), nullable=False)
def set_password(self, plaintext):
self.password_hash = generate_password_hash(plaintext)
def verify_password(self, plaintext):
return check_password_hash(self.password_hash, plaintext)
# Creating a user
user = User(username='alice')
user.set_password('SuperSecret99!')
db.session.add(user)
db.session.commit()
# Verifying at login
user = User.query.filter_by(username='alice').first()
if user and user.verify_password('SuperSecret99!'):
print('Login successful')
else:
print('Invalid credentials')Password Strength Rules
Enforce strong passwords at registration. WTForms validators make this straightforward:
import re
from wtforms import ValidationError
def strong_password(form, field):
password = field.data
if len(password) < 8:
raise ValidationError('Password must be at least 8 characters.')
if not re.search(r'[A-Z]', password):
raise ValidationError('Password must contain an uppercase letter.')
if not re.search(r'\d', password):
raise ValidationError('Password must contain a number.')Password Reset Safety
Never email a user their password — you should not be able to retrieve it. For password resets, generate a time-limited signed token (using itsdangerous), email a reset link containing the token, and allow the user to set a new password only if the token is valid and unexpired.
Summary
Always hash passwords before storing them. Werkzeug's generate_password_hash() adds a unique salt and applies a slow hashing algorithm. check_password_hash() verifies a plain password against the stored hash without reversing it. Use methods like PBKDF2 or scrypt and never store plain-text passwords, never encrypt them, and never try to retrieve them — only verify them.
