f1tness_parser/backend/app/core/security.py
2026-10-23 20:30:41 +03:00

39 lines
1.2 KiB
Python

"""Password hashing with scrypt from the standard library (no extra dependencies).
Stored format: scrypt$<n>$<r>$<p>$<salt, base64>$<hash, base64>. The parameters are stored with
the hash, so they can be raised later without invalidating existing passwords.
"""
import base64
import hashlib
import hmac
import secrets
_N, _R, _P = 2**14, 8, 1
_KEY_LENGTH = 32
def _derive(password: str, salt: bytes, n: int, r: int, p: int) -> bytes:
return hashlib.scrypt(
password.encode(), salt=salt, n=n, r=r, p=p, dklen=_KEY_LENGTH,
maxmem=128 * n * r * 2,
)
def hash_password(password: str) -> str:
salt = secrets.token_bytes(16)
key = _derive(password, salt, _N, _R, _P)
encode = lambda value: base64.b64encode(value).decode() # noqa: E731
return f"scrypt${_N}${_R}${_P}${encode(salt)}${encode(key)}"
def verify_password(password: str, stored: str) -> bool:
try:
scheme, n, r, p, salt, key = stored.split("$")
if scheme != "scrypt":
return False
expected = base64.b64decode(key)
actual = _derive(password, base64.b64decode(salt), int(n), int(r), int(p))
except ValueError:
return False
return hmac.compare_digest(actual, expected)