"""Password hashing for CTXD user accounts (stdlib only).""" from __future__ import annotations import hashlib import secrets PBKDF2_ITERATIONS = 600_000 SCHEME = "pbkdf2_sha256" def hash_password(password: str) -> str: if not password: raise ValueError("password required") salt = secrets.token_hex(16) digest = hashlib.pbkdf2_hmac( "sha256", password.encode("utf-8"), salt.encode("utf-8"), PBKDF2_ITERATIONS, ) return f"{SCHEME}${PBKDF2_ITERATIONS}${salt}${digest.hex()}" def verify_password(password: str, token_hash: str | None) -> bool: if not password or not token_hash: return False try: scheme, iters_s, salt, expected_hex = token_hash.split("$", 3) if scheme != SCHEME: return False digest = hashlib.pbkdf2_hmac( "sha256", password.encode("utf-8"), salt.encode("utf-8"), int(iters_s), ) return secrets.compare_digest(digest.hex(), expected_hex) except (ValueError, TypeError): return False