新闻 · Blackbear 的安全
我们读不到的东西
Blackbear 是本地优先的。每一项功能都在你的设备上运行,默认情况下,你的数据只存在那里,全程没有服务器参与。
当你开启云同步后,我们的服务器开始在你的设备之间、在你和你的人之间传递数据。这个系列就是要说清楚这对你的隐私意味着什么。第 1 部分讲加密:什么会离开你的设备,以及我们为什么读不了。第 2 部分是一份坦诚的清单,列出我们的服务器确实保存的元数据。第 3 部分讲分享与协作。
下面的一切描述的都是已经上线的代码,而不是打算。凡是我们点名的算法或数字,就是今天生产环境中正在运行的那一个。
加密在你的设备上完成
你同步的每一处改动,在接触网络之前就已加密。笔记、文档、任务、事件、日记、表格、画布、音频、版本历史,每一项都用 XChaCha20-Poly1305 密封,这是一种认证加密算法,所用的密钥绝不会在未受保护的状态下离开你的设备。我们的服务器收到的是密文,存储的也是密文。
图片和音频文件也一样:在你的设备上加密,再以不透明的数据块上传。
负责这一切的代码是同一份 Rust 实现,基于 RustCrypto 生态,编译为 WebAssembly。App 和 Blackbear CLI 共用它,一字节不差;我们还用跨语言的测试样例来验证它,确保不会有第二份实现悄悄偏离。
它能防什么,不能防什么
加密是一件具体的工具,不是防护罩。在讲细节之前,先说边界。
这个设计能保护你免受:
- 我们的服务器或数据库遭到被动入侵,存储的数据被窃。泄露出去的只有密文和被包裹的密钥。
- 我们自己。我们上线的生产系统从不接触你的内容密钥或明文,所以就算有人依法要求调取你的内容,得到的也只是我们实际持有的东西:元数据(第 2 部分)和密封的数据块。
它无法保护你免受:
- 被入侵、被偷或处于解锁状态的设备。你的设备保管着你的密钥;它的安全是其他一切的地基。
- 任何知道你密码短语或十二个恢复词的人。
- 获得授权的接收者如何处理你分享的内容。分享靠的是对人的信任,而不只是数学。
- 通信元数据:谁、什么时候、多少。这正是第 2 部分的全部主题。
- App 本身。在网页上,每次加载时 App 都从我们的服务器下发;在桌面端,则通过签名的版本发布。不管是谁发布的,没有任何加密方案能抵挡一个恶意 App。
- 你主动选择以明文发出数据的时刻:让 Pax 使用云端模型、连接外部日历、导出文件、用邮件发送邀请。这些数据是你刻意发给你选定的一方的,受对方的政策约束,而不是我们的加密。
密钥的层级
你的密码短语从不离开你的设备。一切都由它而来:
- 你的密码短语会经过 Argon2id 处理。这是一种内存密集型的密钥派生函数,参数设为 64 MiB 内存、三轮迭代、四条并行通道。得到的就是你的账户密钥。
- 账户密钥包裹着你的保险库密钥:一把在你设备上生成的随机 256 位密钥。真正加密你数据的,是保险库密钥。
- 我们的服务器存储被包裹的保险库密钥、盐值和参数。被包裹的密钥本身就是密文。没有你的密码短语,它只是一堆噪声。
服务器只存储被包裹的副本:用密文保护密文。
为什么要绕这一层?这样你修改密码短语时,不必把整个人生重新加密一遍;恢复机制也能存在,而不必削弱前门。
有一句提醒值得白纸黑字写下来:Argon2id 让猜密码变得昂贵,却无法让弱密码变强。被包裹的保险库密钥,强度恰好等于包裹它的密码短语。请选一个长的。
恢复靠十二个词
设置同步时,Blackbear 会给你一组恢复短语:十二个词(BIP39),编码了 128 位熵。它们派生出一把独立的密钥,把你的保险库密钥再包裹一次。
忘了密码短语?十二个词能让你重新进来。两个都丢了?你同步的数据就没了。我们无法重置,也没有哪张客服工单能说服我们去重置,因为我们这边根本没有东西可以重置。这不是我们服务的漏洞,而是设计在发挥作用。
添加设备
在新设备上,你输入密码短语。设备在本地派生出你的账户密钥,再从我们服务器保存的包裹副本中解开保险库密钥。服务器从来看不到密码短语,也看不到解开的结果。
一台已解锁的现有设备也可以直接把密钥交过去。它用新设备的 X25519 公钥密封保险库密钥,放进一个传输槽,不管用没用,五分钟后都会自动销毁。这里有一个坦诚的边界:你的现有设备是通过你已登录的账户得知新设备的公钥的,所以这条协助路径需要信任我们的服务器会为你选择的设备转交正确的密钥。密码短语路径则不需要这种信任。
因为这套说法正是钓鱼者会借用的,有一条规则值得白纸黑字写下来:我们绝不会向你索要密码短语或恢复词,也绝不会要求你批准设备传输。这些提示只应该出现在你自己打开的 App 里。其他任何人这么问,都是攻击。
消息
消息使用自己的密钥,和同步分开。每个账户都有一个 X25519 消息身份,每条消息都会用一个新的逐条盐值,通过 HKDF-SHA256 派生出独一无二的密钥。消息以密封的信封形式传递。服务器负责转发,却打不开它们。信封外面会透露什么,请看第 2 部分。
有一点设计说明,直说如下:逐条独立的密钥,并不是某些通讯工具采用的轮转棘轮设计。如果某台设备的长期消息密钥泄露,密封给它的消息就有风险。逐条密钥能限制波及范围,但它不是前向保密,我们宁可你知道其中的区别。
没有内容指纹的历史
版本历史以加密快照的形式同步,用仅在你的保险库内有效的 ID 寻址。如果你和一个陌生人保存了一模一样的文件,服务器看到的是两个毫不相干的 ID:相同的内容无法跨账户匹配。大小和时间仍然可见,和所有同步流量一样。那是元数据,属于第 2 部分的话题。
证明,而不暴露
破坏性的账户操作需要保险库证明:由保险库密钥派生的 Ed25519 密钥所做的签名。你能证明自己持有保险库,而保险库本身从不离开。密钥在内存中完成任务后,会被清零。
这些都不稀奇。XChaCha20-Poly1305、Argon2id、X25519、Ed25519、HKDF-SHA256:标准、平淡、经过大量审查的基础算法。有意思的是那份自律:处处都用上它们,并把它们管不到的地方大声说出来。这个系列接下来要谈的,就是这份自律。
延伸阅读
- Local-first software — Ink & Switch 的这篇文章阐述了背后的理念
- RFC 9106 — Argon2 规范
- XChaCha20-Poly1305 — IETF 规范草案
- RFC 7748 — X25519 背后的椭圆曲线
- RFC 8032 — Ed25519 签名
- The Password Hashing Competition — Argon2 胜出的地方
Blackbear 的安全
- 1. 我们读不到的东西 (当前文章)
- 2. 我们的服务器能看到什么
- 3. 分享如何保持密封