bigywater.tech
网络协议 · 95

TLS 记录层:分片、加密与填充

TLS 把应用的字节流切成一条条记录,每条记录单独加密、认证。这篇抓包看这些记录:一次 write 变成多长的记录、每条固定多出多少字节、大块数据怎么切、为什么小消息写得越碎越吃亏,以及记录长度在线上为什么泄露了消息大小。

22 字节
TLS 1.3 每条记录固定多出的字节:5 字节记录头 + 1 字节真实类型 + 16 字节认证标签
16401 字节
一条满记录的记录体长度(16384 字节明文 + 17)
22.0% vs 0.5%
同样 4000 字节,40 次小写入与一次大写入的 TLS 开销占比

每条记录的开销

实测 · 本机执行输出
  TLS 1.3(协商 TLSv1.3,TLS_AES_256_GCM_SHA384):最后 5 条应用数据记录的长度(记录体,不含 5 字节记录头)= [18, 27, 117, 1017, 10017]
    每条的开销(记录体 − 明文 + 5 字节记录头):1 → 22, 10 → 22, 100 → 22, 1000 → 22, 10000 → 22
  TLS 1.2(协商 TLSv1.2,ECDHE-ECDSA-AES256-GCM-SHA384):最后 5 条应用数据记录的长度(记录体,不含 5 字节记录头)= [25, 34, 124, 1024, 10024]
    每条的开销(记录体 − 明文 + 5 字节记录头):1 → 29, 10 → 29, 100 → 29, 1000 → 29, 10000 → 29
  TLS 1.3:记录体 = 明文 + 1 字节真实内容类型 + 16 字节认证标签 → 开销 22 字节(含 5 字节头);TLS 1.2 GCM:记录体 = 8 字节显式 nonce + 密文 + 16 字节标签 → 开销 29 字节。最外层的“记录类型”在 TLS 1.3 里一律写成 23(ApplicationData),真实类型藏在密文里
  记录长度明文可见:1 → 10 → 100 → 1000 → 10000 的明文长度,在线上对应的记录长度严格单调且差值恰好等于明文差——路径上的观察者能看出每条消息有多长

TLS 1.3 固定 22 字节,TLS 1.2 GCM 固定 29 字节,与明文长度无关;记录长度在线上明文可见,观察者能看出每条消息有多长(TLS 1.3 允许填充来掩盖,本篇没有测)。

大块切分

实测 · 本机执行输出
  一次写 100000 字节,最后出现的记录体长度:[69, 16401, 16401, 16401, 16401, 16401, 16401, 1713]
  其中 6 条是满的记录(体 16401 字节 = 16384 明文 + 1 类型字节 + 16 标签 = 16401);剩下的是尾部较短的记录;总共 100188 字节密文承载 100000 字节明文
  TLS 记录明文最大 2^14 = 16384 字节;100000 ÷ 16384 = 6.10 → 需要 7 条记录;额外开销 7 × 22 = 154 字节(0.15%)

明文最大 16384 字节一条;100000 字节需要 7 条记录。

小写入

实测 · 本机执行输出
  40 次 × 100 字节:线上应用数据记录 40 条(最后这批),合计记录体 4680 字节,加上记录头 4880 字节;明文 4000 字节;开销 880 字节(22.0%)
  1 次 × 4000 字节:线上应用数据记录 1 条(最后这批),合计记录体 4017 字节,加上记录头 4022 字节;明文 4000 字节;开销 22 字节(0.5%)
  每条记录固定多 22 字节的头 + 标签,所以小消息越碎、开销占比越高;应用要合并小写入(或依赖 TCP 层之上的缓冲)

40 次小写入的开销是一次大写入的 40 倍(22.0% 对 0.5%)。

分步演示

TLS 记录层
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。全部在本机 lo0:Python ssl 服务器 + 客户端,自签名证书(用后删除),客户端不校验证书;只统计客户端→服务器方向的应用数据记录;每组只测一次,记录结构是确定的,所以没有重复
    • 没有验证:TLS 1.3 的记录填充(Python 没有暴露);小于 16384 的最大片段长度协商(max_fragment_length、record_size_limit);记录大小对首字节延迟的影响(大记录要收齐才能解密);KeyUpdate;ChaCha20-Poly1305 套件的开销(预期相同,没有测);TLS 1.2 的 CBC 套件填充;压缩
    京ICP备2026067502号-1京公网安备11010502063487号