"""Password hashing helpers for lab users.""" from __future__ import annotations import base64 import hashlib import hmac import secrets def _b64(value: bytes) -> str: return base64.urlsafe_b64encode(value).rstrip(b"=").decode() def _unb64(value: str) -> bytes: return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4)) def hash_secret(secret: str, *, salt: bytes | None = None) -> str: actual_salt = salt or secrets.token_bytes(16) digest = hashlib.scrypt(secret.encode(), salt=actual_salt, n=2**14, r=8, p=1, dklen=32) return f"scrypt$16384$8$1${_b64(actual_salt)}${_b64(digest)}" def verify_secret(secret: str, encoded: str) -> bool: try: algorithm, n, r, p, salt, expected = encoded.split("$") if algorithm != "scrypt": return False actual = hashlib.scrypt( secret.encode(), salt=_unb64(salt), n=int(n), r=int(r), p=int(p), dklen=32 ) return hmac.compare_digest(actual, _unb64(expected)) except (ValueError, TypeError): return False