剪贴板内容的敏感程度被严重低估。在一个普通的工作日里,它会承载:
- 密码、一次性验证码、API token、助记词
- 私钥、签名材料、配置片段
- 内部文档、未发布稿件、私下沟通的草稿
- 跨设备复制的文件
大多数剪贴板工具把这些数据当作"短暂的、无关紧要的"。但事实上,剪贴板是任何设备上风险最高的数据面之一——存在时间短、频繁通过网络传输、几乎不被审计。
UniClipboard 围绕一个原则构建:
便利不应当以放弃数据的控制权为代价。
本白皮书描述的所有设计,都直接来自这一条原则。
UniClipboard 的加密体系建立在四条不可妥协的原则之上:
-
由用户掌控
加密由用户发起、由用户拥有。密钥从用户口令派生,永远不会由服务端代为生成或托管。
-
端到端加密
数据在离开设备前完成加密,只在持有匹配密钥的设备上解密。传输层被视为不可信。
-
架构层面的零知识
UniClipboard 的服务端无法读取你的剪贴板内容——这是架构上的事实,不是隐私策略上的承诺。
-
最小暴露面
明文只在严格必要的位置出现,停留时间尽可能短,并在内存释放时清零。
UniClipboard 围绕**空间(space)**这个概念展开:一组互相信任、共享一段加密剪贴板历史的设备。每个空间拥有独立的主密钥;新设备通过短期有效的邀请码 + 空间口令加入。
空间建立后,数据流如下:
剪贴板内容
→ 本地加密(源设备)
→ P2P 直连通道 ──或── 加密中继回退
→ 本地解密(目标设备)
由此带来的几个直接结论:
- 优先使用 P2P 直连,跨家庭/办公室网络也通过 NAT 打洞实现
- 中继节点和任何中间设施只看得到经过认证的密文
- 网络传输路径不会改变密码学保证——载荷加密与传输独立
- 信任边界完全位于用户的设备上
UniClipboard 刻意只采用一组小而成熟的现代原语。
| 用途 |
原语 |
| 认证加密(AEAD) |
XChaCha20-Poly1305 |
| 从口令派生密钥 |
Argon2id(memory-hard,抗 GPU/ASIC) |
| AAD 指纹 / 域分离 |
BLAKE3 |
| 本地加密搜索索引 |
基于 MasterKey 派生的搜索密钥上的 HMAC |
| 随机数 |
操作系统级 CSPRNG(OsRng) |
AEAD 参数
- 算法:XChaCha20-Poly1305
- 密钥长度:32 字节(256 位)
- nonce 长度:24 字节,每条记录由 OS CSPRNG 重新生成。192 位的 nonce 空间使得碰撞在工程意义上完全不必担心。
- 认证标签:16 字节(Poly1305)
- AAD:与数据的逻辑身份绑定(见 §5.2)
Argon2id 参数
- 内存成本:128 MB
- 迭代次数:3
- 并行度:4 线程
- 输出长度:32 字节
这组参数在普通用户设备上仍能维持流畅体验,同时让大规模离线爆破在经济上变得不划算。
5.1 分层密钥结构
UniClipboard 通过把"用户秘密"和"加密数据用的秘密"分离开,避免单点泄露。
用户口令(Passphrase)
│ Argon2id(每空间独立 salt,128 MB · 3 iter · 4 threads)
▼
密钥加密密钥(KEK,32 字节)
│ XChaCha20-Poly1305(KeySlot 包装)
▼
主密钥(MasterKey,32 字节,随机生成)
│ XChaCha20-Poly1305(每条记录绑定 AAD)
▼
剪贴板内容 / Blob 存储
各层的职责:
- 用户口令 —— 永不离开设备,也永不直接用于加密数据。
- KEK —— 由口令通过 Argon2id 派生,仅用于解包 MasterKey。存放在系统 keyring 中。
- MasterKey —— 本地随机生成的 32 字节密钥,用于加密该空间内所有的剪贴板条目和 blob。磁盘上只以已包装的形式(KeySlot 文件)存在。
- 每空间隔离 —— 每个空间持有独立的 MasterKey;切换到另一个空间时,本地历史会在新 MasterKey 下重新加密。
5.2 每条记录的域分离(AAD)
每条加密记录都通过 Additional Authenticated Data(AAD)绑定到自己的身份。AAD 格式带有命名空间和版本号:
uc:inline:v1|<event_id>|<representation_id> // 内联剪贴板数据
uc:blob:v1|<blob_id> // blob 存储
这意味着一段密文要么在原本属于它的位置上解密成功,要么完全不能解密。攻击者无法:
- 把旧记录重放到另一个槽位
- 把一段 blob 密文挪到另一个 blob id 下
- 在事件之间拼接记录
存储侧还会保留一段 16 字节的 BLAKE3 AAD 指纹,用于尽早发现身份不匹配的错误。
5.3 安全本地存储
被包装后的密钥材料分两处存放:
- 系统 keyring 保存 KEK,使用平台原生的安全存储:
- macOS —— Keychain
- Windows —— Credential Manager
- Linux —— Secret Service(兼容 libsecret)
- KeySlot 文件 在磁盘上保存被 KEK 包装后的 MasterKey。仅有磁盘访问权限不足以恢复任何明文。
进程内的敏感值(口令、主密钥、明文缓冲区)都装在专用的"秘密容器"类型里:
- 不能被 clone、序列化、打印或写入日志
Debug 与 Display 输出固定为 [REDACTED]
- drop 时自动将内存清零
6.1 哪些会被加密、哪些不会
UniClipboard 把加密区分成三个相互独立的层次:
- 应用层(基于 MasterKey) —— 在空间 MasterKey 之下的 XChaCha20-Poly1305。防的是"任何能从磁盘读到原始字节、或经手数据但又不持有 MasterKey 的角色"。
- 传输层(QUIC) —— 由 iroh 的 QUIC 栈提供,TLS 1.3 风格的 AEAD,由两端各自的长期 Ed25519 设备身份认证。无论里面装什么,线路上的字节都受这层保护。中继也包含在内。
- 静态存储 —— 落到磁盘的那一份是不是密文。
| 内容 |
应用层(MasterKey) |
传输层(iroh QUIC) |
落盘形式 |
| 文本(inline) |
加密 |
加密 |
密文(每条 AEAD) |
| 图片(blob) |
加密 |
加密 |
密文(UCBL:zstd + AEAD) |
| 文件载荷字节 |
不加密(策略性决定) |
加密 |
明文(按 BLAKE3 寻址) |
| 文件元数据 |
加密 |
加密 |
密文(与剪贴板事件一同加密) |
| 搜索索引 |
加密(HMAC 标签) |
不适用 |
无明文 token(详见 §8) |
| 密钥材料 |
不适用 |
不适用 |
KEK 在系统 keyring;MasterKey 在 KeySlot 文件中以包装态保存 |
为什么文件载荷不在应用层加密。 这是一项有意为之的范围决定,不是疏忽:
- 来源——文件载荷是用户主动复制的内容,其机密性由用户在复制时的意图决定。
- 真正会泄露身份的是元数据。 最可能暴露"这是什么文件"的字段——文件名、所在目录、MIME 类型、预览缩略图——并不在文件本体里,而是剪贴板事件本身的一部分;它们和文本一样、在同一空间 MasterKey 之下被加密。
- 性能。 把文件视为原始的、内容寻址的字节,可以让大文件传输走 BLAKE3 outboard、APFS / Btrfs / XFS 上的 reflink、Windows 上的外部句柄导入、以及 iroh-blobs 的流式下载路径。再套一层 AEAD 会把这些收益抹平,而对机密性几乎没有额外提升(传输层本身已经有保护)。
实际含义。
- 在线路上——即便是文件——任何时候都不会暴露原始明文。QUIC 层会对每个 packet 在两端之间做端到端的认证与加密。Wi-Fi 嗅探者、ISP、iroh 中继看到的都只是 QUIC 密文;中继既不持有 QUIC 会话密钥,也不持有任何空间 MasterKey,无法解开 BlobTicket 所指向的内容。
- 在磁盘上——只有用户自己已配对的设备上——文件载荷会以明文落到内容寻址的本地缓存中。任何能读到该目录的人,或者能进入已登录、已解锁桌面会话的人,都可以恢复出文件字节,本质上跟把文件复制到文件系统是一样的。
- 引用这些文件的剪贴板事件(文件名、路径、MIME、缩略图等)仍然以加密形式落盘,不解锁空间就读不出来。
- 对文本和图片来说,字节本身在空间 MasterKey 之下加密落盘,且在线路上叠加 QUIC 传输层加密。
6.2 静态存储布局
- 剪贴板事件数据库 —— 加密 SQLite 存储;内联文本与结构化 representation 数据按记录加密。
- 加密 blob 存储(UCBL 格式) —— 用于图片载荷及其它应用层管理的 blob。每个 blob 先经 zstd 压缩,再用 XChaCha20-Poly1305 在 per-blob AAD 下加密;文件头为
[UCBL magic | version | nonce]。
- 内容寻址的文件缓存 —— 由 iroh-blobs 提供。文件载荷以原始字节存放,按 plaintext 的 BLAKE3 哈希寻址。该缓存不在应用层加密(见 §6.1)。
- 搜索索引 —— 以 HMAC term 标签为键的倒排索引(见 §8),不含明文 token。
- 密钥材料 —— KEK 存于系统 keyring;包装后的 MasterKey 写入磁盘上的 KeySlot 文件。
UniClipboard 的传输基于 QUIC P2P 网络(iroh)。在它之上叠加了两层互相独立的加密:
- iroh QUIC(传输层) —— 所有连接(剪贴板事件、blob 拉取、在线状态、配对)都跑在 QUIC 之上,使用 TLS 1.3 风格的 AEAD,由两端各自的长期 Ed25519 身份认证。无论里面装什么,线路上的所有字节都受这层保护。中继只转发已经加密的 QUIC 数据报,不持有任何能解密的密钥材料。
- MasterKey(应用层) —— 在 QUIC 之上,剪贴板事件与图片 blob 还会额外用 XChaCha20-Poly1305 在空间 MasterKey 下再加密一遍,这样即便一台已经接入 iroh 网络但不持有 MasterKey 的端点,也读不出内容。如 §6.1 所述,文件载荷不享有这第二层保护——在传输中只依赖 QUIC 端到端加密。
7.1 配对与设备身份
- 每台设备首次启动时在本地生成长期的密钥对。
- 加入空间需要邀请码(短期有效,几分钟内过期)+ 空间口令。没有邮箱、没有账号、没有云端注册。
- 配对信息保存在本地;从任意一台已配对设备上撤销某台设备后,下次同步立刻不再包含它。
7.2 局域网同步
- 同一 Wi-Fi 下的设备直接互联,不经过任何外部基础设施。
- 载荷在到达网线之前就已用空间 MasterKey 加密。
- 抓包只能拿到密文。
7.3 跨网络同步
当两台设备无法直连时,UniClipboard 通过 NAT 打洞建立 P2P 连接。如果打洞失败:
- iroh 中继在双方之间转发 QUIC 数据报。
- 中继只看得到 QUIC 密文以及连接元数据(peer ID、时间戳)。它既不持有 QUIC 会话密钥,也无从持有空间 MasterKey。
- 中继无法派生密钥、无法解密、也无法在不被发现的情况下篡改内容——QUIC 层(始终)和 MasterKey 层(文本与图片)的 AEAD 认证标签都会捕获篡改。
- 中继是可自建的:你可以让 UniClipboard 指向自己的中继节点,并用内置的连通性测试确认配置可用——跨网络同步因此无需经过任何你不掌控的基础设施。
- 仅局域网(LAN-only)模式会彻底关闭中继和所有公网路径——已配对设备此时只走局域网发现与直连,任何数据都不会离开你的局域网。
7.4 剪贴板事件的 wire format
剪贴板事件使用分块的应用层 wire format:
- 把载荷切分为有界的 chunk(大事件不必整个装入内存)
- 每个 chunk 在加密前用 zstd 压缩
- 每个 chunk 在
(transfer_id ‖ chunk_index) 的 AAD 下被认证,因此 chunk 不能被乱序、丢弃或重放
- 头部带有版本号(
UC3\0),以便未来格式升级可以平滑过渡
大型文件载荷走的是另一条传输层原生路径:发送端把文件加入自己的 iroh-blobs 存储并签发一张 BlobTicket;ticket 通过加密的剪贴板事件发给接收端;接收端在带 BLOBS_ALPN 协议标识的 QUIC 流上拉取 blob。该 QUIC 流本身被加密(见 §7.3),但流内的 blob 字节不会再被 MasterKey 二次加密(见 §6.1)。
7.5 韧性
切换 Wi-Fi、休眠唤醒、短暂断网后,连接会自动恢复,无需重新配对。
UniClipboard 在历史规模达到数万条时仍能在毫秒级完成全文搜索,并保持索引在磁盘上加密。
- 搜索密钥派生 —— 解锁后,从 MasterKey 派生出专用的搜索密钥,与内容加密用的密钥分离。
- Term 标签 —— 每个 token 在写入倒排索引前会被替换为
HMAC(search_key, normalized_token)。磁盘上不会出现明文搜索词。
- 会话锁感知 —— daemon 在锁定状态下不会捕获或索引新的剪贴板内容;解锁之前搜索功能不可用。索引重建只发生在解锁状态下。
- 规范化 —— 稳定且带版本的规则(Unicode NFKC、转小写、合并空白、剥离 HTML 标签、URL 与路径切分)。索引中保存
index_version,以便未来规则变化能触发安全的重建。
- 分词 —— 混合策略:拉丁文字、数字、路径段、URL 片段、文件名采用词级切分;中文使用 bigram。这与剪贴板内容常见的混合形态(自然语言、shell 命令、代码、URL、文件路径、中英混排)相匹配。
搜索子系统只做必要的取舍:它不试图隐藏本地访问模式或本地查询频率——这些信息对已经能访问到解锁机器的人来说本就是可观察的。
桌面 GUI 和 uniclip 命令行都通过 HTTP + WebSocket 与本地后台 daemon 通信。daemon 经过专门加固以抵御本地误用:
- 仅 loopback —— daemon 仅监听
127.0.0.1,流量永远不离开本机。
- Bearer → Session 交换 —— 一个保存在
chmod 600 文件中的 bearer token 用于换取短期会话 JWT(5 分钟有效,HS256 签名,密钥 32 字节)。
- PID 白名单 —— 每个会话与调用方进程的 PID 绑定;PID 不匹配会被拒绝。
- 限流 —— 每客户端每分钟 100 次,滑动窗口。
- 不在客户端持久化 —— 会话 token 仅存在于内存中;不会写入
localStorage、sessionStorage 或 cookie。
- 纵深防御 —— 鉴权先于限流执行;危险端点(如清空缓存)必须显式带上
confirmed: true。
以上属性已在内部 phase 级别的安全审计中得到验证。
10.1 UniClipboard 抵御的威胁
| 威胁 |
是否抵御 |
| 局域网的 Wi-Fi 嗅探 |
是 |
| 中继链路上的窃听 |
是 |
| 服务端 / 中继节点被攻陷 |
是 |
| 云端或第三方存储被攻陷 |
是 |
| 中继上的主动 MITM |
是 |
| 失窃设备的磁盘被读取(非活跃状态) |
是 |
| 在不同槽位之间重放或拼接记录 |
是 |
| 本地非特权进程访问 daemon |
是 |
10.2 明确的非目标
UniClipboard 不声称能够抵御以下场景:
- 设备已被完全攻陷(root 级恶意软件、键盘记录、有恶意权限的管理员)
- 用户主动泄露口令
- 在用户已登录、设备已解锁的情况下被取证
- 拥有完整本地权限的人能观察到的旁路(例如原始访问模式)
- 直接读取本地文件缓存中的文件原始字节。 见 §6.1:文件载荷以内容寻址的原始字节形式落盘,不在应用层加密;与之相关的元数据(文件名、路径、MIME、缩略图)仍作为剪贴板事件的一部分被加密。
只有把威胁边界讲清楚,安全声明才有意义。我们把非目标也列出来,是为了让用户能够做出明智的判断。
用户可以:
- 在首次启动时启用或不启用加密初始化,并在之后轮换
- 切换到另一个空间;本地历史会在新空间的 MasterKey 下重新加密
- 立即暂停同步
- 在任意一台已配对设备上撤销丢失或被盗的设备——下次同步起空间不再包含它
- 随时关闭匿名遥测。遥测从不携带剪贴板内容或个人数据。
完整源代码——包括本文描述的每一项密码学操作——已在 GitHub 以 AGPL-3.0 协议开源。请相信代码本身,而不是市场宣传。
UniClipboard 中的加密不是一个可选功能、不是一项市场宣传项、也不是收费版的卖点。
它是产品的地基。
我们的信念是:
- 你的数据属于你
- 同步不等于托管
- 隐私不应当用便利来交换
本文中的每一项架构决策,都直接来自这三句话。