摘要

  • TLS 1.3 把应用正文、一个内部内容类型字节和末尾的零值填充共同保护。发送端决定如何填充,接收端验证并去掉填充后才把正文交给应用;填充改变表示,不赋予应用含义。
  • 抓包能证明密文记录的长度、方向和时间,却不能直接拆出正文、内容类型、填充与 AEAD 开销。更长的记录因此不能单独证明更长的应用消息。
  • 填充可以粗化长度指纹,也能制造掩护流量,但不会消除时序、条数、响应、分段和应用处理泄漏。可审计结论需要端点原始长度、版本化策略、协商限制和拒绝证据。

一个准确数字如何变成错误事实

事故发生后,网络团队保存了完整数据包。某条外发 TLSCiphertext 长度突然上升,时间又恰好靠近一项敏感业务动作。报告于是把长度差当作业务负载差,并把动作归给终端用户。

端点日志补上了缺失部分:进程启用了块填充回调。每条应用数据在加密前都会被补到下一个长度档位;空闲期还可能发出没有应用正文的记录。512 字节是真实传输成本,却不全是用户数据。

这类错误最危险之处,是底层测量完全正确。问题不是“有没有看到字节”,而是“这些字节有权证明什么”。

正文在哪里结束

TLS 1.3 的 TLSInnerPlaintext 依次容纳正文、一个非零 ContentType 字节,以及任意数量的末尾零字节。整个结构进入记录保护。外层类型统一表现为 Application Data,旁观者不能从头部读出内部类型。

AEAD 验证成功后,接收端只在解密返回的明文范围内从尾部向前扫描。零字节是填充,遇到的第一个非零字节是内部内容类型,更前面的部分才是正文。如果整段都找不到非零字节,连接必须以 unexpected_message 终止。

端点由此得到确定边界。应用收到的是去掉填充后的正文。填充即使与正文一起通过完整性验证,也不会因此变成方法、凭证、命令或权限。

空记录也有严格语法

TLS 1.3 允许 Application Data 的内部正文长度为零。发送端可以在没有应用字节时制造外观合理的受保护记录,让“有流量”与“有业务动作”的简单对应变得不可靠。

这个许可不能泛化到所有类型。Handshake 和 Alert 不能把空正文靠填充伪装成合法消息。接收端应拒绝这种形式,因为协议状态变化必须来自真实消息语法,而不是来自一串占空间的零。

掩护流量也不是隐私证明。真实请求可能引发响应、存储访问、不同延迟或连接关闭。填充能改变一个长度分布,不能保证旁观者失去其他区分信号。

填充不能突破记录预算

完整的 TLSInnerPlaintext 仍受大小限制,其中包括正文、内部类型和填充。双方若协商了更小的 record_size_limit,发送端必须把这三部分共同控制在对方给出的限制内。

所以“补 1024 字节”只是策略请求,不是无条件结果。现有正文可能只留下较小空间,库可以截短填充,也可能先改变分片。OpenSSL 的运行代码会计算最大可加长度,并把请求限制在记录上限以内。

同一应用消息于是可能被分成多条、合并在一条、补到档位,或因限制而达不到预期档位。只看策略名称和最终密文长度,无法还原运行路径。

规范提供机制,策略仍由本地承担

现行 TLS 1.3 规范明确没有规定统一填充算法。它不替应用选择块大小、概率分布或掩护流量频率。应用层若知道哪些字段和消息边界敏感,应用自身填充有时更合适;加密后的握手和告警则仍需 TLS 层处理。

OpenSSL 默认不加记录填充。操作者可以设置块大小,也可以安装逐记录回调;应用数据与握手、告警可使用不同档位。启用回调还可能使 kernel TLS 无法使用。这些都是必须纳入成本表的运行事实。

GnuTLS 提供逐次发送的填充参数和长度隐藏能力查询。API 的存在只证明软件有入口,不证明某个进程启用了策略,更不证明某次记录实际加了多少字节。

常数时间检查也只覆盖一层

去填充过程自身可能泄漏处理时间。GnuTLS 提供 GNUTLS_SAFE_PADDING_CHECK,用额外性能成本降低填充长度对库内处理时间的影响。

但正文交给应用后,解析、分配、数据库访问和响应生成仍可能随数据变化。即使 TLS 层扫描近似常数时间,应用层也未必如此。

现行规范因此没有把记录填充描述成完整流量分析防御。它能模糊长度,不能单独消除时序和行为通道。若要更强保证,应用协议、调度和掩护流量必须协同,并接受延迟与带宽代价。

不同协议组合需要不同填充位置

EAP-TLS 1.3 建议使用记录填充降低证书大小泄漏。ECH 则先对内部 ClientHello 做自己的长度整理,又指出含敏感字段的后续加密握手消息可能需要 TLS 记录填充。这些做法都先说明要保护什么,再选择所在层。

HTTP/3 区分应用帧、保留帧与传输层填充,因为三者的粒度、丢包行为和控制主体不同。QUIC 使用 TLS 握手,却不在网络上传普通 TLS 记录。因此不能把 TLS 回调的启用状态自动写成 QUIC 包填充事实。

TLS 1.3 的完整性-only 密码套件还提供了反例:结构可以被完整性保护,但若正文没有加密,填充就无法遮蔽原始大小。隐私效果来自机密性、策略和外围行为的组合,不来自零字节本身。

旁观者可以怎样表述

被动抓包可以可靠记录端点、方向、时间、密文长度、分包与重传。它不能直接看见内部类型、正文长度、填充长度或应用含义。

因此安全表述是:“端点在时间 T 发出 N 字节受保护记录。”端点证据可以继续补充:“进程收到 C 字节正文,执行策略 P 的版本 V,实际加入 Z 个零字节,在限制 L 下形成 I 字节内部明文和 N 字节密文。”

只有两边证据接合后,因果说明才成立。没有策略来源时,可以基于分布提出假设,但不能把两条密文的差值直接升级为用户动作或对象大小。

能经受复盘的证据

逐条保存连接、方向、序列、TLS 版本、保护模式、内部类型、填充前正文长度、请求与实际填充、最终内部长度、密文长度、协商限制、分片决定、库版本、kernel TLS 状态和策略版本。

把带宽、延迟与 CPU 成本和隐私目标放在同一张表里。分别统计正文长度档、填充档、最终长度档、响应相关性与掩护流量比例,不要只报“额外字节百分比”。

还要保存拒绝证据:全零内部明文、空 Handshake、空 Alert、超限填充请求和超过协商限制的记录都应进入负向测试。记录实际告警与本地失败。关闭策略后,再证明回调活动和长度分布确实消失。

最关键的字段正是抓包无法提供的字段:填充前应用正文长度。缺少它,可见长度只能是网络证据,不能成为应用权威。

来源