mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
4162 字
12 分钟
密码学攻击案例
2026-04-27

2014 年 Heartbleed 爆发,一个缓冲区越界读取漏洞让攻击者可以从服务器内存中窃取私钥——影响全球 17% 的 HTTPS 服务器。2016 年 ROBOT 攻击复活了 1998 年的 Bleichenbacher 攻击,证明 18 年后的 OpenSSL 仍然没有正确修复 RSA PKCS#1 v1.5 的填充预言机。这些攻击不打破任何密码学理论——它们利用的是实现中的边界检查缺失、时序差异、状态管理错误。理解攻击,才能真正理解防御。eartbleed、ROBOT——理解攻击如何发生,才能更好地防御。

一、协议层攻击#

1.1 BEAST 攻击(2011)#

BEAST(Browser Exploit Against SSL/TLS)是 Thai Duong 和 Juliano Rizzo 在 2011 年 Ekoparty 安全会议上演示的攻击,针对 TLS 1.0 中 CBC 模式的可预测 IV 问题。

攻击原理:TLS 1.0 使用 CBC 模式加密时,每个记录的 IV 直接使用前一个记录的最后一个密文块(即”链式 IV”)。攻击者可以观察到密文,预测下一个 IV,然后精心构造明文使得 P ⊕ IV_prev = P' ⊕ IV_known,从而逐字节推断出 Cookie 等敏感信息。

sequenceDiagram participant A as 攻击者(中间人) participant B as 浏览器 participant S as 服务器 Note over A,S: TLS 1.0 CBC 模式:IV = 前一密文块 A->>B: 注入恶意 JavaScript B->>S: 发送请求(含 Cookie) S-->>A: 返回密文(C1) Note over A: 已知 IV = C1 最后一块 A->>B: 触发第二次请求(猜测字节) B->>S: 发送请求(含 Cookie) S-->>A: 返回密文(C2) Note over A: 比较密文验证猜测<br/>逐字节恢复 Cookie
维度说明
目标TLS 1.0 CBC 模式
原理IV 可预测 + CBC 模式,逐字节推断明文
影响TLS 1.0 的 CBC 模式不安全
修复TLS 1.1+ 使用显式 IV,TLS 1.3 移除 CBC
CVE无独立 CVE(协议设计缺陷)
时间线2011-09 公开演示,2012-01 RFC 7366 提出

BEAST 的逐字节恢复过程:假设攻击者想恢复 Cookie 的第一个字节 c,他构造明文块 P_guess = c_guess ⊕ IV_prev ⊕ C_{n-1},使得加密后如果 c_guess == c,则密文块会与已知值匹配。每次猜测一个字节最多需要 256 次尝试(ASCII 字符集更少),整个 Cookie 可以在几千次请求内恢复。

def beast_guess_byte(target_byte, prev_cipher_block, known_prefix, guess):
"""构造猜测明文,使得加密结果可预测"""
crafted_plaintext = guess ^ prev_cipher_block[0] ^ target_byte
return crafted_plaintext
for guess in range(256):
crafted = beast_guess_byte(cookie_byte, prev_block, prefix, guess)
if check_ciphertext_match(crafted):
print(f"Cookie 字节恢复: {chr(guess)}")
break

防御措施

防御说明效果
升级到 TLS 1.1+使用显式随机 IV根治
1/n-1 记录分割将记录分为 1 字节 + n-1 字节缓解
RC4 优先避开 CBC 模式(但 RC4 本身不安全)不可取
TLS 1.3完全移除 CBC 模式,仅保留 AEAD根治

1.2 CRIME 攻击(2012)#

CRIME(Compression Ratio Info-leak Made Easy)同样由 Duong 和 Rizzo 提出,利用 TLS 压缩的信息泄露来恢复秘密数据。

攻击原理:TLS 支持对数据进行压缩后再加密。如果攻击者能控制部分请求内容(通过 JavaScript 注入),他可以让自己的数据与目标 Cookie 出现在同一个压缩上下文中。由于 DEFLATE 算法会消除冗余,当攻击者猜测的 Cookie 前缀正确时,压缩后的密文会更短——密文长度的变化泄露了明文信息。

