"""密码哈希与校验。 当前实现使用 PBKDF2-HMAC-SHA256(内置 hashlib),以避免引入额外依赖。 存储格式: pbkdf2_sha256$$$ """ from __future__ import annotations import base64 import hashlib import hmac import secrets _ALGO = "pbkdf2_sha256" _ITERATIONS = 210_000 _SALT_BYTES = 16 _DKLEN = 32 def hash_password(password: str) -> str: """对明文密码进行哈希并返回可存储字符串。""" if not isinstance(password, str) or not password: raise ValueError("password required") salt = secrets.token_bytes(_SALT_BYTES) dk = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, _ITERATIONS, dklen=_DKLEN) salt_b64 = base64.urlsafe_b64encode(salt).decode("ascii").rstrip("=") dk_b64 = base64.urlsafe_b64encode(dk).decode("ascii").rstrip("=") return f"{_ALGO}${_ITERATIONS}${salt_b64}${dk_b64}" def verify_password(password: str, stored_hash: str) -> bool: """校验明文密码是否匹配已存储的哈希。""" try: algo, iters_s, salt_b64, dk_b64 = stored_hash.split("$", 3) if algo != _ALGO: return False iterations = int(iters_s) salt = _b64url_decode(salt_b64) expected = _b64url_decode(dk_b64) except Exception: return False dk = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iterations, dklen=len(expected)) return hmac.compare_digest(dk, expected) def _b64url_decode(value: str) -> bytes: """解码不带 padding 的 base64url 字符串。""" padded = value + "=" * (-len(value) % 4) return base64.urlsafe_b64decode(padded.encode("ascii"))