安全与隐私白皮书(总览)第 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 绑定 transferId、fileId、chunkIndex,用于防止上下文替换与重放。 服务端访问 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 分片规则
- MUST:
chunkSize = 16777216(16MB)。 - MUST:
chunkIndex从 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 字段固定为:transferId、fileId、chunkIndex。编码固定为:
EncodeAAD_v1(transferId, fileId, chunkIndex) =
UTF8("v1|") ||
UTF8(transferId) || 0x1F ||
UTF8(fileId) || 0x1F ||
uint32_be(chunkIndex)- MUST:字符串 UTF-8 编码。
- MUST:分隔符固定单字节
0x1F。 - MUST:
chunkIndex为 4 字节大端整数,禁止字符串形式。
5.5 密钥与公开参数
5.5.1 CEK
- MUST:CEK 为 16 字节(128-bit),由浏览器安全随机源生成。
- MUST NOT:CEK 不得上传至服务端;服务端不得存储或记录 CEK。
- MUST:CEK 仅通过 URL fragment 分发。
5.5.2 CEK 编码
- MUST:
cek_b64url为 base64url 编码且不含=。 - MUST:
cek_b64url匹配^[A-Za-z0-9\-_]+$。 - MUST:解码后长度恰好为 16 字节,否则链接无效或版本不支持。
5.5.3 每文件公开参数
系统 MUST 为每个文件提供以下公开参数(可存于服务端元数据/manifest):
fileId:在该 transfer 内唯一noncePrefix:8 字节,base64url(无 padding)编码cryptoVersion:v1chunkSize:16777216
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> - MUST:
cek_b64url为 base64url 且不含=。 - MUST:
cek_b64url匹配^[A-Za-z0-9\-_]+$。 - MUST:
cek_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 || tag5.8.4 失败模式
以下任一条件出现时,客户端 MUST 终止并提示错误,且 MUST NOT 输出任何部分明文:
cek_b64url无法解码或解码长度不为 16 字节。- 缺失必要参数或参数格式不正确(例如
noncePrefix缺失、长度不为 8 字节,或无法解码)。 cryptoVersion不支持,或与当前实现的 v1 规则不一致。chunkIndex重复、回绕、越界,或与预期分片结构不一致。- AAD 无法按
EncodeAAD_v1生成或校验(字段缺失、编码不一致、上下文不匹配)。 - 任一分片的 IV 无法按 v1 规则生成,或出现潜在 IV 复用风险(例如同一文件下
chunkIndex被重复使用)。 chunkObject长度异常(小于tagLength=16,或与预期结构不一致/被截断)。- GCM tag 校验失败(认证失败)。
发生失败时,客户端还必须满足以下要求:
- MUST 不输出任何部分明文,不进行“跳过认证”的降级解密。
- MUST NOT 进行宽松重试(不得在同一分片上尝试不同 AAD/IV 组合以“猜测”通过)。
- MUST 避免泄露敏感材料:日志/错误上报中不得包含 URL fragment、CEK、
noncePrefix或可推导密钥材料。