Spaces:
Paused
Paused
| """ | |
| 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 | |