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