from __future__ import annotations import json from typing import Any from cryptography.fernet import Fernet, InvalidToken class SecretVault: def __init__(self, key: str) -> None: try: self._fernet = Fernet(key.encode("ascii")) except (ValueError, TypeError) as exc: raise ValueError("DATAHUB_ENCRYPTION_KEY 格式无效。") from exc @staticmethod def generate_key() -> str: return Fernet.generate_key().decode("ascii") def encrypt_json(self, payload: dict[str, Any]) -> str: raw = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8") return self._fernet.encrypt(raw).decode("ascii") def decrypt_json(self, token: str) -> dict[str, Any]: if not token: return {} try: payload = json.loads(self._fernet.decrypt(token.encode("ascii")).decode("utf-8")) except (InvalidToken, UnicodeDecodeError, json.JSONDecodeError) as exc: raise ValueError("凭据无法解密,请检查 DATAHUB_ENCRYPTION_KEY。") from exc if not isinstance(payload, dict): raise ValueError("凭据格式无效。") return payload def mask_secret(value: str, last_n: int = 4) -> str: text = str(value or "") if not text: return "" if len(text) <= last_n: return "*" * len(text) return ("*" * max(4, len(text) - last_n)) + text[-last_n:]