Fastwhisper / app /security_passwords.py
Mbonea's picture
Deploy Habit Journal backend S0-S10 to Hugging Face Space.
990895d
Raw
History Blame Contribute Delete
1.57 kB
"""Hash and verify the single application password.
PBKDF2, random salts, and constant-time comparison are provided by stdlib.
"""
import base64
import hashlib
import hmac
import os
ALGORITHM = "pbkdf2_sha256"
DEFAULT_ITERATIONS = 260_000
MIN_ITERATIONS = 200_000
def hash_password(password: str, iterations: int = DEFAULT_ITERATIONS) -> str:
"""Return a salted PBKDF2 password hash in the configured wire format."""
if iterations < MIN_ITERATIONS:
raise ValueError(f"iterations must be at least {MIN_ITERATIONS}")
salt = os.urandom(16)
digest = hashlib.pbkdf2_hmac(
"sha256",
password.encode("utf-8"),
salt,
iterations,
)
salt_b64 = base64.b64encode(salt).decode("ascii")
digest_b64 = base64.b64encode(digest).decode("ascii")
return f"{ALGORITHM}${iterations}${salt_b64}${digest_b64}"
def verify_password(password: str, encoded: str) -> bool:
"""Verify a password without leaking comparison timing."""
try:
algorithm, raw_iterations, salt_b64, expected_b64 = encoded.split("$", 3)
iterations = int(raw_iterations)
if algorithm != ALGORITHM or iterations < MIN_ITERATIONS:
return False
salt = base64.b64decode(salt_b64, validate=True)
expected = base64.b64decode(expected_b64, validate=True)
except (ValueError, TypeError):
return False
actual = hashlib.pbkdf2_hmac(
"sha256",
password.encode("utf-8"),
salt,
iterations,
)
return hmac.compare_digest(actual, expected)