Spaces:
Paused
Paused
File size: 1,557 Bytes
91d89f7 | 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 54 55 56 57 58 | """
Password hashing utilities.
"""
from passlib.context import CryptContext
# Configure Argon2 with secure defaults
try:
from passlib.hash import argon2 as argon2_hasher
# memory_cost: 65536 KB (64 MB)
# time_cost: 3
# parallelism: 4
argon2_hasher = argon2_hasher.using(
memory_cost=65536,
time_cost=3,
parallelism=4,
)
HAS_ARGON2 = True
except ImportError:
HAS_ARGON2 = False
argon2_hasher = None
# PBKDF2 fallback
pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto")
PASSWORD_MIN_LENGTH = 8
def hash_password(password: str) -> str:
"""Hash a password using Argon2 (preferred) or PBKDF2 (fallback)."""
if len(password) < PASSWORD_MIN_LENGTH:
raise ValueError(f"Password must be at least {PASSWORD_MIN_LENGTH} characters")
if HAS_ARGON2 and argon2_hasher:
return argon2_hasher.hash(password)
else:
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""Verify a password against its hash."""
if not plain_password or not hashed_password:
return False
# Try Argon2 first if available
if HAS_ARGON2 and argon2_hasher:
try:
if argon2_hasher.verify(plain_password, hashed_password):
return True
except Exception:
pass # Not an Argon2 hash, try other methods
# Try PBKDF2
try:
return pwd_context.verify(plain_password, hashed_password)
except Exception:
return False
|