Skip to content

调研:保险库格式、密钥结构、不用服务器的同步与共享(2026-11-16) ​

读过源码的项目:rbw、kbs2、oak-keyring、Rowel、Secretive、KeePassXC,以及 Bitwarden 桌面版的 Windows Hello 代码。读过的文档:gopass 文档,1Password 和 Bitwarden 2026 年版白皮书。

已有项目的做法 ​

项目许可证要点
rbwMIT解密后的密钥只放在后台进程里:固定的 4 KiB 缓冲区,用 region 锁住内存。socket 放在 0700 目录里,但不检查对端身份。macOS 上用 PT_DENY_ATTACH 防调试并关闭 core dump。默认 3600 秒锁定
kbs2MIT,2026-03 已归档age 密钥用口令包裹;后台进程用 getpeereid 检查调用方用户
gopassMIT每个密钥一个 gpg 或 age 文件,git 同步,多收件人。增删成员要重加密全部文件,但被移除的人仍保有 git 历史。收件人列表的哈希存在 git 之外,发生变化时要用户确认,防止被人偷偷加收件人
KeePassXCGPL,只能参考单个 KDBX4 文件,Argon2,内存 64 MiB。Windows Hello:用 Hello 密钥签一个随机挑战,签名的 SHA-256 作为 AES-GCM 密钥。Touch ID:用 BiometryCurrentSet 保护钥匙串条目。Linux:Polkit 检查加上内存里的密钥
RowelGPL,只能参考每个 lockbox 一个 SQLCipher 库,每条记录再加一层加密。Argon2id 64 MiB / t3 / p4。同步按行取最后写入者,删除标记保留 90 天
oak-keyringMITSQLCipher,每条记录用 XChaCha20 加密并带版本号;冲突由用户选择保留哪边
1Password闭源密码经 PBKDF2 650k 次,再和 Secret Key 合成解锁密钥,登录用 SRP。每个保险库一把密钥,用每个成员的 RSA 公钥分别包裹;条目用 AES-256-GCM
BitwardenGPL主密钥 → 用户对称密钥 → 每条目一把 64 字节密钥;组织密钥用 RSA 包裹
SecretiveMIT安全飞地里的 P-256 密钥,配合生物识别。按 PID 认调用方,PID 被复用时有竞争问题

1. 保险库格式 ​

单个文件(KDBX)每条目一个文件(gopass/age)行级加密的 SQLite
同步冲突任何并发修改都冲突只有改同一条目时才冲突直接同步会损坏,必须导出包
元数据泄露大小和修改时间条目数、大小、修改时间大小

推荐做法:

  • 每台设备只往自己的加密操作日志里追加:vault/<id>/ops/<device>/<seq>,并定期生成快照。
  • 每个文件只有一个写入者,所以 iCloud、Syncthing、Dropbox 都不会产生冲突副本,git 也不会冲突。
  • 文件名用随机 ID,内容按固定大小分档补齐,减少元数据泄露。
  • 本地的 SQLite 只是搜索缓存,随时可以从日志重建。

2. 密钥结构 ​

  • 账户解锁密钥(AUK):Argon2id(主密码) 和 Secret Key 合在一起,经 HKDF 派生。参数用 m=64 MiB、t=3、p=4,写在文件头里。内存控制在 128 MiB 以内,否则以后 iOS 的 AutoFill 扩展可能跑不动。
  • 每个成员:一把 X25519 加密密钥和一把 Ed25519 签名密钥,每台设备再有各自的密钥。所有私钥都用 AUK 包裹。
  • 每个保险库:每个"纪元"(epoch)一把随机 256 位密钥,分别用每个成员的 X25519 公钥包裹(age 收件人或 HPKE)。条目用 XChaCha20-Poly1305 加密,关联数据里绑定 vault ID、item ID 和 epoch。
  • 生物识别解锁(包裹的是 AUK):
    • macOS:安全飞地里的 P-256 密钥,带 privateKeyUsage 和 biometryCurrentSet,用 ECIES 包裹。包裹时不用验证,解开时要 Touch ID。security-framework crate 支持这些操作。
    • Windows:Windows Hello 签挑战,再对签名做 HKDF。
    • Linux:主密码解锁后,密钥留在内存里,取用时过一道 Polkit 检查;TPM2 可选。

