跳到主要内容
服务器是不是敌人,这个项目当真了

服务器是不是敌人,这个项目当真了

小周
小周

· 阅读约 6 分钟

六月底在 dev.to 的 security 标签里翻到一篇文章,标题起得像简历:「从零用 X3DH、Double Ratchet、Safety Numbers 实现一个 Signal Protocol 消息应用」。

这种我一般直接划走。协议规范全公开,照着抄一遍不算本事。把我拽回去的是评论区,11 条,至少三条在真的抠细节。评论区能吵起来的仓库,通常值得多看两眼。

报幕:ABHIRAM-CREATOR06/Halonyx。一个把"服务器不可信"写进密码学前提、而不是写在隐私政策里的浏览器消息应用。有点像不放心物业的业主,自己把锁换了,钥匙只留在自己兜里。

它自己那句话说得比我有攻击性:TLS 只保护传输那一段,保护不了"服务器发过来的联系人公钥是不是真的"。拆开就是——恶意中继在 X3DH 握手期间拿自己的公钥顶替接收方,解密、重新加密、转发,两边都察觉不到。这话不新,肯为它动架构的项目不多。

四次 DH 各绑一件事:长期身份、临时随机性、历史会话、可否认与重放保护。SK 由四段结果拼起来过一遍 HKDF。服务器手上只有接收方的预密钥包和那个 x3dh_init 包,凑不齐推导 SK 需要的材料。Double Ratchet 那块我不展开,同行都懂:对称棘轮推链密钥、消息密钥用完即弃、方向一变 DH 棘轮重新随机化。写它是因为它没为了好写就砍掉一轮 DH。

写到这我得推翻自己前面那句。"照着抄不难"——说轻了。四次 DH 的顺序、拼进 HKDF 的输入方式,错一点整个握手就废,而且废得悄无声息,测试都测不出来。这活儿抄也是体力活。

真正让这期选手值得占一个名额的,是几处它本来可以糊过去、结果没糊的地方。

一是曲线那笔烂账。Signal 全程 Curve25519,它身份密钥换成 P-256,X3DH 那边又用回 X25519。技术上不干净,这点没得洗。但作者自己在文章里就把这条拎出来写了,评论区那位 Vinicius Pereira 说得更准:这不是随手犯的错,是 WebCrypto 对不可导出密钥的 ECDH 支持在浏览器之间不一致逼出来的取舍,将来要迁 X25519,得靠运行时特性检测加 P-256 回退。曲线不统一是代价,把代价写在明面上是另一回事。

第二处是 Safety Number。双方各自把 SHA256(USID) 和公钥拼起来,两个结果做字典序排序,再哈希一次,出来 12 组每组 5 位、一共 60 位数字。重点在那个"排序":它得保证发起方和接收方零协调、互相不通气,算出来还是同一个数。居然就靠一次字典序排序解决。作者在评论里说这段比他预想难,我信,这种东西写起来全是边角。他们还把上次看到的编号存进 localStorage,编号一变先弹警告再放行。

第三处更偏工程。密钥用 WebCrypto 生成成不可导出的 CryptoKey,原始密钥字节不进 JS 内存,会话状态(根密钥、链密钥、棘轮密钥、计数器)序列化成对象存 IndexedDB,刷新页面接着用。浏览器里做密码学就该是这样。反过来做的人一大把:导出 raw bytes 存着,写起来顺,安全性当场掉一截。

数据库也拆了三个:identity.db 放姓名邮箱和哈希过的 USID,app.db 放联系人信箱,keys.db 放 X3DH 公钥包,三边只靠单向的 SHA-256(USID) 连线。思路对。但别把它当护身符——这只是让"整库被拖走"那一下损失小一点,防不了运行期实时查询,也防不了服务器自己主动关联。架构上排除和运营上承诺是两个量级的东西,它这套只做到前者的七成。

上手门槛给个参考,本地起就三行:

git clone ... && cd halonyx && npm install && npm start

在线那份在 halonyx.onrender.com。仓库里除了代码还塞了 STRIDE 威胁模型(盖了 17 个攻击面)和一份 benchmark 文档,有 X3DH、Double Ratchet、WebSocket、SQLite 的延迟数据。先说清楚:那份 benchmark 我没跑,自己也没做压测,前后拢共摸了半天,下面这些判断都只到"读了一圈"这个深度。

不顺的地方得老实说,而且是结构性的。

一次性预密钥是消耗品,每建一个会话吃掉一个,人长期离线池子就见底。项目加了 /keys/replenish 端点和监控,客户端自动补充还挂在路线图上——也就是说现在这套东西,一个人离线太久,别人可能就建不起会话了。这不是理论问题。

另一个更别扭:文件走 WebTorrent,浏览器里用 WebRTC 数据通道做种,文件不经过服务器,接收方拿 magnet URI 直接从发送方拉,公共 tracker 只管找人。好处实打实。代价是 WebRTC 可能经 STUN 把本地和公网 IP 漏出去。作者把它记进了威胁模型,缓解办法写的是"用 VPN 或者浏览器层面禁用 WebRTC"——说白了等于没解。要 P2P 又要不暴露 IP,这俩本来就打架。

评论区还有条比文章本身值钱的。有人问为什么不上个更简单的密钥交换,作者答 X3DH 适合异步首次接触、第一条消息起就有前向保密;然后他自己补了一句:这套挺适合 agent 之间异步建安全通道,但 agent 没有人类那套"当面念一串数字"的带外比对能力,Safety Number 那环得换成签名身份断言或者注册表。这话从一个刚把 Safety Number 实现完的人嘴里说出来,分量不一样。

— 顺带跑个题,最近刷同题材仓库刷到麻木,一堆 README 比代码长三倍,点进去三个文件。刷着刷着跑偏了,拉回来说正事。

路线图上还挂着后量子(CRYSTALS-Dilithium / SPHINCS+)、多设备会话同步、Sender Keys 群聊、WebRTC 音视频、Web Push 推送。这份清单长得像许愿池,个人项目一个人扛,能落地几条是另一说。候选池里同类方向的东西还有两个,一个走的是完全不同的路子,等它代码再长点下次讲。

能不能打,你自己拉下来跑一圈最准。想试试的把 star 挂上蹲更新就行,别急着往生产上放——OPK 耗尽和 IP 泄露这两条现在都还开着。