What Is Passlib
Passlib is a powerful Python library designed specifically for secure password hashing. It provides a high‑level solution that bundles numerous cryptographic algorithms under a single interface. The library is widely used by developers worldwide to build robust authentication systems.
The primary goal of Passlib is to give developers an easy‑to‑use and secure way to work with passwords, hiding the complexities of cryptographic operations behind an intuitive API. The library supports more than 30 different hashing algorithms, ranging from modern standards to legacy formats for compatibility.
Why Passlib Is Essential for Modern Development
Problems with Traditional Approaches
Many developers still rely on outdated password‑hashing methods such as plain SHA‑256 or MD5. These approaches have critical drawbacks:
- Lack of protection against rainbow tables
- Vulnerability to brute‑force attacks
- Inability to adapt to increasing computational power
- No mitigation of timing attacks
Benefits of a Modern Approach
Passlib addresses these issues by providing:
- Automatic salting for each password
- Configurable iteration counts to slow down attacks
- Constant‑time execution to defend against timing attacks
- Seamless migration between algorithms without data loss
Installation and Setup
Basic Installation
pip install passlib
Installation with Additional Algorithm Support
To gain access to modern hashing algorithms, it is recommended to install extra dependencies:
pip install passlib[bcrypt,argon2]
Verify Installation
After installation you can list the available algorithms:
from passlib import registry
print(registry.list_crypt_handlers())
Architecture and Core Components
Library Structure
Passlib is built on a modular architecture that includes:
- Hash Handlers — implementations of specific algorithms
- CryptContext — centralized configuration management
- Registry — registration and lookup system for handlers
- Utilities — helper functions for safe operations
How It Works
The library follows a “secure‑by‑default” principle, automatically applying best‑practice cryptography. Each algorithm is encapsulated in its own handler with a unified interface.
Quick Start
Simple Hashing
from passlib.hash import pbkdf2_sha256
# Create a hash
password = "my_secure_password"
hash_value = pbkdf2_sha256.hash(password)
print(f"Hash: {hash_value}")
# Verify the password
is_valid = pbkdf2_sha256.verify(password, hash_value)
print(f"Password is valid: {is_valid}")
Using CryptContext
from passlib.context import CryptContext
# Create a context with multiple schemes
pwd_context = CryptContext(
schemes=["bcrypt", "pbkdf2_sha256"],
default="bcrypt",
deprecated="auto"
)
# Hash and verify
hash_value = pwd_context.hash("user_password")
is_valid = pwd_context.verify("user_password", hash_value)
Supported Hashing Algorithms
Modern Recommended Algorithms
Argon2
The winner of the 2015 Password Hashing Competition. Provides strong resistance to GPU and ASIC attacks thanks to high memory consumption.
from passlib.hash import argon2
# Basic usage
hash_value = argon2.hash("password")
# Custom parameters
hash_value = argon2.using(
time_cost=2, # Number of iterations
memory_cost=102400, # Memory in KB
parallelism=8 # Number of threads
).hash("password")
bcrypt
A time‑tested algorithm based on the Blowfish cipher. Widely supported and well optimised.
from passlib.hash import bcrypt
# Standard usage
hash_value = bcrypt.hash("password")
# Adjust work factor
hash_value = bcrypt.using(rounds=12).hash("password")
PBKDF2
The NIST‑approved standard that uses HMAC to derive keys.
from passlib.hash import pbkdf2_sha256
# Basic hashing
hash_value = pbkdf2_sha256.hash("password")
# Increase iteration count
hash_value = pbkdf2_sha256.using(rounds=200000).hash("password")
Compatibility Algorithms
SHA‑256 Crypt and SHA‑512 Crypt
Used on Unix systems for storing passwords in /etc/shadow.
from passlib.hash import sha256_crypt, sha512_crypt
sha256_hash = sha256_crypt.hash("password")
sha512_hash = sha512_crypt.hash("password")
Legacy Algorithms
Supported for migration of existing systems:
- md5_crypt
- des_crypt
- mysql41 (for older MySQL versions)
- ldap_md5, ldap_sha1 (for LDAP systems)
Working with CryptContext
Creating and Configuring a Context
CryptContext is the central component of Passlib, providing a single interface for multiple algorithms:
from passlib.context import CryptContext
# Context with prioritized schemes
pwd_context = CryptContext(
schemes=["argon2", "bcrypt", "pbkdf2_sha256"],
default="argon2",
deprecated="auto",
# Argon2 settings
argon2__min_rounds=1,
argon2__max_rounds=4,
argon2__default_rounds=2,
# bcrypt settings
bcrypt__min_rounds=10,
bcrypt__max_rounds=15,
bcrypt__default_rounds=12
)
Hash Updates
Passlib can automatically re‑hash passwords on user login:
def authenticate_user(username, password, stored_hash):
# Verify password
if not pwd_context.verify(password, stored_hash):
return False
# Check if re‑hash is needed
if pwd_context.needs_update(stored_hash):
new_hash = pwd_context.hash(password)
# Save the new hash to the database
update_user_hash(username, new_hash)
return True
Serialisation and Loading Configuration
# Save configuration to a string
config_string = pwd_context.to_string()
# Load from a string
new_context = CryptContext.from_string(config_string)
# Write to a file
with open("password_config.ini", "w") as f:
f.write(config_string)
# Load from a file
loaded_context = CryptContext.from_path("password_config.ini")
Advanced Features
Using a Pepper
A pepper is an additional secret stored separately from password hashes:
import os
from passlib.context import CryptContext
PEPPER = os.environ.get("PASSWORD_PEPPER", "default_pepper_value")
def hash_password_with_pepper(password):
return pwd_context.hash(password + PEPPER)
def verify_password_with_pepper(password, hash_value):
return pwd_context.verify(password + PEPPER, hash_value)
Custom Hash Algorithms
from passlib.utils.handlers import HasManyIdents
from passlib import registry
class CustomHash(HasManyIdents):
name = "custom_hash"
# Implementation of a custom algorithm
# Register the handler
registry.register_crypt_handler(CustomHash)
Database Integration
class UserManager:
def __init__(self):
self.pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def create_user(self, username, password):
hash_value = self.pwd_context.hash(password)
# Persist to the database
save_user(username, hash_value)
def authenticate(self, username, password):
stored_hash = get_user_hash(username)
if not stored_hash:
return False
return self.pwd_context.verify(password, stored_hash)
Passlib Methods and Functions Table
Core CryptContext Methods
| Method | Description | Example |
|---|---|---|
hash(password) |
Creates a password hash using the selected scheme | context.hash("password123") |
verify(password, hash) |
Validates a password against a hash | context.verify("password123", hash) |
needs_update(hash) |
Determines whether a hash should be re‑hashed | context.needs_update(old_hash) |
identify(hash) |
Detects the algorithm used to create a hash | context.identify(hash_string) |
update(**kwds) |
Creates a new context with updated parameters | context.update(default="argon2") |
to_string() |
Serialises the configuration to a string | config = context.to_string() |
from_string(config) |
Builds a context from a configuration string | CryptContext.from_string(config) |
from_path(path) |
Loads a context from a file | CryptContext.from_path("config.ini") |
Algorithm Handler Methods
| Method | Description | Example |
|---|---|---|
hash(password) |
Creates a hash for a specific algorithm | bcrypt.hash("password") |
verify(password, hash) |
Checks a password against a hash | bcrypt.verify("password", hash) |
using(**kwds) |
Returns a variant with customised parameters | bcrypt.using(rounds=15) |
identify(hash) |
Tests if a hash matches the algorithm | bcrypt.identify(hash_string) |
genconfig(**kwds) |
Generates a configuration for the algorithm | bcrypt.genconfig(rounds=12) |
genhash(password, config) |
Creates a hash using a supplied configuration | bcrypt.genhash(pwd, config) |
Utility Functions
| Function | Description | Example |
|---|---|---|
passlib.utils.safe_str_cmp(a, b) |
Constant‑time string comparison (protects against timing attacks) | safe_str_cmp(hash1, hash2) |
passlib.registry.register_crypt_handler(handler) |
Registers a custom handler | register_crypt_handler(MyHash) |
passlib.registry.list_crypt_handlers() |
Returns a list of available algorithms | handlers = list_crypt_handlers() |
passlib.context.lazy_crypt_context(**kwds) |
Creates a lazy context (initialises on first use) | lazy_context = lazy_crypt_context() |
Popular Algorithm Parameters
| Algorithm | Main Parameters | Description |
|---|---|---|
| bcrypt | rounds (4‑31) |
Work factor, default is 12 |
| argon2 | time_cost, memory_cost, parallelism |
Time, memory (KB), threads |
| pbkdf2_sha256 | rounds, salt_size |
Iterations (default 29 000), salt size |
| scrypt | rounds, block_size, parallelism |
N, r, p parameters of the algorithm |
| sha256_crypt | rounds, salt_size |
Iterations (5 000‑999 999 999), salt size |
Integration with Popular Frameworks
Flask and Flask‑Security
from flask import Flask
from flask_security import Security, SQLAlchemyUserDatastore
from passlib.context import CryptContext
app = Flask(__name__)
# Configure Passlib for Flask‑Security
app.config['SECURITY_PASSWORD_HASH'] = 'bcrypt'
app.config['SECURITY_PASSWORD_SALT'] = 'your-secret-salt'
# Create a custom context
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
class CustomPasswordUtil:
def hash_password(self, password):
return pwd_context.hash(password)
def verify_password(self, password, hash_value):
return pwd_context.verify(password, hash_value)
FastAPI
from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import OAuth2PasswordBearer
from passlib.context import CryptContext
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
@app.post("/register")
async def register_user(username: str, password: str):
hashed_password = get_password_hash(password)
# Persist user to the database
return {"message": "User registered successfully"}
@app.post("/token")
async def login_for_access_token(username: str, password: str):
user = authenticate_user(username, password)
if not user:
raise HTTPException(status_code=401, detail="Invalid credentials")
# Create JWT token
return {"access_token": token, "token_type": "bearer"}
Django
from django.contrib.auth.hashers import BasePasswordHasher
from passlib.context import CryptContext
class PasslibPasswordHasher(BasePasswordHasher):
algorithm = "passlib_bcrypt"
def __init__(self):
self.pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def encode(self, password, salt):
return self.pwd_context.hash(password)
def verify(self, password, encoded):
return self.pwd_context.verify(password, encoded)
def safe_summary(self, encoded):
return {
'algorithm': self.algorithm,
'hash': encoded[:6] + '...',
}
# In settings.py
PASSWORD_HASHERS = [
'myapp.hashers.PasslibPasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
]
Security Best Practices
Choosing an Algorithm
When selecting a hashing algorithm, consider:
- Argon2 — the top choice for new projects
- bcrypt — battle‑tested with good performance
- PBKDF2 — NIST standard with broad compatibility
- scrypt — strong protection against hardware attacks
Parameter Tuning
# Recommended production settings
production_context = CryptContext(
schemes=["argon2", "bcrypt"],
default="argon2",
deprecated="auto",
# Argon2 settings
argon2__memory_cost=102400, # 100 MB
argon2__time_cost=2,
argon2__parallelism=8,
# bcrypt settings
bcrypt__rounds=12,
# Global settings
all__vary_rounds=0.1, # Vary rounds by ±10%
)
Migrating Between Algorithms
def migrate_password_hashes():
# Context for migration
migration_context = CryptContext(
schemes=["argon2", "bcrypt", "pbkdf2_sha256", "md5_crypt"],
default="argon2",
deprecated=["md5_crypt", "pbkdf2_sha256"]
)
users = get_all_users()
for user in users:
if migration_context.needs_update(user.password_hash):
# Password will be re‑hashed on next login
user.needs_password_update = True
user.save()
Performance and Optimisation
Benchmarking
import time
from passlib.hash import bcrypt, argon2, pbkdf2_sha256
def benchmark_algorithm(hash_func, password, iterations=100):
start_time = time.time()
for _ in range(iterations):
hash_value = hash_func.hash(password)
hash_func.verify(password, hash_value)
end_time = time.time()
avg_time = (end_time - start_time) / iterations
return avg_time
# Test various algorithms
password = "test_password_123"
algorithms = [
("bcrypt (rounds=12)", bcrypt.using(rounds=12)),
("argon2 (default)", argon2),
("pbkdf2_sha256", pbkdf2_sha256)
]
for name, algorithm in algorithms:
avg_time = benchmark_algorithm(algorithm, password)
print(f"{name}: {avg_time:.4f} seconds per operation")
High‑Load Tuning
# Context for high‑throughput systems
high_load_context = CryptContext(
schemes=["bcrypt"],
default="bcrypt",
bcrypt__rounds=10, # Lowered for speed
bcrypt__vary_rounds=0.05 # Small variation
)
# Asynchronous hashing
import asyncio
from concurrent.futures import ThreadPoolExecutor
async def async_hash_password(password):
loop = asyncio.get_event_loop()
with ThreadPoolExecutor() as executor:
return await loop.run_in_executor(
executor, high_load_context.hash, password
)
Error Handling and Debugging
Common Issues and Solutions
Missing Dependency Error
try:
from passlib.hash import bcrypt
bcrypt.hash("test")
except ImportError:
print("Install bcrypt support: pip install passlib[bcrypt]")
Invalid Hash Format
def safe_verify_password(password, hash_value):
try:
return pwd_context.verify(password, hash_value)
except ValueError as e:
print(f"Invalid hash format: {e}")
return False
except Exception as e:
print(f"Password verification error: {e}")
return False
Logging for Debugging
import logging
from passlib.context import CryptContext
# Configure logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
class DebuggableCryptContext:
def __init__(self, **kwds):
self.context = CryptContext(**kwds)
def hash(self, password):
logger.debug(f"Hashing password of length {len(password)}")
hash_value = self.context.hash(password)
logger.debug(f"Generated hash: {hash_value[:20]}...")
return hash_value
def verify(self, password, hash_value):
logger.debug(f"Verifying password against hash {hash_value[:20]}...")
result = self.context.verify(password, hash_value)
logger.debug(f"Verification result: {result}")
return result
Practical Usage Examples
User Registration and Authentication System
class AuthenticationSystem:
def __init__(self):
self.pwd_context = CryptContext(
schemes=["argon2", "bcrypt"],
default="argon2",
deprecated="auto"
)
self.users = {} # In a real app this would be a database
def register_user(self, username, password, email):
if username in self.users:
raise ValueError("User already exists")
# Password validation
if not self._validate_password(password):
raise ValueError("Password does not meet requirements")
# Create hash
password_hash = self.pwd_context.hash(password)
# Store user
self.users[username] = {
'email': email,
'password_hash': password_hash,
'created_at': time.time()
}
return True
def authenticate_user(self, username, password):
user = self.users.get(username)
if not user:
return False
# Verify password
if not self.pwd_context.verify(password, user['password_hash']):
return False
# Update hash if needed
if self.pwd_context.needs_update(user['password_hash']):
user['password_hash'] = self.pwd_context.hash(password)
return True
def _validate_password(self, password):
# Basic password policy
if len(password) < 8:
return False
if not any(c.isupper() for c in password):
return False
if not any(c.isdigit() for c in password):
return False
return True
Password Reset System
import secrets
import time
from datetime import datetime, timedelta
class PasswordResetSystem:
def __init__(self, auth_system):
self.auth_system = auth_system
self.reset_tokens = {}
self.token_lifetime = 3600 # 1 hour
def generate_reset_token(self, username):
if username not in self.auth_system.users:
raise ValueError("User not found")
# Generate a secure token
token = secrets.token_urlsafe(32)
# Store token data
self.reset_tokens[token] = {
'username': username,
'expires_at': time.time() + self.token_lifetime
}
return token
def reset_password(self, token, new_password):
# Validate token
if token not in self.reset_tokens:
raise ValueError("Invalid token")
token_data = self.reset_tokens[token]
# Check expiration
if time.time() > token_data['expires_at']:
del self.reset_tokens[token]
raise ValueError("Token has expired")
# Reset password
username = token_data['username']
new_hash = self.auth_system.pwd_context.hash(new_password)
self.auth_system.users[username]['password_hash'] = new_hash
# Remove used token
del self.reset_tokens[token]
return True
Frequently Asked Questions
Which algorithm should I pick for a new project?
For new projects we recommend Argon2, as it is the modern standard selected by the Password Hashing Competition. It offers the strongest protection against contemporary attack vectors.
How do I migrate from MD5 to a secure algorithm?
Create a context that supports both the old and the new algorithms:
migration_context = CryptContext(
schemes=["argon2", "md5"],
default="argon2",
deprecated=["md5"]
)
When verifying a password, Passlib will automatically recognise the legacy format and suggest an upgrade.
Can Passlib be used in multi‑threaded applications?
Yes, Passlib is fully thread‑safe. CryptContext instances can be shared across threads without issues.
How should I tune performance for high‑traffic systems?
Lower the work factor of the chosen algorithm and employ asynchronous processing:
fast_context = CryptContext(
schemes=["bcrypt"],
bcrypt__rounds=10 # Reduced from the default 12
)
Is it safe to keep Passlib configuration in source code?
Algorithm configuration can live in code, but secrets such as peppers and salts should be stored in environment variables or protected configuration files.
How can I check which algorithm created a given hash?
Use the identify() method:
algorithm = pwd_context.identify(hash_value)
print(f"Hash was created with: {algorithm}")
What if I forget the parameters used to generate a hash?
Passlib extracts all required parameters directly from the hash string, so you never need to remember the original settings.
Can I supply my own salt?
While technically possible, we strongly recommend letting Passlib generate salts automatically. This guarantees uniqueness and cryptographic strength.
Conclusion
Passlib is a comprehensive solution for secure password management in Python applications. The library effectively solves the core challenges of modern password cryptography: it defends against a wide range of attacks, offers flexible configuration, and simplifies migration between algorithms.
Key benefits of using Passlib include a unified API for multiple algorithms, automatic application of security best practices, easy integration with popular web frameworks, and painless migration of legacy systems.
Backed by an active community, regular updates, and adherence to current security standards, Passlib remains the de‑facto standard for password hashing in the Python ecosystem. Proper use of the library dramatically raises application security and protects user data from modern threats.
The Future of AI in Mathematics and Everyday Life: How Intelligent Agents Are Already Changing the Game
Experts warned about the risks of fake charity with AI
In Russia, universal AI-agent for robots and industrial processes was developed