Python Implementation
Python Implementation
Python's ecosystem offers excellent cryptographic libraries with clean APIs for password hashing. The recommended approach uses the argon2-cffi library for Argon2 (the most modern algorithm) with bcrypt as a fallback for compatibility. Python's built-in secrets module provides cryptographically secure random generation for scenarios requiring custom salt handling.
import secrets
import hmac
import json
import base64
from datetime import datetime
from typing import Optional, Tuple, Dict
from argon2 import PasswordHasher, Type
from argon2.exceptions import VerifyMismatchError, InvalidHash
import bcrypt
class SecurePasswordManager:
"""Production-ready password hashing implementation for Python"""
def __init__(self, algorithm='argon2', pepper: Optional[bytes] = None):
self.algorithm = algorithm
self.pepper = pepper
# Configure Argon2 with secure defaults for 2024
self.argon2 = PasswordHasher(
time_cost=3, # iterations
memory_cost=65536, # 64MB
parallelism=4, # threads
hash_len=32, # output length
salt_len=16, # salt length
type=Type.ID # Argon2id variant
)
# Version for migration support
self.version = "v3"
def hash_password(self, password: str) -> str:
"""Hash a password using the configured algorithm"""
if not password:
raise ValueError("Password cannot be empty")
if len(password) > 1024: # Prevent DoS
raise ValueError("Password too long")
# Apply pepper if configured
if self.pepper:
password = self._apply_pepper(password)
try:
if self.algorithm == 'argon2':
hash_value = self.argon2.hash(password)
return f"{self.version}$argon2${hash_value}"
elif self.algorithm == 'bcrypt':
# bcrypt has 72-byte limit, hash longer passwords first
if len(password.encode('utf-8')) > 72:
password = base64.b64encode(
hmac.new(b'bcrypt-key', password.encode(), 'sha256').digest()
).decode('ascii')
salt = bcrypt.gensalt(rounds=13) # 2^13 iterations
hash_value = bcrypt.hashpw(password.encode('utf-8'), salt)
return f"{self.version}$bcrypt${hash_value.decode('ascii')}"
else:
raise ValueError(f"Unknown algorithm: {self.algorithm}")
except Exception as e:
# Log error securely without exposing password
self._log_error(f"Hashing failed: {type(e).__name__}")
raise RuntimeError("Password hashing failed")
def verify_password(self, password: str, stored_hash: str) -> Tuple[bool, Optional[str]]:
"""
Verify password and return (is_valid, new_hash_if_needs_rehash)
"""
if not password or not stored_hash:
return False, None
try:
# Parse stored hash format
parts = stored_hash.split('$', 2)
if len(parts) != 3:
# Legacy format - needs migration
return self._verify_legacy(password, stored_hash)
version, algorithm, hash_value = parts
# Apply pepper if configured
if self.pepper:
password = self._apply_pepper(password)
# Verify based on algorithm
if algorithm == 'argon2':
try:
self.argon2.verify(hash_value, password)
# Check if rehashing needed
if self.argon2.check_needs_rehash(hash_value):
new_hash = self.hash_password(password)
return True, new_hash
return True, None
except VerifyMismatchError:
return False, None
elif algorithm == 'bcrypt':
# Handle bcrypt's 72-byte limit
if len(password.encode('utf-8')) > 72:
password = base64.b64encode(
hmac.new(b'bcrypt-key', password.encode(), 'sha256').digest()
).decode('ascii')
if bcrypt.checkpw(password.encode('utf-8'), hash_value.encode('ascii')):
# Migrate bcrypt to argon2
if self.algorithm == 'argon2':
new_hash = self.hash_password(password)
return True, new_hash
return True, None
return False, None
else:
self._log_error(f"Unknown algorithm in stored hash: {algorithm}")
return False, None
except Exception as e:
self._log_error(f"Verification error: {type(e).__name__}")
return False, None
def _apply_pepper(self, password: str) -> str:
"""Apply pepper using HMAC"""
peppered = hmac.new(
self.pepper,
password.encode('utf-8'),
'sha256'
).digest()
return base64.b64encode(peppered).decode('ascii')
def _verify_legacy(self, password: str, legacy_hash: str) -> Tuple[bool, Optional[str]]:
"""Handle legacy hash formats for migration"""
# Example: Migrate from old bcrypt format
try:
if bcrypt.checkpw(password.encode('utf-8'), legacy_hash.encode('ascii')):
# Generate new hash with current algorithm
new_hash = self.hash_password(password)
return True, new_hash
except:
pass
return False, None
def _log_error(self, message: str):
"""Secure error logging"""
print(f"[SECURITY] {datetime.utcnow().isoformat()} - {message}")
def generate_secure_password(self, length: int = 16) -> str:
"""Generate a cryptographically secure random password"""
# Character set with good entropy and usability
charset = (
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"
"!@#$%^&*"
)
return ''.join(secrets.choice(charset) for _ in range(length))
# Example usage and tests
def demonstrate_python_implementation():
"""Demonstrate the password manager usage"""
# Initialize password manager
pm = SecurePasswordManager(algorithm='argon2')
# Hash some passwords
passwords = [
"SimplePassword123",
"VeryLongPasswordThatExceedsNormalLengthRequirements" * 3,
"Short",
"Unicode password: café été 你好"
]
print("Python Password Hashing Demo\n")
hashed_passwords = {}
for pwd in passwords:
try:
hashed = pm.hash_password(pwd)
hashed_passwords[pwd] = hashed
print(f"Password: {pwd[:20]}...")
print(f"Hash: {hashed[:60]}...")
print()
except ValueError as e:
print(f"Failed to hash '{pwd[:20]}...': {e}\n")
# Verify passwords
print("Verification Tests:")
for pwd, hashed in hashed_passwords.items():
valid, new_hash = pm.verify_password(pwd, hashed)
print(f"'{pwd[:20]}...' : {'Valid' if valid else 'Invalid'}")
if new_hash:
print(f" → Needs rehashing")
# Test wrong password
if hashed_passwords:
first_pwd = list(hashed_passwords.keys())[0]
first_hash = hashed_passwords[first_pwd]
valid, _ = pm.verify_password("wrong password", first_hash)
print(f"\nWrong password: {'Valid' if valid else 'Invalid'}")
# Generate secure password
secure_pwd = pm.generate_secure_password(20)
print(f"\nGenerated secure password: {secure_pwd}")
# Run demonstration
demonstrate_python_implementation()