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

PropertyHashingEncryption
Reversible?No (one-way)Yes (with the key)
Use for passwords?YesNo
VerificationHash the input and compareDecrypt 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')   # False

How 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'))  # True

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

Leave a Comment

Your email address will not be published. Required fields are marked *