Argon2-cffi-safe hashing

онлайн тренажер по питону
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 argon2-cffi

The argon2-cffi library is the official Python wrapper for the Argon2 password‑hashing algorithm, implemented via the CFFI (C Foreign Function Interface) layer. Argon2 is a modern password‑hashing algorithm that won the Password Hashing Competition in 2015 and has since been regarded as the gold standard for secure password storage.

The main advantage of argon2-cffi is its high performance thanks to a C‑based implementation, while offering a convenient Python API. The library is actively maintained by the community and receives regular updates to stay aligned with the latest security standards.

Installation and Setup

Standard Installation

pip install argon2-cffi

Installation with Extra Dependencies

pip install argon2-cffi[dev]  # development tools
pip install argon2-cffi[tests]  # testing utilities

Verify Installation

import argon2
print(argon2.__version__)

Argon2 Algorithm Variants

Argon2i (Data‑Independent)

Argon2i is optimized to resist side‑channel attacks. It accesses memory in a pattern that does not depend on the password content, making it robust against timing analysis. Use this variant when protection from side‑channel leakage is a priority.

Argon2d (Data‑Dependent)

Argon2d provides maximum resistance to GPU and specialized‑hardware attacks by using data‑dependent memory accesses, which hinder parallelisation. However, it is less resistant to side‑channel attacks.

Argon2id (Hybrid)

Argon2id combines the strengths of both modes. It is the default in argon2-cffi and recommended for most use‑cases. The initial passes behave like Argon2i, followed by Argon2d‑style processing.

Quick Start

Basic Usage

from argon2 import PasswordHasher

# Create a hasher instance
ph = PasswordHasher()

# Hash a password
password = "my_super_secret_password"
hash_result = ph.hash(password)
print(f"Hash: {hash_result}")

# Verify the password
try:
    ph.verify(hash_result, password)
    print("Password is correct!")
except Exception as e:
    print(f"Password is incorrect: {e}")

Working with Different Data Types

from argon2 import PasswordHasher

ph = PasswordHasher()

# Hash a string
string_hash = ph.hash("string_password")

# Hash bytes
bytes_password = b"bytes_password"
bytes_hash = ph.hash(bytes_password)

# Verify
ph.verify(string_hash, "string_password")
ph.verify(bytes_hash, bytes_password)

PasswordHasher Configuration

Constructor Parameters

from argon2 import PasswordHasher, Type

ph = PasswordHasher(
    time_cost=3,        # Number of iterations (default 2)
    memory_cost=65536,  # Memory usage in KB (default 51200)
    parallelism=2,      # Number of parallel threads (default 1)
    hash_len=32,        # Length of the hash in bytes (default 32)
    salt_len=16,        # Length of the salt in bytes (default 16)
    encoding="utf-8",   # Text encoding (default "utf-8")
    type=Type.ID        # Argon2 variant (default Type.ID)
)

Impact of Parameters on Security

time_cost increases execution time linearly; each increment roughly doubles the hashing duration.

memory_cost defines the amount of memory (KB) used. Larger values make attacks with specialized hardware more difficult.

parallelism enables multi‑threaded hashing, which can speed up processing on multi‑core systems.

Settings for Different Scenarios

# Web applications (fast verification)
web_hasher = PasswordHasher(time_cost=2, memory_cost=51200, parallelism=1)

# High‑security data
secure_hasher = PasswordHasher(time_cost=4, memory_cost=102400, parallelism=2)

# Resource‑constrained devices (IoT)
iot_hasher = PasswordHasher(time_cost=1, memory_cost=8192, parallelism=1)

Library Methods and Functions

Core PasswordHasher Methods

Method Description Return Type
hash(password) Creates a hash from the given password str
verify(hash, password) Validates that a password matches a hash bool
check_needs_rehash(hash) Determines whether a stored hash should be updated bool

Low‑Level Functions

