Bcrypt in Python Applications

Guide to using bcrypt in Python with the bcrypt library and passlib. Covers Django and Flask integration, byte string handling, and migrating from hashlib-based password schemes to bcrypt.

Implementation

Detailed Explanation

Bcrypt in Python Applications

Python’s ecosystem offers multiple ways to use bcrypt: the bcrypt library (low-level), passlib (high-level with algorithm agility), and framework-specific integrations in Django and Flask.

The bcrypt Library

pip install bcrypt
import bcrypt

# Hashing
password = b"my_secure_password"
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(password, salt)
# b'$2b$12$...'

# Verification
is_valid = bcrypt.checkpw(password, hashed)
# True

Byte String Requirement

Python’s bcrypt library requires byte strings (bytes), not text strings (str). If your password is a str, encode it first:

password_str = "user_password"
password_bytes = password_str.encode('utf-8')
hashed = bcrypt.hashpw(password_bytes, bcrypt.gensalt(rounds=12))

Forgetting to encode is the most common error when using bcrypt in Python.

Using passlib

passlib provides a higher-level interface with automatic algorithm detection and upgrade support:

from passlib.hash import bcrypt

# Hash
hashed = bcrypt.using(rounds=12).hash("my_password")

# Verify
is_valid = bcrypt.verify("my_password", hashed)

# Check if rehash is needed (cost factor upgrade)
needs_update = bcrypt.needs_update(hashed)

passlib’s needs_update() method makes cost factor upgrades seamless.

Django Integration

Django uses PBKDF2 by default but supports bcrypt through its PASSWORD_HASHERS setting:

# settings.py
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    'django.contrib.auth.hashers.BCryptPasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
]
pip install bcrypt

Django’s BCryptSHA256PasswordHasher pre-hashes the password with SHA-256 before bcrypt, bypassing the 72-byte limit.

Flask Integration

from flask_bcrypt import Bcrypt

app = Flask(__name__)
bcrypt_ext = Bcrypt(app)

# Hash
pw_hash = bcrypt_ext.generate_password_hash('password', rounds=12)

# Verify
is_valid = bcrypt_ext.check_password_hash(pw_hash, 'password')

Migrating from hashlib

If your application currently uses hashlib.sha256() or hashlib.md5() for passwords, migrate with the wrap-and-rehash pattern:

import bcrypt
import hashlib

def verify_and_upgrade(password: str, stored_hash: str, is_legacy: bool):
    if is_legacy:
        # Verify against legacy SHA-256
        old_hash = hashlib.sha256(password.encode()).hexdigest()
        if old_hash == stored_hash:
            # Upgrade to bcrypt
            new_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(12))
            return True, new_hash.decode()
        return False, None
    else:
        valid = bcrypt.checkpw(password.encode(), stored_hash.encode())
        return valid, None

This allows gradual migration without forcing password resets.

Use Case

Python powers many web applications through Django, Flask, and FastAPI, all of which need password hashing. This guide helps Python developers navigate the byte-string pitfall, choose between bcrypt and passlib, and integrate bcrypt into their framework of choice. The migration section is particularly valuable for teams upgrading legacy applications that used hashlib for password storage.

Try It — Bcrypt Generator

Open full tool