graph TD subgraph 压缩前["压缩前"] R1["请求: GET /path<br/>Cookie: session=SECRET"] R2["请求: GET /path<br/>Cookie: session=S<br/>X-Custom: S"] end subgraph 压缩后["压缩后"] C1["压缩长度: L1"] C2["压缩长度: L2 < L1<br/>S 被去重 → 长度更短"] end R1 --> C1 R2 --> C2 Note["长度差异泄露了<br/>Cookie 首字母是 S"]
维度说明
目标TLS 压缩(DEFLATE)
原理压缩算法泄露明文信息(CRIME = Compression Ratio Info-leak Made Easy)
影响可窃取 Cookie/Session
修复禁用 TLS 压缩
CVE无独立 CVE
时间线2012-09 Ekoparty 公开演示
import zlib
def compress_length(data: bytes) -> int:
"""返回压缩后的长度"""
return len(zlib.compress(data))
secret_cookie = "session=Aj3xK9mP"
known = ""
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for pos in range(len(secret_cookie)):
best_char = ""
best_len = float('inf')
for c in charset:
guess = known + c
crafted = f"Cookie: {guess}\r\nX-Attacker: {guess}".encode()
length = compress_length(crafted)
if length < best_len:
best_len = length
best_char = c
known += best_char
print(f"位置 {pos}: 猜测 = {best_char}, 已恢复 = {known}")
Note

CRIME 攻击的变体 TIME(Timing Info-leak Made Easy)和 BREACH(Browser Reconnaissance and Exfiltration via Adaptive Compression of Hypertext)将同样的原理应用到 HTTP 层压缩(gzip)。BREACH 攻击至今仍对启用了 HTTP 压缩的网站有效,防御手段包括:禁用 HTTP 压缩、为敏感响应添加随机填充、使用 CSRF Token 保护。

1.3 POODLE 攻击(2014)#

POODLE(Padding Oracle On Downgraded Legacy Encryption)由 Google 安全团队发现,利用 SSL 3.0 中 CBC 填充验证的缺陷。

攻击原理:SSL 3.0 的 CBC 填充只验证填充的最后一个字节(表示填充长度),而不验证所有填充字节是否正确。攻击者通过降级攻击迫使客户端使用 SSL 3.0,然后利用填充预言机逐字节解密。

flowchart LR subgraph 降级阶段["阶段一:协议降级"] C1["客户端: TLS 1.2"] -->|"攻击者干扰"| F1["握手失败"] F1 -->|"回退"| C2["客户端: SSL 3.0"] end subgraph 解密阶段["阶段二:填充预言"] C2 -->|"修改密文最后一块"| S1["服务器验证填充"] S1 -->|"只检查最后1字节"| V1["验证通过/失败"] V1 -->|"统计推断"| P1["恢复明文字节"] end 降级阶段 --> 解密阶段
维度说明
目标SSL 3.0 CBC 模式
原理SSL 3.0 填充验证不完整
影响降级攻击 + 明文恢复
修复完全禁用 SSL 3.0
CVECVE-2014-3566
时间线2014-10 Google 公开

POODLE 的填充缺陷对比

协议填充格式验证方式安全性
SSL 3.0XX XX ... LL仅验证最后 1 字节 LL不安全
TLS 1.0+LL LL ... LL验证所有填充字节 == LL安全(但 IV 可预测)
TLS 1.1+LL LL ... LL + 显式 IV验证所有填充 + 随机 IV安全
TLS 1.3无 CBC 模式仅 AEAD安全
Warning

POODLE 证明了协议降级是致命的。即使你支持 TLS 1.2,只要还允许 SSL 3.0 回退,攻击者就能强制降级。防御 POODLE 的唯一正确做法是完全禁用 SSL 3.0,并启用 TLS_FALLBACK_SCSV 信号防止降级。2014 年后主流浏览器已默认禁用 SSL 3.0。

1.4 Lucky13 攻击(2013)#

Lucky13 由 Al Fardan 和 Paterson 在 2013 年提出,是针对 TLS CBC 模式填充验证的时序侧信道攻击。

攻击原理:TLS 服务器在验证 CBC 填充时,如果填充不正确会立即返回错误,而填充正确但 MAC 不匹配时需要额外计算 MAC。这个时间差异(约 1-2 微秒)虽然很小,但通过大量统计采样可以区分,从而构建填充预言机。