Function Description Parameters
hash_secret() Direct hashing of a secret secret, salt, time_cost, memory_cost, parallelism, hash_len, type
verify_secret() Direct verification of a secret hash, secret, type
hash_secret_raw() Hashing that returns raw bytes Same as hash_secret

Constants and Types

Constant / Type Description
Type.I Argon2i variant
Type.D Argon2d variant
Type.ID Argon2id hybrid variant
ARGON2_VERSION Current Argon2 version identifier

Hash Validation and Upgrades

Handling Incorrect Passwords

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError, InvalidHash

ph = PasswordHasher()
hash_result = ph.hash("correct_password")

try:
    ph.verify(hash_result, "wrong_password")
    print("Password is valid")
except VerifyMismatchError:
    print("Invalid password")
except InvalidHash:
    print("Corrupted hash")

Automatic Hash Upgrades

from argon2 import PasswordHasher

old_hasher = PasswordHasher(time_cost=1, memory_cost=8192)
new_hasher = PasswordHasher(time_cost=3, memory_cost=65536)

# Old hash
old_hash = old_hasher.hash("password")

# Check if rehash is needed
if new_hasher.check_needs_rehash(old_hash):
    print("Hash is outdated, rehash required")
    new_hash = new_hasher.hash("password")
    print(f"New hash: {new_hash}")

Password Migration

def migrate_password(old_hash, password, old_hasher, new_hasher):
    """Migrate a password to new hashing parameters."""
    try:
        # Verify old password
        old_hasher.verify(old_hash, password)
        
        # Create new hash
        new_hash = new_hasher.hash(password)
        
        return new_hash
    except Exception as e:
        raise ValueError(f"Password migration failed: {e}")

Low‑Level API

Direct Function Calls

from argon2.low_level import hash_secret, verify_secret, Type

# Low‑level hashing
password = b"secret_password"
salt = b"random_salt_16b"

hash_result = hash_secret(
    secret=password,
    salt=salt,
    time_cost=2,
    memory_cost=65536,
    parallelism=2,
    hash_len=32,
    type=Type.ID
)

# Low‑level verification
is_valid = verify_secret(
    hash=hash_result,
    secret=password,
    type=Type.ID
)

Working with Raw Bytes

from argon2.low_level import hash_secret_raw

# Obtain a raw hash without encoding
raw_hash = hash_secret_raw(
    secret=b"password",
    salt=b"salt_16_bytes",
    time_cost=2,
    memory_cost=65536,
    parallelism=1,
    hash_len=32,
    type=Type.ID
)

print(f"Raw hash: {raw_hash.hex()}")

Exceptions and Error Handling

Exception Hierarchy

Exception Description Base Class
VerificationError Base verification exception Exception
VerifyMismatchError Password does not match the hash VerificationError
InvalidHash Hash format is invalid VerificationError
HashingError Error while creating a hash Exception

Comprehensive Error Handling

from argon2 import PasswordHasher
from argon2.exceptions import (
    VerifyMismatchError, 
    InvalidHash, 
    HashingError,
    VerificationError
)

def safe_password_check(hash_str, password):
    """Safely verify a password while handling all possible exceptions."""
    ph = PasswordHasher()
    
    try:
        ph.verify(hash_str, password)
        return True, "Password is correct"
    except VerifyMismatchError:
        return False, "Invalid password"
    except InvalidHash:
        return False, "Corrupted hash"
    except VerificationError as e:
        return False, f"Verification error: {e}"
    except Exception as e:
        return False, f"Unexpected error: {e}"

Integration with Popular Frameworks

FastAPI

from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

app = FastAPI()
security = HTTPBearer()
ph = PasswordHasher()

class AuthService:
    def __init__(self):
        self.ph = PasswordHasher(time_cost=3, memory_cost=65536)
    
    def hash_password(self, password: str) -> str:
        """Hash a password for storage."""
        return self.ph.hash(password)
    
    def verify_password(self, password: str, hashed: str) -> bool:
        """Validate a password against a stored hash."""
        try:
            self.ph.verify(hashed, password)
            return True
        except VerifyMismatchError:
            return False
    
    def needs_rehash(self, hashed: str) -> bool:
        """Check whether the stored hash should be refreshed."""
        return self.ph.check_needs_rehash(hashed)

