import base64 import hashlib import hmac import secrets PBKDF2_ITERATIONS = 260_000 PASSWORD_HASH_PREFIX = "pbkdf2_sha256" def hash_password(password: str) -> str: salt = secrets.token_bytes(16) digest = hashlib.pbkdf2_hmac( "sha256", password.encode("utf-8"), salt, PBKDF2_ITERATIONS) return "$".join( [ PASSWORD_HASH_PREFIX, str(PBKDF2_ITERATIONS), base64.b64encode(salt).decode("ascii"), base64.b64encode(digest).decode("ascii"), ] ) def verify_password(password: str, password_hash: str | None) -> bool: if not password_hash: return False try: algorithm, iterations_raw, salt_raw, digest_raw = password_hash.split("$", 3) if algorithm != PASSWORD_HASH_PREFIX: return False iterations = int(iterations_raw) salt = base64.b64decode(salt_raw.encode("ascii")) expected_digest = base64.b64decode(digest_raw.encode("ascii")) except (ValueError, TypeError): return False actual_digest = hashlib.pbkdf2_hmac( "sha256", password.encode("utf-8"), salt, iterations) return hmac.compare_digest(actual_digest, expected_digest)