Logo

安全与隐私白皮书(总览)第 5 章

第 5 章 加密与密钥管理

本章说明 FileBolt 的端到端加密(E2EE)与零知识(Zero-Knowledge)边界,并给出 cryptoVersion=v1 的参数、编码规则、失败模式与客户端约束。文件在客户端生成密钥并加密; 服务端与对象存储仅处理密文分片与必要元数据,不具备解密能力,也不保存解密密钥。

5.0 本章摘要

文件在发送方浏览器端生成密钥并完成加密;服务端与对象存储仅保存密文分片, MUST 不接收、不存储、不记录解密密钥(CEK)。 解密密钥通过 URL fragment(# 后部分)携带;fragment 不会随 HTTP 请求发送到服务端, 因此服务端无法从请求或服务端日志中获得 CEK。

加密算法为 AES-128-GCM(AEAD)。每个文件使用独立 CEK(16 字节)。分片大小固定为 16MB。 v1 固定:IV = noncePrefix(8B) || uint32_be(chunkIndex)(4B)AAD 绑定 transferIdfileIdchunkIndex,用于防止上下文替换与重放。 服务端访问 token 仅用于密文访问控制,到期失效。

5.1 传输层加密(TLS)

5.1.1 安全目标

  • 防止上传/下载/API 链路被窃听或篡改。
  • 保护鉴权令牌与密文在网络传输时的机密性与完整性。

5.1.2 安全策略

  • 系统 MUST 仅通过 HTTPS 提供所有页面与 API。
  • 系统 SHOULD 优先启用 TLS 1.3,并按需兼容 TLS 1.2;禁用不安全协议与弱套件。
  • 系统 MUST 启用 HSTS,并设置合理 max-age
  • 若存在网关/代理终止 TLS:回源链路 MUST 同样使用 HTTPS,并进行严格证书校验或等价防护。

5.1.3 限制与边界

TLS 只能保护传输中数据。若用户设备被恶意软件或浏览器扩展控制,TLS 无法阻止本地窃取密钥或明文。

5.2 静态加密(对象存储层)

5.2.1 设计说明

对象存储保存客户端加密后的密文分片。对象存储层静态加密属于额外防护层,不改变零知识边界(CEK 仍只在客户端存在)。

5.2.2 安全策略

  • 文件内容 MUST 以密文形式写入对象存储;服务端 MUST NOT 将明文写入磁盘或持久化缓存。
  • 对象访问 MUST 通过受控接口完成,不允许通过可猜测路径绕过鉴权。
  • 存储访问凭据 MUST 最小权限化,并存放于服务端机密管理;MUST NOT 出现在任何前端可见配置或产物中。

5.3 零知识链接模型

5.3.1 关键约束

  • 服务端 MUST NOT 生成包含解密密钥的完整下载链接。
  • 发送方浏览器 MUST 在本地生成 CEK,并在本地拼接最终分享链接。
  • CEK MUST 仅存在于 URL fragment(#...)或用户设备内存中;服务端 MUST NOT 接收、存储或记录 CEK。
  • 若用户丢失 CEK(或 fragment),平台 MUST NOT 提供“找回密钥/重置密钥以解密历史文件”的能力。

5.3.2 链接格式

https://filebolt.net/transfer?k=<transferId>#<cek_b64url>

  • k 为 transferId(资源定位键),不是加密密钥。
  • cek_b64url 为 CEK 的 base64url 编码,仅存在于 fragment。

5.3.3 token 与 CEK 的分离

  • token 用于控制是否允许获取密文分片/manifest,到期失效。
  • CEK 用于控制是否能解密密文。
  • 系统 MUST 保持二者分离:只有 CEK 但 token 过期/无效时无法继续获取密文;只有 token 但无 CEK 时只能获得密文,无法解密。

5.3.4 fragment 处理要求

  • 客户端 SHOULD 在解析 fragment 后尽早移除地址栏 #...(例如 history.replaceState)。
  • 客户端与服务端日志/上报 MUST NOT 包含 location.hash 或包含 fragment 的完整 URL。

5.4 分片加密

5.4.1 分片规则

  • MUSTchunkSize = 16777216(16MB)。
  • MUSTchunkIndex 从 0 递增且不重复;最后一片 MAY 小于 16MB。
  • MUST:每个文件一个 CEK(每文件一把密钥)。

5.4.2 算法与认证

  • MUST:每个分片使用 AES-128-GCM 加密。
  • MUST:解密端校验 GCM tag;认证失败 MUST 终止并报错,且 MUST NOT 输出任何部分明文。

5.4.3 IV/Nonce 结构

为保证 GCM 安全性,同一 CEK 下 IV MUST NOT 重复。v1 固定:

  • MUST:每文件生成一次随机 noncePrefix(8 字节,安全随机源)。
  • MUST:每分片计数器 uint32_be(chunkIndex)(4 字节,大端)。
  • IV = noncePrefix(8B) || uint32_be(chunkIndex)(4B)(总 12 字节)。
  • MUST:单文件分片数 < 2^32,避免计数回绕。

5.4.4 AAD 字段与编码

AAD 字段固定为:transferIdfileIdchunkIndex。编码固定为:

EncodeAAD_v1(transferId, fileId, chunkIndex) =
  UTF8("v1|") ||
  UTF8(transferId) || 0x1F ||
  UTF8(fileId)     || 0x1F ||
  uint32_be(chunkIndex)
  • MUST:字符串 UTF-8 编码。
  • MUST:分隔符固定单字节 0x1F
  • MUSTchunkIndex 为 4 字节大端整数,禁止字符串形式。

5.5 密钥与公开参数

5.5.1 CEK

  • MUST:CEK 为 16 字节(128-bit),由浏览器安全随机源生成。
  • MUST NOT:CEK 不得上传至服务端;服务端不得存储或记录 CEK。
  • MUST:CEK 仅通过 URL fragment 分发。

5.5.2 CEK 编码

  • MUSTcek_b64url 为 base64url 编码且不含 =
  • MUSTcek_b64url 匹配 ^[A-Za-z0-9\-_]+$
  • MUST:解码后长度恰好为 16 字节,否则链接无效或版本不支持。

5.5.3 每文件公开参数

系统 MUST 为每个文件提供以下公开参数(可存于服务端元数据/manifest):

  • fileId:在该 transfer 内唯一
  • noncePrefix:8 字节,base64url(无 padding)编码
  • cryptoVersionv1
  • chunkSize16777216

5.6 客户端侧泄露控制

  • MUST:下载/解密页不加载第三方脚本(分析/广告/热力图/第三方小组件等)。
  • MUST:下载/解密页仅加载站点自有脚本;第三方域名脚本数量为 0。
  • MUST NOT:日志/错误上报不得包含完整 URL(含 fragment)、CEK 或可推导密钥材料。
  • SHOULD:解析 fragment 后尽早移除地址栏 #...
  • SHOULD:上传/下载完成后清理内存中的密钥材料。

5.7 相关 Claim IDs

本章内容在 Claim IDs 总表中的对应条目(以总表为唯一权威入口):

  • CRYPTO-ZK-01 :CEK 仅在客户端生成并位于 fragment;服务端不生成带密钥链接
  • CRYPTO-ZK-02 :服务端不接收/不存储/不记录 CEK;仅保存密文分片
  • CRYPTO-E2EE-01 :AES-128-GCM;认证失败失败关闭
  • CRYPTO-CHUNK-01 :16MB 固定分片
  • CRYPTO-NONCE-01 :IV=noncePrefix(8B)||uint32_be(chunkIndex)
  • CRYPTO-AAD-01 :AAD 绑定 transferId + fileId + chunkIndex(EncodeAAD_v1)
  • CRYPTO-CLIENT-01 :下载/解密页无第三方脚本;日志/上报不含 fragment/密钥材料
  • CRYPTO-TLS-01 :全站 HTTPS;TLS 基线可由状态页证据入口验证
  • WEB-HEADERS-01 :安全响应头基线可由状态页证据入口验证
  • WEB-OBS-01 :第三方最佳实践综合扫描入口

5.8 cryptoVersion=v1 规范

5.8.1 v1 参数

cryptoVersion
v1
algorithm
AES-128-GCM
cekLength
16 bytes
chunkSize
16777216
tagLength
16 bytes
cekScope
per-file
ivFormat
noncePrefix(8) || uint32_be(chunkIndex)
aadFormat
UTF8("v1|") || UTF8(transferId) || 0x1F || UTF8(fileId) || 0x1F || uint32_be(chunkIndex)
chunkObject
ciphertext || tag

5.8.2 链接判定规则

  • 形式:.../transfer?k=<transferId>#<cek_b64url>
  • MUSTcek_b64url 为 base64url 且不含 =
  • MUSTcek_b64url 匹配 ^[A-Za-z0-9\-_]+$
  • MUSTcek_b64url 解码后长度恰好为 16 字节,否则链接无效或版本不支持。

5.8.3 加密/解密流程

IV  = noncePrefix || uint32_be(chunkIndex)
AAD = EncodeAAD_v1(transferId, fileId, chunkIndex)
(ciphertext, tag) = AES-128-GCM(CEK, IV, plaintextChunk, AAD)
chunkObject = ciphertext || tag

5.8.4 失败模式

以下任一条件出现时,客户端 MUST 终止并提示错误,且 MUST NOT 输出任何部分明文:

  1. cek_b64url 无法解码或解码长度不为 16 字节。
  2. 缺失必要参数或参数格式不正确(例如 noncePrefix 缺失、长度不为 8 字节,或无法解码)。
  3. cryptoVersion 不支持,或与当前实现的 v1 规则不一致。
  4. chunkIndex 重复、回绕、越界,或与预期分片结构不一致。
  5. AAD 无法按 EncodeAAD_v1 生成或校验(字段缺失、编码不一致、上下文不匹配)。
  6. 任一分片的 IV 无法按 v1 规则生成,或出现潜在 IV 复用风险(例如同一文件下 chunkIndex 被重复使用)。
  7. chunkObject 长度异常(小于 tagLength=16,或与预期结构不一致/被截断)。
  8. GCM tag 校验失败(认证失败)。

发生失败时,客户端还必须满足以下要求:

  • MUST 不输出任何部分明文,不进行“跳过认证”的降级解密。
  • MUST NOT 进行宽松重试(不得在同一分片上尝试不同 AAD/IV 组合以“猜测”通过)。
  • MUST 避免泄露敏感材料:日志/错误上报中不得包含 URL fragment、CEK、noncePrefix 或可推导密钥材料。