auth_service = AuthService()

@app.post("/register")
async def register(username: str, password: str):
    hashed_password = auth_service.hash_password(password)
    # Save to DB
    return {"message": "User registered"}

@app.post("/login")
async def login(username: str, password: str):
    # Retrieve stored hash from DB
    stored_hash = get_user_hash(username)  # Your implementation
    
    if auth_service.verify_password(password, stored_hash):
        # Refresh hash if needed
        if auth_service.needs_rehash(stored_hash):
            new_hash = auth_service.hash_password(password)
            update_user_hash(username, new_hash)  # Your implementation
        
        return {"message": "Login successful"}
    else:
        raise HTTPException(status_code=401, detail="Invalid credentials")

Django

from django.contrib.auth.hashers import BasePasswordHasher
from django.utils.crypto import constant_time_compare
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

class Argon2PasswordHasher(BasePasswordHasher):
    """Custom Argon2 password hasher for Django."""
    
    algorithm = "argon2"
    
    def __init__(self):
        self.ph = PasswordHasher(
            time_cost=2,
            memory_cost=51200,
            parallelism=1
        )
    
    def encode(self, password, salt):
        """Encode a password."""
        hash_result = self.ph.hash(password)
        return f"{self.algorithm}${hash_result}"
    
    def verify(self, password, encoded):
        """Verify a password."""
        algorithm, hash_part = encoded.split('$', 1)
        assert algorithm == self.algorithm
        
        try:
            self.ph.verify(hash_part, password)
            return True
        except VerifyMismatchError:
            return False
    
    def must_update(self, encoded):
        """Determine if the hash needs updating."""
        _, hash_part = encoded.split('$', 1)
        return self.ph.check_needs_rehash(hash_part)
    
    def safe_summary(self, encoded):
        """Provide a safe summary for admin displays."""
        _, hash_part = encoded.split('$', 1)
        return {
            'algorithm': algorithm,
            'hash': hash_part[:6] + '...',
        }

# In settings.py
PASSWORD_HASHERS = [
    'myapp.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
]

Flask

from flask import Flask, request, jsonify
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

app = Flask(__name__)

class PasswordManager:
    def __init__(self):
        self.ph = PasswordHasher(
            time_cost=2,
            memory_cost=51200,
            parallelism=1
        )
    
    def hash_password(self, password):
        """Hash a password."""
        return self.ph.hash(password)
    
    def check_password(self, password, hash_value):
        """Validate a password."""
        try:
            self.ph.verify(hash_value, password)
            return True
        except VerifyMismatchError:
            return False
    
    def update_hash_if_needed(self, hash_value, password):
        """Refresh the hash when parameters have changed."""
        if self.ph.check_needs_rehash(hash_value):
            return self.hash_password(password)
        return hash_value

pwd_manager = PasswordManager()

@app.route('/register', methods=['POST'])
def register():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    hashed_password = pwd_manager.hash_password(password)
    
    # Save to DB
    save_user(username, hashed_password)
    
    return jsonify({'message': 'User registered'})

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    stored_hash = get_user_hash(username)
    
    if pwd_manager.check_password(password, stored_hash):
        # Refresh hash if needed
        updated_hash = pwd_manager.update_hash_if_needed(stored_hash, password)
        if updated_hash != stored_hash:
            update_user_hash(username, updated_hash)
        
        return jsonify({'message': 'Login successful'})
    else:
        return jsonify({'error': 'Invalid credentials'}), 401

Comparison with Other Algorithms

Detailed Comparison

Criterion Argon2 bcrypt PBKDF2 scrypt
Year Introduced 2015 1999 2000 2009
Memory Usage Configurable Fixed Low Configurable
GPU Resistance Excellent Good Poor Good
ASIC Resistance Excellent Medium Poor Good
Parallelism Yes No Yes Partial
Standardisation RFC 9106 OpenBSD RFC 2898 RFC 7914
Speed Fast Medium Fast Medium