import time
import hmac
def tls_cbc_decrypt_and_verify(key, iv, ciphertext, mac_key):
"""TLS CBC 解密 + MAC 验证(不安全实现)"""
plaintext = aes_cbc_decrypt(key, iv, ciphertext)
padding_len = plaintext[-1]
padding_valid = all(b == padding_len for b in plaintext[-padding_len-1:])
if not padding_valid:
return None # ← 时间差异!
data = plaintext[:-padding_len - mac_length]
mac_received = plaintext[-mac_length:-padding_len-1] if padding_len > 0 else plaintext[-mac_length:]
mac_computed = hmac.new(mac_key, data, 'sha256').digest()
if mac_received != mac_computed:
return None # ← 时间差异!
return data
维度说明
目标TLS CBC 模式 MAC 验证
原理填充验证与 MAC 验证的时间差异
影响理论上可解密 TLS 流量
修复常数时间填充验证 + MAC 验证
CVECVE-2013-0169
时间线2013-02 公开

Lucky13 的防御关键:确保无论填充是否正确,总执行时间相同。OpenSSL 的修复方案是在填充错误时也执行一次”虚假”的 MAC 计算,使得两条路径耗时一致。

1.5 降级攻击总览#

降级攻击是一类通过迫使通信双方使用较弱协议版本或算法来实现攻击的手段。

graph TB subgraph 降级攻击类型 D1["协议降级<br/>TLS 1.2 → SSL 3.0"] D2["密钥交换降级<br/>ECDHE → DH-512"] D3["加密套件降级<br/>AES-GCM → RC4"] D4["签名降级<br/>RSA-PSS → PKCS#1 v1.5"] end subgraph 攻击实例 D1 --> P1["POODLE"] D2 --> P2["Logjam (2015)"] D3 --> P3["FREAK (2015)"] D4 --> P4["ROBOT"] end subgraph 防御机制 DEF1["TLS_FALLBACK_SCSV"] DEF2["TLS 1.3 强制安全参数"] DEF3["禁用旧协议/算法"] DEF4["证书锁定"] end P1 --> DEF1 P2 --> DEF2 P3 --> DEF3 P4 --> DEF3
降级攻击年份降级目标CVE防御
POODLE2014SSL 3.0CVE-2014-3566禁用 SSL 3.0
FREAK2015EXPORT 级 RSA-512CVE-2015-0204禁用 EXPORT 套件
Logjam2015EXPORT 级 DH-512CVE-2015-4000禁用 EXPORT DH,使用 ≥2048 位 DH
DROWN2016SSLv2 解密 TLSCVE-2016-0800禁用 SSLv2
ROBOT2017PKCS#1 v1.5CVE-2017-…使用 RSA-OAEP

二、实现层攻击#

2.1 Heartbleed(2014)#

Heartbleed 是 OpenSSL 的缓冲区越界读取漏洞,是互联网历史上影响最广泛的安全漏洞之一。

graph LR A["攻击者"] -->|"Heartbeat:<br/>payload=1字节<br/>声明长度=65535"| OPENSSL["OpenSSL"] OPENSSL -->|"返回 65535 字节<br/>(1字节有效 + 65534字节内存)"| A Note["请求的长度 > 实际消息长度<br/>OpenSSL 没有验证长度"]

漏洞根因分析:TLS Heartbeat 扩展(RFC 6520)允许通信双方探测连接是否存活。客户端发送 Heartbeat 请求,包含有效载荷和声明长度。服务器应当回显相同的有效载荷。但 OpenSSL 的实现没有验证声明长度是否与实际有效载荷长度一致:

