import base64 import hashlib import hmac import secrets ALGORITHM = "pbkdf2_sha256" ITERATIONS = 390_000 def _b64encode(raw: bytes) -> str: return base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=") def _b64decode(value: str) -> bytes: padding = "=" * (-len(value) % 4) return base64.urlsafe_b64decode(value + padding) def hash_password(password: str, iterations: int = ITERATIONS) -> str: salt = secrets.token_bytes(16) digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iterations) return f"{ALGORITHM}${iterations}${_b64encode(salt)}${_b64encode(digest)}" def verify_password(password: str, stored_hash: str) -> bool: try: algorithm, iterations, salt, expected = stored_hash.split("$", 3) except ValueError: return hmac.compare_digest(password, stored_hash) if algorithm != ALGORITHM: return False digest = hashlib.pbkdf2_hmac( "sha256", password.encode("utf-8"), _b64decode(salt), int(iterations), ) return hmac.compare_digest(_b64encode(digest), expected)