Practical Performance Tests

import time
from argon2 import PasswordHasher
import hashlib

def benchmark_argon2():
    ph = PasswordHasher()
    password = "test_password"
    
    start = time.time()
    hash_result = ph.hash(password)
    hash_time = time.time() - start
    
    start = time.time()
    ph.verify(hash_result, password)
    verify_time = time.time() - start
    
    return hash_time, verify_time

def benchmark_pbkdf2():
    password = b"test_password"
    salt = b"random_salt"
    
    start = time.time()
    hash_result = hashlib.pbkdf2_hmac('sha256', password, salt, 100000)
    hash_time = time.time() - start
    
    start = time.time()
    verification = hashlib.pbkdf2_hmac('sha256', password, salt, 100000)
    verify_time = time.time() - start
    
    return hash_time, verify_time

# Run benchmarks
argon2_hash, argon2_verify = benchmark_argon2()
pbkdf2_hash, pbkdf2_verify = benchmark_pbkdf2()

print(f"Argon2 – Hash: {argon2_hash:.4f}s, Verify: {argon2_verify:.4f}s")
print(f"PBKDF2 – Hash: {pbkdf2_hash:.4f}s, Verify: {pbkdf2_verify:.4f}s")

Security Best Practices

Selecting Secure Parameters

from argon2 import PasswordHasher

# Web apps (balance security & performance)
web_hasher = PasswordHasher(
    time_cost=2,        # 2 iterations
    memory_cost=51200,  # 50 MB memory
    parallelism=1,      # 1 thread
    hash_len=32,        # 32‑byte hash
    salt_len=16         # 16‑byte salt
)

# High‑security data
critical_hasher = PasswordHasher(
    time_cost=4,        # 4 iterations
    memory_cost=102400, # 100 MB memory
    parallelism=2,      # 2 threads
    hash_len=64,        # 64‑byte hash
    salt_len=32         # 32‑byte salt
)

# Mobile apps (resource‑constrained)
mobile_hasher = PasswordHasher(
    time_cost=1,        # 1 iteration
    memory_cost=8192,   # 8 MB memory
    parallelism=1,      # 1 thread
    hash_len=32,        # 32‑byte hash
    salt_len=16         # 16‑byte salt
)

Security Recommendations

Never reuse the same salt across different passwords. argon2-cffi automatically generates a unique salt for each hash.

Regularly update hashing parameters as hardware capabilities evolve.

Never transmit hashes to the client—all verification should happen on the server side.

Always use HTTPS when sending passwords over the network.

Log authentication attempts to detect brute‑force or credential‑stuffing attacks.

Defence Against Attacks

import time
from collections import defaultdict
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

class SecurePasswordManager:
    def __init__(self):
        self.ph = PasswordHasher()
        self.failed_attempts = defaultdict(int)
        self.lockout_time = defaultdict(float)
    
    def is_locked_out(self, username):
        """Check if a user is temporarily locked out."""
        if username in self.lockout_time:
            if time.time() - self.lockout_time[username] < 300:  # 5 minutes
                return True
            else:
                del self.lockout_time[username]
                self.failed_attempts[username] = 0
        return False
    
    def verify_password(self, username, password, stored_hash):
        """Secure password verification with brute‑force protection."""
        if self.is_locked_out(username):
            return False, "Account temporarily locked"
        
        try:
            self.ph.verify(stored_hash, password)
            # Reset counter on success
            self.failed_attempts[username] = 0
            return True, "Password is correct"
        except VerifyMismatchError:
            self.failed_attempts[username] += 1
            
            if self.failed_attempts[username] >= 5:
                self.lockout_time[username] = time.time()
                return False, "Too many attempts. Account locked."
            
            return False, "Invalid password"

Practical Use Cases

API Authentication System

import jwt
from datetime import datetime, timedelta
from argon2 import PasswordHasher