// OpenSSL 1.0.1 中的漏洞代码(简化)
// 文件: ssl/d1_both.c 和 ssl/t1_lib.c
int dtls1_process_heartbeat(SSL *s, unsigned char *p, unsigned int length)
{
unsigned char *pl = p; // 指向 heartbeat 有效载荷
unsigned short hbtype; // heartbeat 类型
unsigned int payload; // 声明的有效载荷长度
unsigned int padding = 16; // 最小填充
hbtype = *pl++; // 读取类型
n2s(pl, payload); // 读取声明的长度(攻击者可控!)
pl = p + 3; // 指向实际有效载荷
// 没有验证 payload <= length - 3!
unsigned char *buffer = OPENSSL_malloc(1 + 2 + payload + padding);
unsigned char *bp = buffer;
*bp++ = TLS1_HB_RESPONSE; // 响应类型
s2n(payload, bp); // 写入长度
// 从 pl 复制 payload 字节,但 pl 实际可能只有 1 字节!
memcpy(bp, pl, payload); // 越界读取!读取服务器内存
return 1;
}
// 修复后的代码(OpenSSL 1.0.1g)
int dtls1_process_heartbeat(SSL *s, unsigned char *p, unsigned int length)
{
unsigned char *pl = p;
unsigned short hbtype;
unsigned int payload;
unsigned int padding = 16;
hbtype = *pl++;
n2s(pl, payload);
// 验证声明长度不超过实际消息长度
if (1 + 2 + payload + 16 > length) {
return 0; /* silently discard */
}
pl = p + 3;
unsigned char *buffer = OPENSSL_malloc(1 + 2 + payload + padding);
unsigned char *bp = buffer;
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload); // 现在安全:payload 已验证
return 1;
}
维度说明
CVECVE-2014-0160
影响可读取服务器内存中的私钥、密码、Cookie
修复OpenSSL 1.0.1g,添加长度验证
教训永远验证输入长度
时间线2012-03 引入漏洞(1.0.1),2014-04 公开修复
影响范围互联网约 17% 的 HTTPS 服务器受影响

Heartbleed 的攻击脚本(仅教学用途):

