35 lines
955 B
Python
35 lines
955 B
Python
|
|
"""Password hashing and session helpers.
|
||
|
|
|
||
|
|
Uses stdlib PBKDF2 so no native build dependencies are required.
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import hashlib
|
||
|
|
import hmac
|
||
|
|
import secrets
|
||
|
|
|
||
|
|
_ALGO = "sha256"
|
||
|
|
_ITERATIONS = 240_000
|
||
|
|
|
||
|
|
|
||
|
|
def hash_password(password: str) -> str:
|
||
|
|
salt = secrets.token_hex(16)
|
||
|
|
digest = hashlib.pbkdf2_hmac(
|
||
|
|
_ALGO, password.encode(), bytes.fromhex(salt), _ITERATIONS
|
||
|
|
).hex()
|
||
|
|
return f"pbkdf2_{_ALGO}${_ITERATIONS}${salt}${digest}"
|
||
|
|
|
||
|
|
|
||
|
|
def verify_password(password: str, stored: str) -> bool:
|
||
|
|
try:
|
||
|
|
scheme, iterations, salt, digest = stored.split("$")
|
||
|
|
if not scheme.startswith("pbkdf2_"):
|
||
|
|
return False
|
||
|
|
algo = scheme.split("_", 1)[1]
|
||
|
|
expected = hashlib.pbkdf2_hmac(
|
||
|
|
algo, password.encode(), bytes.fromhex(salt), int(iterations)
|
||
|
|
).hex()
|
||
|
|
return hmac.compare_digest(expected, digest)
|
||
|
|
except (ValueError, TypeError):
|
||
|
|
return False
|