摘要
- 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、超限填充请求和超过协商限制的记录都应进入负向测试。记录实际告警与本地失败。关闭策略后,再证明回调活动和长度分布确实消失。
最关键的字段正是抓包无法提供的字段:填充前应用正文长度。缺少它,可见长度只能是网络证据,不能成为应用权威。
来源
- https://www.rfc-editor.org/rfc/rfc9846.html
- https://www.rfc-editor.org/rfc/rfc8449.html
- https://www.rfc-editor.org/rfc/rfc9150.html
- https://www.rfc-editor.org/rfc/rfc9190.html
- https://www.rfc-editor.org/rfc/rfc9114.html
- https://www.rfc-editor.org/rfc/rfc9849.html
- https://www.rfc-editor.org/rfc/rfc9001.html
- https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
- https://docs.openssl.org/4.0/man3/SSL_CTX_set_record_padding_callback/
- https://docs.openssl.org/master/man3/SSL_CONF_cmd/
- https://github.com/openssl/openssl/blob/master/ssl/record/methods/tls13_meth.c
- https://www.gnutls.org/manual/html_node/On-Record-Padding.html
- https://gitlab.com/gnutls/gnutls/blob/master/lib/includes/gnutls/gnutls.h.in
- https://heng.lu/running-code-primary-the-patch-needed-to-preserve-the-internet-original-design/
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance
