Passlib - Password Hash

онлайн тренажер по питону
Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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:

  1. Argon2 — the top choice for new projects
  2. bcrypt — battle‑tested with good performance
  3. PBKDF2 — NIST standard with broad compatibility
  4. 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.

News