import struct
import socket
def construct_heartbeat_request(payload_size=65535):
"""构造恶意 Heartbeat 请求"""
# Version = TLS 1.0 (0x0301)
heartbeat_type = b'\x01' # Request
payload = b'\x00' # 1 字节实际载荷
payload_length = struct.pack('>H', payload_size) # 声明 65535 字节
heartbeat_message = heartbeat_type + payload_length + payload
record_length = struct.pack('>H', len(heartbeat_message))
tls_record = b'\x18\x03\x01' + record_length + heartbeat_message
return tls_record
def check_heartbleed(host, port=443):
"""检测目标是否受 Heartbleed 影响"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
try:
sock.connect((host, port))
sock.send(construct_heartbeat_request())
response = sock.recv(65535 + 5)
if len(response) > 10:
print(f"[!] {host}:{port} 可能受 Heartbleed 影响!")
return True
else:
print(f"[] {host}:{port} 不受 Heartbleed 影响")
return False
except Exception as e:
print(f"[-] 检测失败: {e}")
return False
finally:
sock.close()
Caution

Heartbleed 的教训远不止”忘记验证长度”。它暴露了几个深层问题:1)OpenSSL 由两个兼职维护者维护,却保护了互联网 2/3 的流量;2)C 语言缺乏内存安全,memcpy 越界读取不会报错;3)漏洞存在了两年才被发现——说明代码审计严重不足。Heartbleed 之后,Linux 基金会成立了 CII(Core Infrastructure Initiative)资助关键开源项目。

2.2 ROBOT 攻击(2017)#

ROBOT(Return Of Bleichenbacher’s Oracle Threat)是 Bleichenbacher 攻击的回归,由 Hanno Böck、Juraj Somorovsky 和 Craig Young 在 2017 年发现。

Bleichenbacher 攻击原理(1998 年原始版本):RSA PKCS#1 v1.5 加密时,明文格式为 0x00 0x02 [padding] 0x00 [message]。服务器在解密后检查格式是否正确,如果不正确返回错误。这个”格式是否正确”的判断就是一个预言机(Oracle),攻击者可以利用它逐字节恢复明文。

sequenceDiagram participant A as 攻击者 participant S as 服务器(RSA解密) Note over A,S: 目标:解密 C = M^e mod n loop 每次迭代缩小明文范围 A->>S: C' = C * s^e mod n(乘以盲化因子) S->>S: 解密 C',检查 PKCS 格式 alt PKCS 格式正确 S-->>A: 无错误(预言机回答"是") Note over A: 明文 M 在 [B, (B+1)/s) 范围内 else PKCS 格式错误 S-->>A: 错误(预言机回答"否") Note over A: 缩小搜索范围 end end Note over A: 经过 ~数千次查询<br/>恢复完整明文 M
维度说明
目标RSA PKCS#1 v1.5 填充
原理利用 Padding Oracle 逐字节恢复明文
影响可解密 TLS 流量、伪造签名
修复使用 RSA-OAEP 替代 PKCS#1 v1.5
CVE多个 CVE(CVE-2017-6279 等)
时间线1998 原始攻击,2017 ROBOT 回归

为什么 20 年后仍然有效? 许多厂商在修复 Bleichenbacher 攻击时采用了”打补丁”的方式——添加随机延迟、模糊化错误消息——而不是从根本上替换 PKCS#1 v1.5。ROBOT 证明这些补丁是不够的:

修复方式效果问题
统一错误消息缓解时序差异仍可利用
随机延迟缓解统计平均可消除噪声
添加随机填充检查缓解仍存在可区分的差异
替换为 RSA-OAEP根治不兼容旧客户端
TLS 1.3 禁用 RSA 密钥交换根治仅限 TLS 1.3
Warning

Bleichenbacher 攻击在 1998 年就被发现了,但 20 年后仍然有大量系统受影响(ROBOT)。这说明:1)安全漏洞不会自动消失;2)旧协议的兼容性支持是安全债务;3)必须主动移除不安全的算法。ROBOT 影响了包括 F5、Cisco、HPE 等厂商的设备,证明”打补丁”式的安全修复是不可持续的。

2.3 Debian RNG 灾难(2008)#

2008 年,Debian 发行版的一个 OpenSSL 补丁意外地破坏了随机数生成器,导致生成的密钥空间从 2^128 缩小到 32768 种可能。

漏洞根因:Debian 维护者在 2006 年为了消除 Valgrind 内存检查的警告,移除了 OpenSSL 中 md_rand.c 里向随机数状态添加不确定数据的代码。结果 PRNG 的种子仅来自进程 ID(最多 32768 种可能)。

// Debian 修改前的 OpenSSL 代码(md_rand.c)
// 这段代码将不确定的堆栈数据混入随机状态
static void ssleay_rand_add(const void *buf, int num, double add_entropy)
{
// ...
for (i = 0; i < num; i++) {
md_buf[0] = buf[i]; // 来自调用者的数据
md_buf[1] = (unsigned char)(local_lock); // 不确定数据
// Debian 补丁移除了下面这行:
// md_buf[2] = (unsigned char)(local_md[0]); // ← 被移除!
// md_buf[3] = (unsigned char)(local_md[1]); // ← 被移除!
// ...
}
}
import itertools
possible_pids = range(1, 32768) # Linux PID 最大 32768
def generate_all_weak_keys():
"""预计算所有可能的弱密钥"""
weak_keys = {}
for pid in possible_pids:
seed = pid # 实际种子仅来自 PID
key = derive_key_from_seed(seed)
weak_keys[key.public_key()] = key
return weak_keys
维度说明
CVECVE-2008-0166
影响2006-09 至 2008-05 间 Debian/Ubuntu 生成的所有密钥
根因维护者不理解代码的密码学意义,移除了”看似无用”的代码
修复OpenSSL 0.9.8c-4etch3,重新生成所有密钥
教训不要修改你不理解的密码学代码

Debian RNG 的深远影响

影响范围说明
SSH 密钥所有受影响期间生成的 SSH 密钥可被秒破
SSL/TLS 证书受影响期间签发的证书需要吊销重签
GPG 密钥OpenPGP 密钥同样受影响
DNSSECDNS 签名密钥需要重新生成
比特币部分早期比特币地址使用弱密钥

三、侧信道攻击#

3.1 时序攻击#

时序攻击通过测量操作时间推断密钥信息,是最实用的侧信道攻击之一。

def verify_mac(expected, actual):
for a, b in zip(expected, actual):
if a != b:
return False # 第一个不匹配就返回 → 时间泄露位置
return len(expected) == len(actual)
def verify_mac_safe(expected, actual):
if len(expected) != len(actual):
return False
result = 0
for a, b in zip(expected, actual):
result |= ord(a) ^ ord(b) # XOR,总是比较所有字节
return result == 0

HMAC 时序攻击实战:攻击者可以通过测量服务器验证 HMAC 的时间,逐字节恢复正确的 MAC 值。假设 HMAC 是 32 字节(SHA-256),攻击者逐字节尝试:

import hmac
import time
def timing_attack_vulnerable_compare(a: bytes, b: bytes) -> bool:
description: " """不安全的逐字节比较——时间泄露不匹配位置"""
if len(a) != len(b):
return False
for x, y in zip(a, b):
if x != y:
return False # 短路返回!时间与匹配前缀长度成正比
return True
def attack_hmac_byte_by_byte():
"""逐字节恢复 HMAC"""
target_hmac = b'\x3a\xf7\x2b\x1c' # 假设这是正确的 HMAC(简化为4字节)
recovered = b''
for pos in range(len(target_hmac)):
best_byte = 0
best_time = 0
for guess in range(256):
crafted = recovered + bytes([guess]) + b'\x00' * (len(target_hmac) - pos - 1)
start = time.perf_counter_ns()
timing_attack_vulnerable_compare(crafted, target_hmac)
elapsed = time.perf_counter_ns() - start
if elapsed > best_time:
best_time = elapsed
best_byte = guess
recovered += bytes([best_byte])
print(f"位置 {pos}: 恢复字节 0x{best_byte:02x}")
return recovered
防御措施说明推荐度
常数时间实现所有路径执行时间相同
hmac.compare_digest()Python 内置的常数时间比较
crypto.timingSafeEqual()Node.js 常数时间比较
ConstantTimeUtils.equals()Java 常数时间比较
随机延迟添加随机噪声(可能被统计消除)
Node.js
# Python
import hmac
hmac.compare_digest(received_mac, expected_mac)
const crypto = require('crypto');
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
# Go
import "crypto/subtle"
subtle.ConstantTimeCompare(a, b)
# Java
import java.security.MessageDigest;
MessageDigest.isEqual(a, b);

3.2 缓存时序攻击#

缓存时序攻击利用 CPU 缓存的行为差异推断密钥信息。当程序访问的内存地址在缓存中(cache hit)时速度更快,不在缓存中(cache miss)时需要从主存读取,时间差异可达 100 倍以上。

AES 缓存时序攻击:AES 的 SubBytes 步骤使用查找表(T-table),访问模式取决于密钥和明文。攻击者通过测量加密时间,可以推断密钥字节。

graph TD subgraph CPU缓存["CPU 缓存层次"] L1["L1 Cache<br/>~1ns<br/>32-64KB"] L2["L2 Cache<br/>~4ns<br/>256KB-1MB"] L3["L3 Cache<br/>~12ns<br/>4-32MB"] RAM["主存 RAM<br/>~100ns<br/>GB级"] end L1 -->|"miss"| L2 L2 -->|"miss"| L3 L3 -->|"miss"| RAM subgraph 攻击原理 A1["攻击者测量加密时间"] --> A2["时间短 → cache hit<br/>推断访问了哪些表项"] A2 --> A3["访问模式 → 密钥字节"] end RAM -.->|"时间差异 100x"| A1
攻击类型目标时间差异防御
AES T-table 攻击AES 查找表访问模式~100nsAES-NI 硬件指令
RSA 幂等攻击RSA 滑动窗口指数运算~1μs常数时间幂运算
ECDSA nonce 攻击椭圆曲线标量乘法~100ns常数时间标量乘法
def aes_t_table_access_time(plaintext_byte, key_byte):
"""模拟 T-table 访问时间"""
index = plaintext_byte ^ key_byte # S-box 输入
pass
import subprocess
def check_aes_ni():
"""检查系统是否支持 AES-NI"""
result = subprocess.run(['grep', 'aes', '/proc/cpuinfo'],
capture_output=True, text=True)
if 'aes' in result.stdout:
print(" 支持 AES-NI,不受缓存时序攻击影响")
else:
print(" 不支持 AES-NI,可能受缓存时序攻击影响")

3.3 功耗分析#

功耗分析是对硬件实现的最强大侧信道攻击之一,特别针对智能卡、HSM 和嵌入式设备。

攻击类型说明防御难度
简单功耗分析(SPA)观察功耗曲线,直接识别操作序列常数时间实现
差分功耗分析(DPA)统计分析多次功耗,提取密钥位掩码技术
电磁分析测量电磁辐射法拉第笼
模板攻击预先建立功耗模板,匹配识别电路级防护极高
graph LR subgraph DPA攻击流程["DPA 攻击流程"] S1["收集大量<br/>加密操作的功耗曲线"] --> S2["选择中间值<br/>(如 S-box 输出某一位)"] S2 --> S3["根据密钥猜测<br/>划分功耗曲线"] S3 --> S4["计算差分曲线<br/>正确猜测 → 出现尖峰"] S4 --> S5["逐字节恢复<br/>完整密钥"] end subgraph 防御技术["防御技术"] D1["布尔掩码<br/>XOR 随机掩码"] D2["算术掩码<br/>加法随机掩码"] D3["乱序执行<br/>随机化操作顺序"] D4["噪声注入<br/>增加功耗噪声"] end S5 -.->|"对抗"| D1 S5 -.->|"对抗"| D2 S5 -.->|"对抗"| D3

DPA 攻击的数学原理:假设攻击者收集了 N 次加密的功耗曲线 T_1, T_2, ..., T_N,对应明文 P_1, P_2, ..., P_N。对于密钥字节 k 的某个猜测 k_guess

  1. 计算中间值 D_i = S-box(P_i ⊕ k_guess) 的某一位
  2. 根据 D_i 将功耗曲线分为两组:D_i = 0D_i = 1
  3. 计算两组平均功耗的差分:ΔT = avg(T | D=0) - avg(T | D=1)
  4. 如果 k_guess 正确,ΔT 会出现统计显著的尖峰;否则趋近于零

3.4 Spectre/Meltdown(2018)#

Spectre 和 Meltdown 是 2018 年披露的 CPU 微架构侧信道攻击,影响了几乎所有现代处理器。

维度SpectreMeltdown
类型侧信道权限检查绕过
影响跨进程读取内存读取内核内存
根因CPU 分支预测CPU 乱序执行
CVECVE-2017-5753/5715CVE-2017-5754
缓解KPTI、retpolineKPTI、微码更新
import time
array1_size = 16
array1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
array2 = [0] * 256 * 512 # 探测数组,用于 Flush+Reload
secret = b"SECRET_KEY"
def spectre_v1_demo(malicious_index):
"""Spectre v1 简化演示"""
for _ in range(100):
index = 0 # 合法索引,训练分支预测为"进入 if"
if index < array1_size:
value = array2[array1[index] * 512] # 访问探测数组
index = malicious_index # 越界索引!
if index < array1_size: # 分支预测器猜"进入",投机执行
value = array2[array1[index] * 512] # 投机执行越界读取!
for i in range(256):
start = time.perf_counter_ns()
_ = array2[i * 512]
elapsed = time.perf_counter_ns() - start
if elapsed < 100: # cache hit → 这是被投机执行访问的项
return i
return None

四、密码学攻击时间线与全景#

timeline title 密码学攻击重大事件时间线 1998 : Bleichenbacher 攻击 : PKCS#1 v1.5 Padding Oracle 2004 : MD5 碰撞攻击 : 理论碰撞 → 实际碰撞 2006 : Debian RNG 被破坏 : 弱随机数种子(2008年发现) 2011 : BEAST 攻击 : TLS 1.0 CBC 可预测 IV 2012 : CRIME 攻击 : TLS 压缩信息泄露 2013 : Lucky13 : CBC 填充时序攻击 2014 : Heartbleed : OpenSSL 缓冲区越界读取 2014 : POODLE : SSL 3.0 填充预言机 2015 : FREAK/Logjam : EXPORT 级降级攻击 2016 : DROWN : SSLv2 攻击 TLS 2017 : ROBOT : Bleichenbacher 回归 2018 : Spectre/Meltdown : CPU 微架构侧信道 2020 : Zombieload/RIDL : MDS 攻击 2022 : Hertzbleed : 频率侧信道攻击

五、攻击分类与对比#

攻击层次类型受影响协议影响修复方案
BEAST协议IV 可预测TLS 1.0 CBC明文恢复TLS 1.1+ 显式 IV
CRIME协议压缩泄露TLS 压缩Cookie 窃取禁用 TLS 压缩
POODLE协议填充预言SSL 3.0 CBC明文恢复禁用 SSL 3.0
Lucky13协议+实现时序侧信道TLS CBC理论解密常数时间验证
Heartbleed实现越界读取OpenSSL内存泄露验证输入长度
ROBOT协议+实现填充预言RSA PKCS#1 v1.5解密+伪造RSA-OAEP
Debian RNG实现弱随机数OpenSSL (Debian)密钥可破修复 PRNG
Spectre硬件微架构侧信道所有跨进程读取微码+软件缓解
DPA硬件功耗侧信道智能卡/HSM密钥提取掩码+噪声

六、密码学攻击防御原则#

原则说明对应攻击
使用标准库不要自己实现密码学Debian RNG
移除旧算法主动禁用不安全的协议和算法POODLE/ROBOT
常数时间敏感操作使用常数时间实现Lucky13/时序攻击
输入验证验证所有输入的长度和格式Heartbleed
纵深防御多层安全,单点失败不影响整体降级攻击
及时更新保持依赖和协议版本最新所有攻击
硬件加速使用 AES-NI 等硬件指令缓存时序攻击
密钥隔离不同用途使用不同密钥Heartbleed
安全审计定期审计密码学代码Heartbleed/ROBOT
最小权限仅暴露必要的信息Spectre/DPA

七、动手实践:检测与防御#

7.1 TLS 配置安全检测#

git clone https://github.com/drwetter/testssl.sh.git
cd testssl.sh
./testssl.sh example.com
./testssl.sh --heartbleed example.com # Heartbleed
./testssl.sh --robot example.com # ROBOT
./testssl.sh --poodle example.com # POODLE
./testssl.sh --crime example.com # CRIME
openssl s_client -connect example.com:443 -ssl3 # SSL 3.0(应失败)
openssl s_client -connect example.com:443 -tls1 # TLS 1.0(应失败)
openssl s_client -connect example.com:443 -tls1_2 # TLS 1.2(应成功)
openssl s_client -connect example.com:443 -tls1_3 # TLS 1.3(应成功)

7.2 常数时间代码审计#

def unsafe_compare(a, b):
if len(a) != len(b):
return False
for i in range(len(a)):
if a[i] != b[i]:
return False # 时间泄露不匹配位置
return True
def safe_compare(a, b):
if len(a) != len(b):
return False
result = 0
for i in range(len(a)):
result |= a[i] ^ b[i]
return result == 0
def unsafe_modexp(base, exp, mod):
result = 1
for bit in bin(exp)[2:]:
result = (result * result) % mod
if bit == '1': # 分支依赖密钥位!
result = (result * base) % mod
return result
# 安全:常数时间模幂(使用平方-乘法,无分支)
def safe_modexp(base, exp, mod):
result = 1
for bit in bin(exp)[2:]:
result = (result * result) % mod
# 无论 bit 是 0 还是 1,都执行乘法
temp = (result * base) % mod
# 常数时间选择:bit=1 选 temp,bit=0 选 result
mask = -(bit == '1') # bit=1 → mask=0xFFFFFFFF,bit=0 → mask=0
result = (result & ~mask) | (temp & mask)
return result

7.3 安全的 TLS 配置#

# Nginx 安全 TLS 配置(防御已知攻击)
server {
listen 443 ssl http2;
# 仅允许 TLS 1.2 和 TLS 1.3(防御 POODLE/BEAST)
ssl_protocols TLSv1.2 TLSv1.3;
# 仅允许强加密套件(防御 CRIME/FREAK/Logjam)
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
# 服务器偏好(防御客户端降级)
ssl_prefer_server_ciphers on;
# 禁用 TLS 压缩(防御 CRIME)
# OpenSSL 1.0.2+ 默认禁用,但显式声明更安全
ssl_compression off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
# HSTS(防御降级攻击)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
}

八、总结#

上一章深入探讨了安全工程实践。

维度关键要点
协议攻击BEAST/CRIME/POODLE/Lucky13,TLS 1.3 移除所有弱点
实现攻击Heartbleed(越界读取)、ROBOT(Padding Oracle)、Debian RNG(弱随机数)
侧信道攻击时序攻击、缓存时序、功耗分析、Spectre
降级攻击POODLE/FREAK/Logjam/DROWN,TLS 1.3 强制安全参数
防御原则标准库、移除旧算法、常数时间、输入验证、硬件加速
Note

密码学攻击的历史告诉我们一个残酷的事实:理论上安全 ≠ 实现上安全。AES 在数学上没有被破解,但实现可能受缓存时序攻击;RSA 在数学上安全,但 PKCS#1 v1.5 的填充方案有预言机攻击;TLS 1.2 的协议设计是安全的,但降级攻击让客户端回退到不安全版本。安全工程师的职责就是消除”理论安全”与”实现安全”之间的鸿沟。


参考#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

密码学攻击案例
https://blog.souloss.com/posts/cryptography/cryptographic-attacks/
作者
Souloss
发布于
2026-04-27
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时