3. 不用服务器的共享 ​

  • 成员日志:只追加、哈希链接的签名操作序列,操作包括加人、删人、改角色、换钥。每条操作都必须由当时有效的管理员签名。并发的管理员操作按确定的规则排序。每台设备记住自己见过的最新日志头。
  • 改动的作者认证:每个操作文件都用 Ed25519 签名,读的时候拒绝没有写权限的成员签名的文件。这一步必须有:共享保险库密钥做 AEAD 分辨不出是谁写的,age 本身也不认证发送者。
  • 移除成员:开新的纪元、换新密钥,重加密现有条目,并提示其余成员轮换那个人看得到的密码。没有服务器,就没法撤回别人已经读过的内容。
  • 防回滚:每台设备记住每个写入者的最高序号和对应哈希,日志被截断或倒退时能发现。但别人有意不同步给你的更新发现不了,没有服务器做不到。
  • 公钥真实性:加人时当面核对指纹或扫二维码,类似聊天软件的安全码。1Password 和 Bitwarden 是靠服务器做这件事的。
  • 参考:Ink & Switch 的 Keyhive(Apache-2.0,pre-alpha)。

4. 给 agent 用的后台进程 ​

  • macOS / Linux:socket 放在 0700 目录里,文件权限 0600,并检查 getpeereid 返回的是本用户。
  • macOS 调用方身份:用 LOCAL_PEERTOKEN 拿 audit token,再用 SecCodeCheckValidity 校验 Team ID。不靠 PID。
  • Linux 调用方身份:SO_PEERCRED,内核支持的话再加 SO_PEERPIDFD。
  • Windows:命名管道,DACL 只允许当前用户的 SID,设 first_pipe_instance 和 reject_remote_clients。调用方身份用 GetNamedPipeClientProcessId 加 Authenticode 校验。
  • 签名校验的局限:只能证明调用方是我们自己的 CLI,证明不了是哪个 agent 在调用。所以给 agent 的是 Rowel 式的限定授权:每次要弹窗批准;密钥直接注入到命令或请求里,agent 看不到值;全程记审计日志;保险库一锁,授权全部作废。
  • 内存:用 zeroize / secrecy 加锁定内存;防调试、关 core dump。Windows 上用 VirtualLock 加 DPAPI。
  • 自动锁定:空闲超时、电脑睡眠或锁屏、会话达到最长时长,任一条件触发就锁。

5. 同步方式 ​

方式优点缺点
git有历史,合并干净完整历史都泄露;被移除的成员保有旧数据
iCloud DriveMac 和 iOS 原生支持,Windows 有客户端Linux 没有;文件可能被移出本地、按需下载
Syncthing好用iOS 没有官方客户端
Dropbox能用会生成冲突副本,但我们每个文件只有一个写入者,正好避开

合并规则:按字段取最后写入者,时间戳用混合逻辑时钟(HLC),平局时比设备 ID。并发修改用版本向量检测,被覆盖的版本放进条目历史。不需要完整的 CRDT 库,备注字段以后可以考虑。

6. Rust crate(除注明外都是 MIT/Apache) ​

用途crate
加密age 0.12、chacha20poly1305 0.11、argon2 0.6(2026-08 发了新大版本)、hkdf、hpke、ed25519-dalek、x25519-dalek
内存zeroize、secrecy、region
macOSsecurity-framework 3.7、objc2-local-authentication
Windowswindows(KeyCredentialManager)、tokio 命名管道
存储和其他keyring、rusqlite、pinentry

要你决定的事 ​

  1. Secret Key(双密钥派生)要不要默认开? 推荐开,因为保险库文件会放在 Dropbox 或 git 远端。代价是加新设备时要用应急套件或扫二维码。
  2. Apple Developer ID:macOS 上用 Touch ID 解锁,必须是签名过的 App bundle,并带 keychain-access-groups 权限。
  3. 成员被移除后,他能看到的旧密码算不算泄露? 推荐算,并提示其余成员轮换。

文档对应提交 80b7c2d · 由仓库 docs/ 自动生成