from __future__ import annotations import base64 import hashlib import hmac import os _ALGORITHM = "pbkdf2_sha256" _DEFAULT_ITERATIONS = 260_000 _SALT_BYTES = 16 # A valid, fixed-cost hash used when a login identity has no local password hash. # Its plaintext value is intentionally irrelevant; the hash only keeps failed # login attempts on the same verification path as existing local accounts. DUMMY_PASSWORD_HASH = ( "pbkdf2_sha256$260000$Z292b3BsYW4tZHVtbXktdjE=" # noqa: S105 # nosec B105 - non-account timing equalizer. "$uWgE7ht8wO6cotOqNKK2yNomPt57gstVss5ben5gTbw=" ) def hash_password(password: str, *, iterations: int = _DEFAULT_ITERATIONS) -> str: salt = os.urandom(_SALT_BYTES) digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iterations) return "$".join( [ _ALGORITHM, str(iterations), base64.b64encode(salt).decode("ascii"), base64.b64encode(digest).decode("ascii"), ] ) def verify_password(password: str, encoded: str | None) -> bool: if not encoded: return False try: algorithm, iterations_text, salt_b64, digest_b64 = encoded.split("$", 3) if algorithm != _ALGORITHM: return False iterations = int(iterations_text) salt = base64.b64decode(salt_b64.encode("ascii")) expected = base64.b64decode(digest_b64.encode("ascii")) except Exception: return False actual = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iterations) return hmac.compare_digest(actual, expected)