""" Password hashing utilities. """ from passlib.context import CryptContext # Configure Argon2 with secure defaults try: from passlib.hash import argon2 as argon2_hasher # memory_cost: 65536 KB (64 MB) # time_cost: 3 # parallelism: 4 argon2_hasher = argon2_hasher.using( memory_cost=65536, time_cost=3, parallelism=4, ) HAS_ARGON2 = True except ImportError: HAS_ARGON2 = False argon2_hasher = None # PBKDF2 fallback pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto") PASSWORD_MIN_LENGTH = 8 def hash_password(password: str) -> str: """Hash a password using Argon2 (preferred) or PBKDF2 (fallback).""" if len(password) < PASSWORD_MIN_LENGTH: raise ValueError(f"Password must be at least {PASSWORD_MIN_LENGTH} characters") if HAS_ARGON2 and argon2_hasher: return argon2_hasher.hash(password) else: return pwd_context.hash(password) def verify_password(plain_password: str, hashed_password: str) -> bool: """Verify a password against its hash.""" if not plain_password or not hashed_password: return False # Try Argon2 first if available if HAS_ARGON2 and argon2_hasher: try: if argon2_hasher.verify(plain_password, hashed_password): return True except Exception: pass # Not an Argon2 hash, try other methods # Try PBKDF2 try: return pwd_context.verify(plain_password, hashed_password) except Exception: return False