| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 |
- """Web API 请求/响应加解密工具。"""
- from __future__ import annotations
- import base64
- import hashlib
- import json
- import os
- import time
- from typing import Any, Dict
- from cryptography.hazmat.primitives.ciphers.aead import AESGCM
- class ApiCryptoError(Exception):
- """API 加解密错误。"""
- def derive_key(fixed_secret: str, user_id: str, ts: int, nonce: str) -> bytes:
- seed = f"{fixed_secret}:{user_id}:{ts}:{nonce}"
- return hashlib.sha256(seed.encode("utf-8")).digest()
- def _b64url_encode(data: bytes) -> str:
- return base64.urlsafe_b64encode(data).decode("utf-8").rstrip("=")
- def _b64url_decode(data: str) -> bytes:
- pad = "=" * (-len(data) % 4)
- return base64.urlsafe_b64decode((data + pad).encode("utf-8"))
- def encrypt_json(payload: Dict[str, Any], key: bytes) -> str:
- iv = os.urandom(12)
- aes = AESGCM(key)
- raw = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
- cipher = aes.encrypt(iv, raw, None)
- return _b64url_encode(iv + cipher)
- def decrypt_json(cipher_text: str, key: bytes) -> Dict[str, Any]:
- try:
- data = _b64url_decode(cipher_text)
- if len(data) < 13:
- raise ApiCryptoError("密文长度非法")
- iv = data[:12]
- cipher = data[12:]
- aes = AESGCM(key)
- plain = aes.decrypt(iv, cipher, None)
- obj = json.loads(plain.decode("utf-8"))
- if not isinstance(obj, dict):
- raise ApiCryptoError("解密后报文格式非法")
- return obj
- except ApiCryptoError:
- raise
- except Exception as exc:
- raise ApiCryptoError("密文解析失败") from exc
- def gen_nonce_hex() -> str:
- return os.urandom(12).hex()
- def is_ts_in_window(ts: int, skew_sec: int) -> bool:
- now_ms = int(time.time() * 1000)
- return abs(now_ms - int(ts)) <= max(30, int(skew_sec)) * 1000
|