这块剪贴板的端到端加密是怎么做的
把加密了什么、用什么加密、这套设计挡得住和挡不住什么,一次说清楚。这里不打太极:浏览器端的端到端加密有一个明确的天花板,它就写在下面,而不是藏起来。
- 只用原生 WebCrypto
- 服务端没有明文
- 局限写明白
试一下
输满 6 位自动进入
内容在你的浏览器里加密后才上传,服务器看不到明文。24 小时无活动后自动销毁。
密钥交换,一步一步来
整个过程在两个浏览器之间完成五步,服务器只负责转交,一步也读不懂。
创建者的浏览器生成内容密钥
一把 256 位 AES 密钥,由 WebCrypto 生成并写进 localStorage。它是唯一能解开这块剪贴板的东西,服务器从不持有它。
每条内容上传前先加密
每一条都用 AES-256-GCM 加上一个新的 IV 封好。到达服务器的是一对 {iv, 密文}——代码里不存在发送明文的路径。
访客生成临时密钥对
有人输入 6 位数字时,他的浏览器当场生成一次性的 ECDH P-256 密钥对,把公钥随申请发出,私钥留在自己设备上。
点「同意」的那一下完成交换
创建者同意时,他的浏览器用访客公钥做 ECDH,再用 HKDF-SHA-256 派生出一把一次性包裹密钥,把内容密钥加密成一个包,交给服务器转送。
访客解包
访客用自己的私钥派生出同一把包裹密钥,打开这个包,拿到内容密钥。如果打不开,说明传输途中被改动过,访客不会被放进来。
用到的密码学
全部使用浏览器原生 WebCrypto,打包产物里没有任何第三方密码学库。
- 内容加密
- AES-256-GCM,每条内容一个随机 96-bit IV
- 密钥协商
- ECDH P-256。没有用 X25519——Safari 的 WebCrypto 至今不支持
- 密钥派生
- HKDF-SHA-256,info = clipboard/cek-wrap/v1
- 内容密钥存在哪
- 获准设备的 localStorage 里,从不以服务器能打开的形式传给服务器
- 服务端收到什么
- 一个 6 位数字、若干 token 哈希、两个公钥、一个密钥包裹、若干密文块
- 确认码
- 由访客公钥派生的 4 位字符,两边屏幕同时显示
- 保留期
- 最后一次写入之后 24 小时,整块销毁
这套设计挡得住什么
服务端日志
请求日志里不会有可读的东西,因为压根就没有可读的东西被发出去过。
数据库 / Redis 被拖库
整个存储被拖走,得到的是号码、哈希、公钥和密文。没有那把只存在于两台设备上的钥匙,一个字也解不出来。
备份快照
备份继承同样的性质。一份半年前的快照和一份刚打的一样没用。
以后才发生的入侵
下个月才进来的人,能看到的只有还没过期那部分的密文——而绝大多数早就过期了。
有人试出号码
输入一个有效的 6 位数字,只会生成一条你看得见的加入申请,仅此而已。没有人点同意,就不会有内容密钥被发出去。
密钥交换被篡改
GCM 会对密钥包做完整性校验。途中被改过就解不开,访客会被拒绝,而不是悄悄拿到一把被掉包的钥匙。
它挡不住什么
做加密的这段代码,是服务器下发的 JavaScript。一个想要你明文的服务器,可以下发一份改过的页面把明文悄悄送走,而任何客户端加密都拦不住——因为加密本身就在被下发的东西里面。这是所有浏览器端端到端加密产品共同的上限,值得知道,而不该被含糊过去。这套设计真正换来的是「事后」的那一切:日志、拖库、备份、将来的入侵,翻出来全是空的。
元数据仍然可见。服务器知道有这么一块剪贴板、里面有多少条、每条大概多大、什么时候到的、来自哪个 IP。它不知道每条写的是什么。
还有,密钥丢了内容就没了。内容密钥只存在获准设备的 localStorage 里。清掉站点数据,这块剪贴板就永久读不出来——没有找回流程,这是故意的,因为任何找回流程都意味着密钥在别处还有一份。
为什么确认码只有 4 位
两边屏幕上各显示一个由访客公钥派生的 4 位确认码。它最主要的作用其实很朴素:多个人同时申请时,让你分得清哪条申请是谁的。其次,它能让一次拙劣的公钥替换变得可见——一个中途把自己公钥塞进来的转发方,会导致两边的码对不上。
它故意做得短,做长是表演。面对一个能下发被篡改前端的服务器,再长的确认码也没有意义,因为你正在比对的那个码同样由它渲染。一个人们真的会去核对的 4 位,胜过没人看的 12 位。
自己验一下
可观测的那部分,你不用信我。打开浏览器开发者工具盯着网络面板,然后粘贴一条内容:请求体里只有一个 IV 和一段密文,你刚才打的字一个也不在里面。加入申请和批准请求也一样,走的是公钥和一个密钥包。
这个检查唯一证明不了的,是这个页面将来的版本会做什么——那正好就是上面说的那个上限。
安全相关问题
在线剪贴板到底安不安全?
真正该问的是运营方能不能读到你粘的内容——大多数在线剪贴板都能。这一块读不到,因为加密发生在你的浏览器里,密钥不会到达服务器。剩下的风险是恶意服务器下发被改过的页面,这一点对所有浏览器端端到端加密工具都成立。
服务器能读到我的内容吗?
按现在的实现读不到。服务器收到的是 {iv, 密文} 这样的一对数据,手里没有能打开它的钥匙;它也从来看不到内容密钥,那是在两个浏览器之间加密传递的。
具体用了哪些算法?
内容用 AES-256-GCM,密钥协商用 P-256 上的 ECDH,一次性包裹密钥用 HKDF-SHA-256 派生。全部跑在浏览器原生的 WebCrypto 实现上。
为什么用 P-256 而不是 X25519?
因为 Safari 的 WebCrypto 至今不支持 X25519。P-256 到处都支持,在这个场景里也完全够用;这是兼容性的选择,不是偏好。
6 位数字会不会被暴力试出来?
试出一个有效号码是可能的,但那只能换来排进审批队列的资格,换不来内容密钥——密钥只有在真人点了同意之后才会针对那条申请发出。
清了浏览器数据会怎样?
那块剪贴板就永久读不出来了。内容密钥只存在 localStorage 里,服务端没有副本。清站点数据之前,需要的内容请先导出。
24 小时之后还会留下什么吗?
不会。最后一次写入之后 24 小时,剪贴板和里面的每一条都被销毁。有活动就重新计时,没活动就到此为止。
这和「我们用了 TLS」的服务有什么区别?
TLS 保护的是浏览器到服务器这一段,而它的终点恰恰是把明文交给服务器——这正是 TLS 的目的。端到端加密是指服务器从头到尾就没拿到过明文。两者是互补的,不是二选一。
接着看
现在就能用
创建一块剪贴板,把 6 位数字报给对方,点同意,然后粘贴。