我们的端到端加密如何工作
更新时间:2026年6月
简而言之
- 你的数据在你的设备上加密,只以不可读的密文形式离开设备。我们的服务器仅存储加密的Blob,无法访问明文。
- 所有密钥都是从一个128位恢复码衍生的,该恢复码永远不会离开你的设备。我们不知道它,也无法恢复它。
- 每个Record都用自己的密钥加密,在解密前要进行真实性检查(使用Ed25519的Verify-before-Decrypt)。
- E2EE并非保护一切:某些元数据(数据量、访问时间)仍然可见,而且被破坏的设备或丢失的恢复码无法通过密码学挽救。
端到端在我们这里意味着什么
端到端加密意味着:你数据的明文仅存在于你自己的设备上。加密和解密完全在本地进行。在设备间同步和存储在我们服务器上的内容总是在离开设备前已加密。
具体来说:一条数据行在客户端规范地编码为JSON,然后用AEAD方法加密。服务器只接收一个不透明的字节Blob加一些管理字段。服务器不拥有解密所需的任何密钥。这是一个架构特性,而非承诺:由于密钥永不到达服务器,服务器在技术上无法读取内容。
点击组件查看详情:
设备 A (发送者)
数据在设备本地使用 AES-256-GCM 加密。恢复码和派生密钥永远不会离开您的设备。
你的数据如何被加密
我们使用来自@noble/ciphers、@noble/hashes和@noble/curves库的久经考验、标准化的构建块:
- AES-256-GCM加密你的Records。AES-256-GCM是一个AEAD方法,同时确保机密性(没人能读取)和完整性(篡改会被发现)。每个Record使用一个随机的12字节Nonce和一个16字节的认证码(MAC)。格式:nonce(12) ‖ ciphertext ‖ mac(16)。
- XChaCha20-Poly1305在设备配对时加密密钥材料(Key-Wrapping)。它也是一个AEAD方法,但使用较长的24字节Nonce,使得随机Nonce不成问题。
- **AAD(Associated Data)**将每个密文绑定到其元数据——头、Bucket、Record-UUID、Revision、Key-Epoch、Schema版本和填充长度。这些字段是共认证的:即使其中一个被修改,解密也会失败。空AAD被严格拒绝(Confused-Deputy防护)。
- Padding隐藏确切大小:每个Record都被填充到Bucket大小(256、1024、4096、16384或65536字节;超过这个范围则为65536的倍数)。因此Blob大小只表示一个粗略等级,而非确切的数据量。
- **Ed25519签名(blob_sig)**对每个Blob进行签名。在每次解密前,客户端根据授权的设备密钥验证此签名(Verify-before-Decrypt)。验证以RFC-8032严格模式运行(zip215:false),这排除了通过Low-Order点伪造。篡改的数据立即产生错误,无静默缺陷。
你的密钥留在你的设备上
一切的根源是一个128位恢复熵——恢复码。从这16个随机字节,使用HKDF-SHA256(一个密钥衍生函数,从一个秘密产生许多域隔离的密钥)确定地衍生Master-Secret,以及进一步的密钥:一个Account-ID、一个密钥加密密钥、认证密钥(authSeed)和路由密钥。每个衍生过程使用自己的、版本化的标签(例如mypep/master/v1),所以密钥不会重叠。
每个Collection有自己的Collection-Key,每个Record和Revision有自己的Record-Key(rec/<uuid>/<rev>)。这种(Record, Revision)的每个密钥确保完全的密钥隔离:没有AES-GCM密钥被使用两次——这是针对Nonce重用攻击的强大防护。
Master-Secret有意不被存储。本地仅存储衍生的值和设备特定的签名/包装密钥——在浏览器中存储在IndexedDB。
点击密钥查看详情:
恢复码
128 位随机熵。绝对根密钥。在账户创建时生成,且不会离开您的设备。
**Recovery——特性和责任:**整个密钥树取决于恢复码。我们不存储它;后门、密钥托管或服务器端重置在架构上被排除——不仅是政策问题,而是因为密钥在技术上永不到达服务器。这是真正E2EE的核心:除你之外——即使是我们——都无法在没有恢复码或配对设备的情况下恢复你的数据。如果码丢失且不存在已配对的设备,数据将永久无法访问。
我们的服务器看到什么——以及什么不看到
服务器仅存储加密的Blob。它看不到:你Records的内容、你Collections的名称(它们仅显示为不透明的HMAC-SHA256-Bucket)或你的密钥。
坦诚地说,它确实看到一些元数据:不透明的Bucket值、Record修订、通过Padding的粗略大小等级、同步频率、设备ID(作为设备密钥的SHA-256哈希)和同步时的游标位置。从中可以推断一个账户是活跃的,以及大约多少和多大的数据何时移动——但不是内容是什么。
冲突解决纯粹通过Revision进行(更高的Revision赢,"Last-Write-Wins"),因为服务器不了解内容且不能进行内容级别的合并。认证通过Ed25519的Challenge-Response进行,随后是JWT令牌;设备列表用你的Auth-Key签名,仅在客户端验证。
多个设备(配对)
一个新设备,比如浏览器,通过QR码与手机配对。QR码直接传输浏览器的公共设备密钥(带外),不通过服务器——这保护密钥交换免受中间人攻击。手机用X25519-ECDH和XChaCha20-Poly1305将Identity-Bundle(账户数据密钥、Auth-Key、Account-ID、路由密钥)仅为浏览器打包并加密传输。
解密后,浏览器显示一个指纹(Account-ID的SHA-256,作为十六进制分组)。如果它与手机上的显示一致,就确保了你收到了正确的Bundle。网络客户端没有自己的恢复码:在浏览器存储丢失时,你只需再次与手机配对。
设备配对流程
浏览器生成临时设备密钥并将其显示为 QR 码。智能手机直接扫描该二维码。由于这是通过摄像头直接进行的 (带外传输),密钥交换对中间人攻击是免疫的。
这不保护什么
E2EE很强大,但不是万能的。老实地面对限制:
- **被破坏的设备:**浏览器上下文中的恶意软件或对IndexedDB的直接访问可以读取本地存储的密钥,从而解密一切。本地数据库以明文形式存在。
- **丢失的恢复码:**没有码和没有配对设备就没有回头路。不存在密钥轮换和备份机制。
- **元数据:**数据量、大小等级、访问模式和时间戳对服务器仍然可见。
- **照片Blob:**照片是内容寻址的(
blob_id = sha256(blob))。拥有明文的人可以检查是否上传了确切的照片。 - **恶意服务器:**它无法读取或伪造内容(Verify-before-Decrypt保护),但它可以扣留数据或重新传输旧的已签名设备列表(Replay)。这种Replay的损害是有限的:设备以幂等方式注册,所以重放的旧列表不会覆盖当前状态。服务器无法发明新的有效数据。
- **Auth-Key:**任何获得Auth-Key的人都可以更改设备列表并重新认证。它必须保持秘密。
密码学构建块概览
- AES-256-GCM——Record加密(AEAD);12字节Nonce,16字节MAC。
- XChaCha20-Poly1305——配对时的Key-Wrapping(AEAD);24字节Nonce,16字节MAC。
- Argon2id v1.3——基于密码的内存硬化衍生(标准:64 MiB、3次迭代、4条通道);可用于未来的基于密码的功能,但不用于恢复码——恢复码已经是128位随机熵,不需要内存硬化。
- HKDF-SHA256——从恢复码衍生所有密钥,通过版本化标签进行域隔离;32字节输出。
- HMAC-SHA256——Collections的不透明路由(Bucket计算)。
- SHA-256——指纹、设备ID和照片寻址的哈希。
- Ed25519(RFC-8032-严格,zip215:false)——Records、设备列表和认证的签名。
- X25519-ECDH——设备配对时的密钥交换。
- Padding-Buckets(256 B至65536 B,超过则为65536的倍数)——大小混淆。
- 密钥长度始终为32字节(256位)。