加密一条记录的两种方式

当一次 TLS 握手商定了密钥之后,每一条应用数据记录都必须既被加密(这样窃听者无法读取),又被身份验证(这样攻击者无法篡改)。密码套件做到这一点有两大类方式,而套件名称里的模式标记会告诉你是哪一种。

-AES- 或 ChaCha20-Poly1305 这样的 密码,在单一原语中完成两项任务。你把明文和一些关联数据交给它,它返回密文外加一个身份验证标签。解密会把标签和数据一并验证,若有任何改动,则作为单一操作失败。

密码只做加密。为获得完整性,套件把它与一个单独的 配在一起,由套件末尾的哈希命名(例如 TLS_RSA_WITH_AES_128_CBC_SHA 中的 SHA)。协议把这两个操作拴在一起,而它们发生的先后顺序事实证明关系重大。

MAC-then-encrypt 为何出了岔子

TLS 最初使用 MAC-then-encrypt:在明文上计算 HMAC、把它附加上去、将结果填充到分组边界,然后用 CBC 把整体加密。问题在于,接收方必须先解密并去除填充,才能检查 MAC,于是格式错误的填充与错误的 MAC 会在不同阶段被发现。一个能看出某条记录是在填充上还是在 MAC 上失败的攻击者,无论是通过一条错误消息,还是仅凭响应时间,每次尝试都能得知关于明文的一个比特。如此反复,那个比特就会变成整条消息。

这就是填充预言机,而且并非纸上谈兵。多年来的一连串攻击,包括 、Lucky13 和 ,全都利用了 TLS 中的 CBC 记录保护。每一个都用愈发精细的常量时间代码打了补丁,但底层构造仍不断产出新的变体。业界由此得出的教训,是不再使用它。

AEAD 为何如今成为默认

AEAD 消除了整整一类问题。没有可供探测的单独填充阶段,也没有可供计时的单独 MAC 阶段,因为验证与解密是同一个要么成功要么失败的操作。TLS 1.3 把这一点贯彻到底,只允许 AEAD 密码,CBC 套件根本不属于 TLS 1.3。在 TLS 1.2 中两者都存在,所以即便密码是 AES,解码器也会把一个 CBCHMAC-SHA1 的套件标记为弱。

当你在套件名称里看到 GCMCCMPOLY1305 时,你看到的是 AEAD。当你看到 CBC 时,你看到的是较旧的构造,在现代服务中它至多只应作为后备。

AEAD 至今仍提出的取舍

AEAD 并非没有棱角,它只是把棱角挪了位置。每一次 AEAD 加密都需要一个一次性使用的数 nonce,而 GCM 的安全性尤其会在同一个 nonce 与同一把密钥被重复使用时崩塌。TLS 用一个按连接计的计数器替你管理 nonce,所以这对自行构建分帧的系统才是隐患,对 TLS 本身则不然,但这正是 AEAD 设计如此看重 nonce 构造的原因。

另一个取舍是身份验证标签的长度。标准的 GCM 和 Poly1305 套件使用 16 字节标签。CCM_8 套件把它截短为 8 字节,以便在受限链路上节省开销,这会以可度量的方式削弱完整性。所以 TLS_AES_128_CCM_8_SHA256 是一个货真价实的 TLS 1.3 AEAD 套件, 却仍拒绝把它标记为推荐:模式是现代的,但短标签是一种一般互联网无需做出的取舍。