Nymir:我给 AI 划下的安全边界

2026-10-01 · 项目故事 · Nymir
P2P端到端加密阅读即焚293 项测试
先说最重要的:这个项目由 AI 辅助开发,作者为零编程基础的个人开发者,代码尚未经过专业安全审计,不建议用于真实敏感场景。

这句话写在 Nymir 仓库最顶上,也在每一次介绍它的最前面。它不是免责声明的客套话——它是这个项目的第一功能。因为一个不会写密码学的人做加密聊天工具,最危险的不是做不出功能,而是做出"看起来安全"的功能。

一条消息的旅程

Nymir 是 P2P 匿名聊天工具,消息直接在两台设备之间传输,不经过我搭建的任何服务器。发送时:明文 → AES-256-GCM 加密 → Ed25519 签名 → 通过 P2P 通道发出。接收时:验签 → 解密 → 字段加密后写入本地 IndexedDB。

加密和签名都在浏览器里完成,中间节点只能看到密文和签名,看不到明文。密钥协商用 X25519,每消息的密钥由 HKDF 单独派生——不同消息的密钥不同。

这里有个关键的诚实点:Nymir 不实现任何密码学原语。它只调用浏览器内置的 WebCrypto API,那是有专业团队维护、经过审计的实现。这是我给自己划的第一条边界——不自己发明加密算法。

三个我主动放弃的东西

1. 放弃"完全去中心化"这个说法

很多隐私工具喜欢说自己"完全去中心化"。Nymir 的存储和传输确实是本地/P2P,但要让两个陌生人找到彼此,仍然需要公共信令(MQTT / Nostr / WebTorrent,三策略冗余)。

所以我把它写成了:无业务服务器 + 本地优先 + P2P 通信。这个词没那么响亮,但它是真的。我不打算用一个听起来更厉害的说法去换信任。

2. 安全码必须是用户自己核对

端到端加密有个绕不开的坑叫 TOFU(首次见面信任):第一次交换公钥时,如果中间人抢先塞给你一个假公钥,协议自己识破不了。

Nymir 的应对是聊天页右上角的盾牌——显示双方共同的 15 位数字 + 7 个 emoji,你可以用另一条渠道(打个电话、发个语音)核对。核对后可以钉住,之后对方公钥再变会自动红色警告。

这个思路不是我发明的,借鉴了 Signal 的 Safety Number 和 Matrix 的 SAS。但有个细节我改了:Matrix 社区已在提案中建议弃用 emoji 验证(不同系统渲染不一致),所以 Nymir 把15 位数字作为唯一主验证手段,emoji 只作视觉辅助。

最关键的是这句话我写进了文档:核对是可选行为,不核对等同于裸奔。我不想让用户以为"开了加密就安全了"。

3. 验签失败要看得见

验签失败的消息,气泡上会明确标示,不静默展示。这是一个很小的交互决定,但逻辑很硬:安全问题上的沉默,等于帮攻击者隐瞒。

还做了什么

  • 阅读即焚 — 被查看后可选择自动销毁(但要说明:防不住对方截图或复制)
  • 本地存储加密 — AES-256-GCM,PBKDF2 60 万次迭代派生密钥
  • 持久化身份 — 刷新页面后加密身份不变,安全码核对长期有效
  • 可验证密钥轮换 — 换钥时用旧签名私钥签新公钥,对端验签通过才接受(ADR-007 有完整记录)
  • 加密备份 — 换设备可恢复,但必须用同一把锁屏密码
  • 二维码离线配对 — v1.6.0 起可以扫码加入房间

293 项测试,React 19 + TypeScript + Vite,AGPL-3.0 协议。CI 里挂着 CodeQL 和 Semgrep 做必检门禁。

做不到什么(这部分请认真读)

  • 高风险通信不要用 — 记者、活动人士、举报人面临国家级对手的场景,请选经过实战检验的专业工具
  • 流量混淆强度有限 — 约每 30 秒、固定约 48 字节的噪声,等间隔等长填充在流量分析中可能可识别,对抗不了认真的分析
  • 无服务端离线收件箱 — 对方不在线,消息就发不出去
  • 心跳消息不加密 — 每 5 秒的 ping/pong 明文走 P2P 通道,不泄露内容但泄露在线状态
  • 忘记锁屏密码 = 数据永久丢失 — 包括备份文件也打不开,这不是吓唬人
  • 锁屏密码至少 10 位 — 太短会被离线暴力破解

为什么还是做了

因为"一个不懂密码学的人能不能做出像样的加密工具"本身就是值得验证的问题。答案是:能做出功能,但做不出信任。

信任需要专业审计、需要时间、需要有人真的拿它去对抗对手——这些我都给不了。我能给的,是把做不到的事情一条一条写在最显眼的地方。

这大概是这个项目最诚实的部分,也是我最看重它的原因。

有新版本可用
在 Safari 里点「分享」→「添加到主屏幕」,可离线阅读本站