"""数据加密与口令散列工具。 - DataEncryptor:AES-256-GCM(依赖 cryptography),密钥由主密钥经 HKDF 派生 - hash_password / verify_password:PBKDF2-HMAC-SHA256(stdlib) """ from __future__ import annotations import base64 import hashlib import hmac import os try: from cryptography.hazmat.primitives.ciphers.aead import AESGCM _HAS_CRYPTO = True except ImportError: # pragma: no cover _HAS_CRYPTO = False def hash_password(password: str, *, iterations: int = 120_000, salt: bytes | None = None) -> str: """PBKDF2-HMAC-SHA256 口令散列,输出 `pbkdf2$iterations$salt_b64$hash_b64`""" salt = salt or os.urandom(16) dk = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, iterations) return "pbkdf2${}${}${}".format( iterations, base64.b64encode(salt).decode(), base64.b64encode(dk).decode()) def verify_password(password: str, stored: str) -> bool: try: scheme, iters, salt_b64, hash_b64 = stored.split("$") if scheme != "pbkdf2": return False dk = hashlib.pbkdf2_hmac("sha256", password.encode(), base64.b64decode(salt_b64), int(iters)) return hmac.compare_digest(dk, base64.b64decode(hash_b64)) except Exception: return False class DataEncryptor: """AES-256-GCM 字段级加密器(用于手机号/身份证等敏感字段落库加密)。""" def __init__(self, master_key: bytes, *, info: bytes = b"wms-field-encryption"): if not _HAS_CRYPTO: raise RuntimeError("需要安装 cryptography 库以使用 AES-256-GCM 加密") if len(master_key) < 16: raise ValueError("master_key 长度至少 16 字节") # HKDF-SHA256 派生 32 字节数据密钥 prk = hmac.new(b"wms-hkdf-salt", master_key, hashlib.sha256).digest() self._key = hmac.new(prk, info + b"\x01", hashlib.sha256).digest() self._aes = AESGCM(self._key) def encrypt(self, plaintext: str) -> str: nonce = os.urandom(12) ct = self._aes.encrypt(nonce, plaintext.encode("utf-8"), None) return base64.b64encode(nonce + ct).decode("ascii") def decrypt(self, token: str) -> str: raw = base64.b64decode(token) nonce, ct = raw[:12], raw[12:] return self._aes.decrypt(nonce, ct, None).decode("utf-8")