跳转到内容

Lucky 13 — 用毫秒级时间差把 TLS 加密看穿

是什么

Lucky 13 是一个TLS 协议的攻击:攻击者不破密钥、不挖算法漏洞,只反复发包,从服务器响应快了几微秒还是慢了几微秒里,一个字节一个字节把别人 HTTPS 流量里的密码、cookie 还原出来。

日常类比:你不知道保险柜密码,但每按一个数字,柜门会”咔哒”一下。错的数字咔哒慢,对的数字咔哒快——你只要安静地按几千次、记下时间,密码就出来了。

这个攻击的”幸运 13”指的是:TLS 在算消息认证码(MAC)时会先吞掉 13 字节的协议头。这个 13 字节是攻击者下手的支点。

为什么重要

不理解 Lucky 13,下面这些事都没法解释:

  • 为什么 TLS 1.3(2018)直接删掉了用了 20 年的 CBC 加密模式
  • 为什么现在浏览器里 HTTPS 几乎都是 AES-GCM 或 ChaCha20-Poly1305(这类叫 AEAD
  • 为什么”理论上安全”的密码协议会被一个时间测量打穿——工程实现的小细节能毁掉数学证明
  • 为什么一系列以”动物”命名的攻击(BEAST / CRIME / Lucky 13 / POODLE / Heartbleed)能在 2011-2014 把 TLS 整个推倒重来

核心要点

Lucky 13 攻击的关键是 三个事实拼到一起

  1. TLS 1.0/1.1 用 MAC-then-Encrypt:先算消息认证码、把 MAC 拼到明文后面、再整体用 CBC 加密。解密时反过来:先 CBC 解密、再去 padding、再验证 MAC。

  2. CBC 解密后要剥两层皮(padding + MAC),错误处理时间不一样

    • 如果 padding 格式错 → 服务器立刻报错(
    • 如果 padding 对、MAC 错 → 服务器先算完 HMAC 再报错(
    • 这两种情况的时间差几微秒,但用网络可测
  3. HMAC 按 64 字节分块:明文长度跨过 64 字节边界时,多算一次压缩函数,时间又抖一下。攻击者操控 padding 长度让明文落在边界两侧,对比时间。

把三件事拼起来:攻击者篡改密文里某个块,让服务器解密后得到不同的 padding 长度,测响应时间,反推那个块的最后一个字节是几。然后把它推到下一个字节、再下一个——两小时还原一个 cookie

13 这个数字来自 TLS 协议头:5 字节记录头 + 8 字节序列号 = 13 字节。HMAC 一开始就要把这 13 字节吞进去,吞完正好让攻击的时间差对齐到容易测的位置——攻击者很”幸运”

实践案例

案例 1:浏览器里看不见的”被淘汰”

打开 Chrome,访问任何 HTTPS 网站,按 F12 → Security 面板。你会看到类似:

Connection - secure connection settings
TLS 1.3, X25519, AES_256_GCM

注意 AES_256_GCM——这是 AEAD 模式,没有 MAC-then-Encrypt 这一步,加密和认证一起做。 2013 年之前你看到的多半是 AES_128_CBC_SHA——CBC 模式 + 单独 MAC。Lucky 13 直接打这种组合。

可以用 OpenSSL 看支持的 cipher:

Terminal window
openssl ciphers -v 'HIGH' | grep CBC

如果输出里还有 CBC 套件,那台服务器还有”被攻击的能力”。现代 Nginx / Caddy 默认配置已经把 CBC 移到列表末尾或彻底关掉。

案例 2:Lucky 13 怎么”试”出一个字节

攻击者想知道某个 CBC 块 C 解密后的最后一字节 b。流程:

  1. C 拼到一个伪造的前缀后面,发给服务器
  2. 服务器解密 → 看 padding → 算 HMAC → 报错
  3. 测响应时间 t
  4. 攻击者把伪造前缀里的某一字节加 1 再发——这等于让服务器解密出不同的 padding 字节
  5. 反复试 256 种可能(一字节有 256 种值)
  6. 哪一种让 padding 看起来”正好合法”,时间就最特别——那就是 b

每个字节要发几千到几百万次包,统计学上才能把几微秒的差稳定地分辨出来。

案例 3:为什么 TLS 1.3 删 CBC

TLS 1.3(RFC 8446,2018)的设计直接砍掉 CBC、RC4、3DES,只留 AEAD

  • AES_128_GCM_SHA256
  • AES_256_GCM_SHA384
  • CHACHA20_POLY1305_SHA256

AEAD 把”加密 + 认证”绑成一个原子操作。Lucky 13 类的”先解密看 padding,再验 MAC”漏洞结构上不存在了。

踩过的坑

  1. “理论安全”不等于”实现安全”:CBC + HMAC 在密码学论文里有可证安全证明,但前提是”两步操作时间不可观测”。Lucky 13 证明这前提在网络上做不到

  2. 修补不是真修:OpenSSL 当时打了补丁,让 padding 错误和 MAC 错误的代码路径走一样长的时间。但补丁本身又被发现有侧信道——常时间编程极难写对。

  3. MAC-then-Encrypt 的设计天生危险:先认证再加密(Encrypt-then-MAC)顺序就没这问题,因为收方先验 MAC、错就直接拒,根本不会进解密路径。TLS 选错了顺序,付了 20 年代价。

  4. DTLS 比 TLS 还危险:DTLS(基于 UDP 的 TLS)允许重传,攻击者可以无限次试同一个密文,效率比 TLS 高一个数量级。

适用 vs 不适用场景

适用攻击的目标(2013 年时):

  • TLS 1.0 / 1.1 / 1.2 用 CBC 模式的连接
  • DTLS 1.0 / 1.2
  • OpenSSL / NSS / GnuTLS / PolarSSL / BouncyCastle 等所有主流实现

不适用

  • TLS 1.3(结构上无 CBC,已免疫)
  • AEAD 模式(AES-GCM / ChaCha20-Poly1305)
  • 启用 Encrypt-then-MAC 扩展(RFC 7366)的连接
  • 网络抖动 > 时间差的场景(攻击需要稳定测量微秒级)

案例 4:常时间编程到底有多难

OpenSSL 给 Lucky 13 打的补丁,思路是:不管 padding 对不对,都跑同样多的 HMAC 压缩。看起来简单,实现却踩了一堆坑:

// 简化示意:错的写法
if (padding_ok) {
hmac(data, real_len);
} else {
hmac(data, fake_len); // 时间不一样
}
// 对的写法(伪代码)
hmac_blocks = compute_blocks(max_possible_len);
for i in 0..hmac_blocks {
sha1_compress(...); // 永远跑 max 次
}

但这还不够:memcpy 的长度、CPU 分支预测、内存访问模式都可能泄露。真正常时间的 HMAC 一直到 2014 年才被 OpenSSL 写对。

历史小故事(可跳过)

  • 2011 BEAST(Duong-Rizzo):CBC 的 IV 可预测漏洞,把 CBC 第一刀
  • 2012 CRIME:TLS 压缩侧信道
  • 2013 Lucky 13(本文):CBC padding 时间侧信道
  • 2014 POODLE(Möller 等):SSL 3.0 的 padding oracle,逼业界彻底淘汰 SSL 3.0
  • 2014 Heartbleed(OpenSSL 内存泄露):和 Lucky 13 不同类,但同年放大了”TLS 不安全”的恐慌
  • 2018 TLS 1.3 发布:清理积累 20 年的设计债

Lucky 13 是这条链子的中间一环——单独看不致命,叠加起来推倒了整个旧 TLS

学到什么

  1. 侧信道是真威胁,不是论文玩具——几微秒的时间差,跨网络也能稳定测出来
  2. 协议设计的顺序至关重要:MAC-then-Encrypt vs Encrypt-then-MAC,一字之差差 20 年
  3. 常时间编程极其难:编译器、CPU 缓存、分支预测都会让”看起来对称的代码”实际不对称
  4. AEAD 是工程上的胜利:把”加密 + 认证”绑成一个原子操作,少一个组合就少一类漏洞
  5. 协议演化靠攻击驱动:BEAST → CRIME → Lucky 13 → POODLE 这条链推动了 TLS 1.3 的彻底重构

延伸阅读

关联

  • aes —— AES 在 CBC 模式里被使用,Lucky 13 不破 AES,破”AES + HMAC + 顺序”的组合
  • [[tls-1.3]] —— TLS 1.3 删掉所有 CBC 模式,结构上免疫 Lucky 13
  • heartbleed-2014 —— 同期 TLS 攻击,但走的是内存读越界路线,不同类
  • libsignal —— 现代端到端加密直接用 AEAD,吸取了 Lucky 13 的教训

反向链接

  • amplification-hell-2014 —— Amplification Hell 2014 — 把家用宽带放大成几百 Gbps 的反射攻击
  • libsignal —— libsignal — 端到端加密的 Rust 内核
  • logjam-2015 —— Logjam 2015 — 全世界共用一把锁,国家级窃听者一次撬完