Skip to content

凭据加密体系

本篇讲 Sigil 在"凭据明文进金库"这一刻发生的具体加密步骤,以及它能挡住什么、挡不住什么。

设计目标

  • 凭据明文:永不写文件、永不进日志、UI 不二次展示
  • 凭据密文:分两层落地,单层泄露不致命
  • 凭据主密钥:由 OS 密钥环托管,不在 Sigil 进程长期驻留

双层加密

你输入的明文 Token (ghp_xxxxxxxxxxxxxxxx)

                │ Rust 内存暂存(zeroize::Zeroizing 包装)

        AES-256-GCM 加密

                │ 用一次性 nonce + Sigil 主密钥

        密文 (binary blob)


        ┌─────────────────────────────┐
        │ OS 密钥环                    │
        │ Service: com.agilefr.sigil  │
        │ Account: sigil_cred_<uuid>  │
        │ Secret:  <密文 base64>       │
        └─────────────────────────────┘

SQLite 中只存:
  credential.id = uuid
  credential.ciphertext_ref = "sigil_cred_<uuid>"  ← 不是密文本身,是密钥环的 key
  credential.metadata = {name, type, tags, expires_at, ...}

这意味着

  • 拿走 SQLite 文件 → 拿不到密文(密文不在 SQLite 里)
  • 拿走 OS 密钥环 → 拿不到明文(缺 AES 主密钥)
  • 同时拿走两者 → 还缺 AES 主密钥
  • 拿走 AES 主密钥 + OS 密钥环 + SQLite → 才能解开(这种情形下你的整个 Windows 账号已经沦陷,谈不上 Sigil 防御范围)

OS 密钥环

不同平台对应不同的系统服务:

平台服务凭据可见位置
Windows凭据管理器 (Credential Manager)控制面板 → 凭据管理器 → Windows 凭据
macOSKeychain(规划中)钥匙串访问
LinuxSecret Service (libsecret)(规划中)GNOME Keyring / KWallet

OS 密钥环本身是对用户透明的——你的 Windows 账号登录态决定你能不能取出。这是安全模型的根基:Sigil 的凭据安全 = 你的 Windows 账号安全

AES-256-GCM 选择理由

算法是否选用理由
AES-256-GCM业内标准,硬件加速,认证加密(AEAD),nonce 误用相对友好
ChaCha20-Poly1305备选不依赖 AES-NI,移动端可能用
AES-256-CBC无认证,需要单独 HMAC,容易出错
RSA / ECC非对称算法不适合这个用途

每次加密使用独立 nonce(96-bit 随机),nonce 与密文一起存。即使两条相同的明文也会产生不同密文。

内存安全(zeroize)

Rust 自身不会自动清零内存——drop 只是标记可回收,旧值可能在堆上停留任意长时间。

Sigil 用 zeroize 库的 Zeroizing<T> 包装所有凭据明文:

rust
let plaintext = Zeroizing::new(input.into_bytes());
// ... 使用 plaintext 加密 ...
// plaintext 离开作用域时自动调用 zeroize() 清零内存

效果:

  • 进程被 dump 时,明文已不在堆上
  • 同一内存被后续分配复用时,没有"幽灵明文"残留

不能解决:

  • cold boot 攻击(物理拷走 DIMM)
  • DMA 攻击(火线 / Thunderbolt 直接读内存)
  • 进程被注入并主动读取(这种情形下你已经被入侵)

Bearer Token(MCP 客户端鉴权)

MCP 客户端用的 Bearer Token 是另一套机制:

sk_sigil_<random32bytes>

        │ 显示给用户一次

SHA-256 hash


存入 OS 密钥环(同样是密文)

校验时用 constant-time compare 防时序攻击。Token 本身的明文 Sigil 永远不再持有——存的是 hash。这意味着:

  • 即使 Sigil 进程被全盘 dump,也只能拿到 hash,不能反推 Token
  • 撤销一个 Token = 删除对应的 hash 记录
  • 验证一个 Token = 拿到客户端传来的 → hash → 查表是否存在

备份文件加密

.sigil-backup 备份文件用独立的主密码加密——和 OS 密钥环里的 AES 主密钥完全不同

用户输入主密码 (string)

        │ PBKDF2-HMAC-SHA256, 600k iterations, 32-byte salt

派生密钥 (32 bytes)

        │ + 12-byte 随机 nonce

AES-256-GCM 加密整个备份内容

为什么用独立主密码:

  • 备份文件可能存在云盘、U盘、邮件——脱离 OS 密钥环保护
  • 主密码由用户记忆,不依赖任何机器
  • 忘记主密码 = 备份永远解不开(这是设计)

PBKDF2 600k 迭代符合 OWASP 2023+ 推荐,单次解密在 i5 上约 200-400ms——人能接受,暴力破解的代价被放大几百万倍。

关于"为什么不直接用 X"

为什么不用 Windows DPAPI?

DPAPI 也是个选项。问题:

  • 跨平台迁移困难(macOS / Linux 没有等价)
  • 加密粒度是"用户级"或"机器级",与 OS 密钥环(账号级 + 应用级)不同

OS 密钥环本身在 Windows 上底层用的就是 DPAPI——Sigil 用 keyring crate 时已经获得了 DPAPI 的保护。AES-256-GCM 这一层是额外的加密深度,不是替代。

为什么不用 1Password CLI / Bitwarden CLI 当后端?

它们是好工具,但:

  • 引入额外依赖(用户需要先装 / 登 / 解锁)
  • 它们的设计目标是"个人密码",不是"AI 代理凭据"
  • MCP 协议需要的快速代理调用与它们的 CLI 调用流程不匹配

Sigil 选择直接做底层,把这层用户体验简化掉。

你还能做什么提升安全

Sigil 默认的安全等级已经覆盖了 90% 个人开发者的场景。如果你想更进一步:

措施防的是
启用 BitLocker 全盘加密物理拿走硬盘的攻击
Windows 账号用强密码 + Windows Hello账号劫持
关闭不必要的远程桌面 / SSH远程入侵
不在虚拟机/共享机上录入生产凭据宿主机/管理员窥探
定期轮换 Token(30-90 天)不可知的历史泄漏
给每个客户端独立 Bearer Token单点撤销
为生产凭据使用白名单 Scope Policy写类能力误用

下一步

让 AI 帮你干活,但永远拿不到你的密钥