class APIAuthSystem:
    def __init__(self, secret_key):
        self.ph = PasswordHasher()
        self.secret_key = secret_key
    
    def register_user(self, username, password, email):
        """Create a new user record."""
        hashed_password = self.ph.hash(password)
        
        user_data = {
            'username': username,
            'password_hash': hashed_password,
            'email': email,
            'created_at': datetime.utcnow().isoformat()
        }
        
        # Persist to DB
        save_user_to_db(user_data)
        return True
    
    def authenticate_user(self, username, password):
        """Authenticate a user and optionally refresh the hash."""
        user_data = get_user_from_db(username)
        if not user_data:
            return None
        
        try:
            self.ph.verify(user_data['password_hash'], password)
            
            # Refresh hash if parameters have changed
            if self.ph.check_needs_rehash(user_data['password_hash']):
                new_hash = self.ph.hash(password)
                update_user_hash(username, new_hash)
            
            return user_data
        except Exception:
            return None
    
    def generate_token(self, user_data):
        """Create a JWT token."""
        payload = {
            'user_id': user_data['id'],
            'username': user_data['username'],
            'exp': datetime.utcnow() + timedelta(hours=24)
        }
        
        token = jwt.encode(payload, self.secret_key, algorithm='HS256')
        return token

Two‑Factor Authentication System

import pyotp
from argon2 import PasswordHasher

class TwoFactorAuthSystem:
    def __init__(self):
        self.ph = PasswordHasher()
    
    def setup_2fa(self, username, password, stored_hash):
        """Configure 2FA for a user."""
        try:
            # Verify password first
            self.ph.verify(stored_hash, password)
            
            # Generate a TOTP secret
            secret = pyotp.random_base32()
            
            # Create QR code URL for Google Authenticator
            totp = pyotp.TOTP(secret)
            qr_url = totp.provisioning_uri(
                username, 
                issuer_name="My App"
            )
            
            return {
                'secret': secret,
                'qr_url': qr_url,
                'backup_codes': self.generate_backup_codes()
            }
        except Exception:
            return None
    
    def verify_2fa(self, username, password, totp_code, stored_hash, totp_secret):
        """Validate password + TOTP code."""
        try:
            # Verify password
            self.ph.verify(stored_hash, password)
            
            # Verify TOTP
            totp = pyotp.TOTP(totp_secret)
            if totp.verify(totp_code, valid_window=1):
                return True
            
            return False
        except Exception:
            return False
    
    def generate_backup_codes(self):
        """Create a set of one‑time backup codes."""
        import secrets
        return [secrets.token_hex(4) for _ in range(10)]

Testing and Debugging

Unit Tests

import unittest
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError, InvalidHash

class TestPasswordHashing(unittest.TestCase):
    def setUp(self):
        self.ph = PasswordHasher()
        self.test_password = "test_password_123"
    
    def test_hash_and_verify(self):
        """Hashing and verification test."""
        hash_result = self.ph.hash(self.test_password)
        self.assertTrue(self.ph.verify(hash_result, self.test_password))
    
    def test_wrong_password(self):
        """Incorrect password test."""
        hash_result = self.ph.hash(self.test_password)
        with self.assertRaises(VerifyMismatchError):
            self.ph.verify(hash_result, "wrong_password")
    
    def test_hash_format(self):
        """Hash format verification."""
        hash_result = self.ph.hash(self.test_password)
        self.assertTrue(hash_result.startswith("$argon2id$"))
    
    def test_different_hashes(self):
        """Ensure different salts produce different hashes."""
        hash1 = self.ph.hash(self.test_password)
        hash2 = self.ph.hash(self.test_password)
        self.assertNotEqual(hash1, hash2)
    
    def test_needs_rehash(self):
        """Check rehash detection."""
        old_hasher = PasswordHasher(time_cost=1)
        new_hasher = PasswordHasher(time_cost=2)
        
        old_hash = old_hasher.hash(self.test_password)
        self.assertTrue(new_hasher.check_needs_rehash(old_hash))
    
    def test_invalid_hash(self):
        """Invalid hash handling."""
        with self.assertRaises(InvalidHash):
            self.ph.verify("invalid_hash", self.test_password)

