Spaces:
Sleeping
Sleeping
File size: 1,566 Bytes
990895d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | """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)
|