"""AES-256-GCM 加解密,与 aiclient Tauri 端一致。""" from __future__ import annotations import base64 import json import os from typing import Any from cryptography.hazmat.primitives.ciphers.aead import AESGCM ENVELOPE_VERSION = 1 ENCRYPTED_HEADER = "x-aiclient-encrypted" def parse_aes_key(raw: str) -> bytes | None: """解析 32 字节密钥:标准 Base64 或 64 位十六进制。""" s = raw.strip() if not s: return None try: if len(s) == 64 and all(c in "0123456789abcdefABCDEF" for c in s): key = bytes.fromhex(s) else: key = base64.b64decode(s) if len(key) != 32: return None return key except Exception: return None def encrypt_bytes(plaintext: bytes, key: bytes) -> str: nonce = os.urandom(12) ciphertext = AESGCM(key).encrypt(nonce, plaintext, None) return base64.b64encode(nonce + ciphertext).decode("ascii") def decrypt_payload(b64_payload: str, key: bytes) -> bytes: raw = base64.b64decode(b64_payload) if len(raw) < 13: raise ValueError("密文过短") nonce, ciphertext = raw[:12], raw[12:] return AESGCM(key).decrypt(nonce, ciphertext, None) def wrap_encrypted(plaintext: bytes, key: bytes) -> bytes: payload = encrypt_bytes(plaintext, key) return json.dumps({"v": ENVELOPE_VERSION, "payload": payload}, ensure_ascii=False).encode( "utf-8" ) def unwrap_encrypted(body: bytes, key: bytes) -> bytes: obj: dict[str, Any] = json.loads(body) if obj.get("v") != ENVELOPE_VERSION or "payload" not in obj: raise ValueError("无效的加密信封") payload = obj["payload"] if not isinstance(payload, str): raise ValueError("payload 应为字符串") return decrypt_payload(payload, key) def is_encrypted_envelope(body: bytes) -> bool: try: obj = json.loads(body) return isinstance(obj, dict) and "payload" in obj and obj.get("v") == ENVELOPE_VERSION except Exception: return False