if __name__ == '__main__':
    unittest.main()

Load Testing

import time
import threading
from concurrent.futures import ThreadPoolExecutor
from argon2 import PasswordHasher

class LoadTester:
    def __init__(self, num_threads=10, num_operations=100):
        self.ph = PasswordHasher()
        self.num_threads = num_threads
        self.num_operations = num_operations
        self.results = []
    
    def hash_operation(self, password):
        """Perform a hash + verify cycle."""
        start_time = time.time()
        hash_result = self.ph.hash(password)
        hash_time = time.time() - start_time
        
        start_time = time.time()
        self.ph.verify(hash_result, password)
        verify_time = time.time() - start_time
        
        return hash_time, verify_time
    
    def run_load_test(self):
        """Execute the load test."""
        passwords = [f"password_{i}" for i in range(self.num_operations)]
        
        with ThreadPoolExecutor(max_workers=self.num_threads) as executor:
            futures = [executor.submit(self.hash_operation, pwd) for pwd in passwords]
            
            for future in futures:
                hash_time, verify_time = future.result()
                self.results.append((hash_time, verify_time))
        
        return self.analyze_results()
    
    def analyze_results(self):
        """Summarise performance metrics."""
        hash_times = [r[0] for r in self.results]
        verify_times = [r[1] for r in self.results]
        
        return {
            'total_operations': len(self.results),
            'avg_hash_time': sum(hash_times) / len(hash_times),
            'avg_verify_time': sum(verify_times) / len(verify_times),
            'max_hash_time': max(hash_times),
            'max_verify_time': max(verify_times),
            'min_hash_time': min(hash_times),
            'min_verify_time': min(verify_times)
        }

# Execute the test
tester = LoadTester(num_threads=5, num_operations=50)
results = tester.run_load_test()
print(f"Load test results: {results}")

Frequently Asked Questions

How do I choose the right parameters for my application?

Parameters depend on your security and performance requirements. For typical web apps, start with time_cost=2 and memory_cost=51200, then adjust to achieve a target hashing duration of 0.5–2 seconds.

Can I change hashing parameters after deployment?

Yes. Use check_needs_rehash() to detect outdated hashes and rehash them on the next successful login.

Is it safe to store hashes in a database?

Absolutely. Argon2 hashes contain the salt and all parameters needed for verification. Just ensure your database is protected against unauthorized access.

How should I handle errors during hashing?

Wrap calls in try/except blocks for specific argon2-cffi exceptions, log the incidents, and avoid exposing technical details to end users.

Does the number of threads affect security?

parallelism influences performance but not the cryptographic strength. More threads can speed up hashing on multi‑core CPUs, at the cost of higher resource consumption.

Can argon2-cffi be used for non‑password data?

Technically possible, but Argon2 is tuned for password hashing. For generic data integrity, consider standard hash functions like SHA‑256.

How do I ensure compatibility across library versions?

Argon2‑cffi follows a stable hash format that is compatible across versions, but it’s best to keep the same library version throughout your stack.

What if hashing is too slow?

Reduce time_cost and/or memory_cost, but remember this lowers security. Find a balance that meets both performance and protection goals for your use case.

Conclusion

The argon2-cffi library provides a reliable, modern solution for password hashing in Python applications. It combines strong security, flexible configuration, and an easy‑to‑use API.

Key benefits include resistance to contemporary attacks, adjustable security parameters, official support, and broad compatibility with popular frameworks. Whether you’re building a small web service or a large enterprise system, argon2-cffi scales to meet your needs.

Proper parameter tuning, adherence to security best practices, and regular hash upgrades will keep user credentials safe for years to come. argon2-cffi is an investment in the long‑term security of your application, paying off by preventing data breaches and authentication attacks.

News