"""Argon2id password hashing (TASK-03-01, D-041, REQ-NFR-AUTH-01). Uses argon2-cffi PasswordHasher with defaults that exceed OWASP minimums (time_cost=3, memory_cost=64MiB, parallelism=4 — RESEARCH-v0.4 §2.1). Single operator, low-frequency logins → hashing latency < 1s is acceptable (R-AUTH-02). """ from __future__ import annotations from argon2 import PasswordHasher from argon2.exceptions import VerifyMismatchError _ph = PasswordHasher() def hash_password(plain: str) -> str: """Hash a plaintext password with argon2id. Returns the encoded hash string.""" return _ph.hash(plain) def verify_password(stored_hash: str, plain: str) -> bool: """Verify a plaintext password against a stored argon2id hash. Returns False on mismatch (no exception) so the login flow can apply a uniform 401 + rate-limit-increment path on any auth failure. """ try: _ph.verify(stored_hash, plain) return True except VerifyMismatchError: return False except Exception: return False def needs_rehash(stored_hash: str) -> bool: """True if the stored hash was produced with weaker params than the current PasswordHasher defaults. The login flow rehashes + updates the store when this returns True (param upgrades without forcing a reset).""" return _ph.check_needs_rehash(stored_hash) __all__ = ["hash_password", "verify_password", "needs_rehash"]