"""外部系统凭据列加密。 与早期微信绑定不同,这里没有明文降级:未配置 master key 时拒绝创建和调用。 """ from __future__ import annotations import base64 import hashlib import os from cryptography.fernet import Fernet, InvalidToken _PREFIX = "v1:" _ENV = "ZCBOT_CREDENTIAL_MASTER_KEY" def configured() -> bool: return len(os.getenv(_ENV, "").strip()) >= 32 def _fernet() -> Fernet: raw = os.getenv(_ENV, "").strip() if not raw: raise RuntimeError(f"{_ENV} 未配置,不能保存或使用外部系统凭据") if len(raw) < 32: raise RuntimeError(f"{_ENV} 至少需要 32 个字符") digest = hashlib.sha256(raw.encode("utf-8")).digest() return Fernet(base64.urlsafe_b64encode(digest)) def encrypt_secret(value: str) -> str: if not isinstance(value, str) or not value: raise ValueError("credential value must be a non-empty string") return _PREFIX + _fernet().encrypt(value.encode("utf-8")).decode("ascii") def decrypt_secret(value: str) -> str: if not isinstance(value, str) or not value.startswith(_PREFIX): raise RuntimeError("外部系统凭据格式无效") try: return _fernet().decrypt(value[len(_PREFIX):].encode("ascii")).decode("utf-8") except InvalidToken as exc: raise RuntimeError("外部系统凭据无法解密,master key 可能已变化") from exc def mask_username(username: str) -> str: username = (username or "").strip() if not username: return "***" if len(username) <= 2: return username[0] + "*" return username[:2] + "***" + username[-1:]