Files
water-management-system/src/security/encryption.py
T

63 lines
2.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""数据加密与口令散列工具。
- 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")