侧信道攻击与防护:密码算法物理实现的安全性实战
前言
1996 年,Paul Kocher 发表了一篇开创性论文,展示了一个令人不安的事实:即使密码算法在数学上被证明是安全的,攻击者仍然可以通过观察算法物理执行过程中的"副作用"来恢复密钥。
这些副作用可能包括:
- 执行时间的微小差异(计时攻击)
- 功耗的波动(功耗分析)
- 电磁辐射(电磁分析)
- CPU 缓存的命中/未命中模式(缓存攻击)
- 声学噪声(声学分析)
2018 年,Spectre 和 Meltdown 漏洞震惊了整个 IT 行业——攻击者利用现代 CPU 的推测执行机制,通过缓存侧信道从其他进程甚至虚拟机中窃取敏感数据。这证明侧信道攻击不仅是智能卡等嵌入式设备的威胁,也是云计算、虚拟化环境中的现实风险。
本文将从基础原理出发,系统介绍主流侧信道攻击技术,然后重点讲解掩码(Masking)和隐藏(Hiding)两大防护策略,并结合国密 GM/T 0028 标准给出工程实践建议。
侧信道攻击的分类
按攻击手段分类
侧信道攻击
├── 被动攻击(Passive)
│ ├── 简单功耗分析(SPA)
│ ├── 差分功耗分析(DPA)
│ ├── 相关功耗分析(CPA)
│ ├── 电磁分析(EMA)
│ └── 缓存攻击(Cache Attack)
│ ├── Flush+Reload
│ ├── Prime+Probe
│ └── Spectre/Meltdown
└── 主动攻击(Active)
├── 差分故障分析(DFA)
├── 时钟毛刺(Clock Glitching)
└── 电压毛刺(Voltage Glitching)按入侵程度分类
| 类型 | 描述 | 典型工具 |
|---|---|---|
| 入侵型 | 需要开盖、去除芯片钝化层 | 探针台、聚焦离子束 |
| 非入侵型 | 无需物理接触 | 示波器、电磁探头 |
| 半入侵型 | 接触芯片表面但不破坏 | 激光注入设备 |
攻击技术详解
1. 计时攻击(Timing Attack)
原理:密码算法的执行时间可能依赖于密钥值。例如,RSA 的模幂运算中,如果指数的某一位为 1,则需要额外的乘法操作。通过精确测量执行时间,攻击者可以逐位恢复密钥。
经典案例:
# 不安全的字符串比较(计时攻击漏洞)
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 constant_time_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 == 0Kocher 在 1996 年的研究表明,通过数千次精确计时测量,可以恢复 RSA 私钥。
2. 差分功耗分析(DPA)
原理:CMOS 电路在处理不同数据时消耗的功耗不同。DPA 通过采集大量功耗迹(power trace),利用统计方法找出功耗与密钥中间值之间的相关性。
DPA 攻击流程:
步骤 1: 采集功耗迹
→ 让密码设备加密 N 个不同明文(N 通常需要 1000-10000)
→ 每次加密时用示波器采集功耗曲线(power trace)
步骤 2: 猜测密钥片段
→ 对密钥的一个片段(通常 8 位,即 256 种可能值)
→ 计算假设中间值
步骤 3: 建立功耗模型
→ 汉明重量模型: HW(x) = x 中 1 的个数
→ 汉明距离模型: HD(x, y) = HW(x ⊕ y)
步骤 4: 统计分析
→ 计算假设中间值与实际功耗的皮尔逊相关系数
→ 相关系数最高的猜测值最可能是正确密钥SM4 的 DPA 攻击实例:
SM4 的非线性变换通过 S 盒实现。攻击者可以针对 S 盒的输出进行 DPA 攻击:
SM4 第一轮:
X1 ⊕ X2 ⊕ X3 ⊕ RK0 → S盒 → 循环左移 → ⊕ X0 = X4
攻击点: S盒输出
→ 猜测 RK0 的低 8 位
→ 计算 S盒输出的假设值
→ 与实际功耗进行相关性分析根据《功耗侧信道分析》(欧长海等,科学出版社,2026)中的实验数据,对未加防护的 SM4 软件实现,约 5000 条功耗迹即可恢复完整密钥。
3. 缓存攻击(Cache Attack)
原理:现代 CPU 使用多级缓存来加速内存访问。当数据在缓存中时(cache hit),访问速度快;不在时(cache miss),需要从主存加载。攻击者通过观察访问时间差异,可以推断数据是否在缓存中。
Flush+Reload 攻击流程:
步骤 1: Attacker 将目标内存行从缓存中清除(clflush)
步骤 2: 等待 Victim 执行密码算法
步骤 3: Attacker 重新访问该内存行,测量访问时间
→ 短时间 = Victim 访问了该地址(cache hit)
→ 长时间 = Victim 未访问该地址(cache miss)
步骤 4: 根据访问模式推断密钥相关的内存访问模式对 AES 的缓存攻击:AES 的软件实现通常使用查找表(T-Table)来加速 S 盒和列混合运算。攻击者通过监控 T-Table 的缓存访问模式,可以恢复轮密钥。
4. 差分故障分析(DFA)
原理:攻击者在密码算法执行过程中引入故障(如电压毛刺、时钟毛刺),导致计算结果出错。通过对比正确密文和故障密文,可以推导出密钥信息。
对 RSA-CRT 的 DFA 攻击:
RSA-CRT 解密: m = m_q + q * ((m_p - m_q) * q^(-1) mod p)
如果故障注入导致 m_p 出错(变为 m_p'),但 m_q 正确:
m' = m_q + q * ((m_p' - m_q) * q^(-1) mod p)
则: gcd(m - m', N) = q
直接分解 N,完全恢复私钥!2003 年,Boneh 等人利用 DFA 成功攻击了未加防护的 RSA-CRT 实现。
防护技术
防护策略总览
侧信道防护
├── 掩码(Masking)
│ ├── 布尔掩码(Boolean Masking)
│ ├── 乘法掩码(Multiplicative Masking)
│ ├── 仿射掩码(Affine Masking)
│ └── 高阶掩码(Higher-Order Masking)
├── 隐藏(Hiding)
│ ├── 时间隐藏(随机延迟、洗牌)
│ ├── 功耗隐藏(恒定功耗逻辑)
│ └── 电磁屏蔽
└── 故障检测(Fault Detection)
├── 冗余计算
├── 一致性校验
└── 传感器检测方案一:掩码(Masking)
核心思想:将敏感中间值拆分为多个随机份额(share),每个份额单独处理,使得任何单条功耗迹都不包含完整的密钥信息。
布尔掩码原理:
对于一个 n 位的中间值 x,将其拆分为 m+1 个份额:
x = x₀ ⊕ x₁ ⊕ x₂ ⊕ ... ⊕ xₘ其中 x₁, ..., xₘ 是随机数,x₀ = x ⊕ x₁ ⊕ ... ⊕ xₘ。
SM4 S 盒的掩码实现(一阶):
import os
def masked_sbox_input(x, mask):
"""对 S 盒输入进行掩码"""
return x ^ mask
def masked_sbox_output(y_masked, mask):
"""对 S 盒输出进行去掩码"""
# 需要构造掩码后的 S 盒查找表
# 对于每个可能的掩码值 m,预计算 S'(x ⊕ m) = S(x) ⊕ m'
pass
# 完整的掩码 SM4 S 盒实现思路:
# 1. 输入掩码: x' = x ⊕ m_in
# 2. 查找预计算的掩码 S 盒: y' = S_box_masked[x']
# 3. 输出掩码: y = y' ⊕ m_out高阶掩码:一阶掩码只能抵抗一阶 DPA 攻击。高阶 DPA 攻击会同时利用多个份额的信息。n 阶掩码需要将所有 n+1 个份额一起处理,复杂度急剧增加。
根据 ISO/IEC 17825 标准,安全等级评估中:
- 一阶攻击:约 1000 条功耗迹
- 二阶攻击:约 100,000 条功耗迹
- 三阶攻击:约 10,000,000 条功耗迹
方案二:隐藏(Hiding)
核心思想:消除功耗/时间与数据之间的相关性,而不是拆分数据。
时间隐藏——随机延迟:
import time
import random
def rsa_modexp_hide(base, exp, mod):
"""带随机延迟的模幂运算"""
result = 1
base = base % mod
# 随机延迟起点
time.sleep(random.uniform(0.001, 0.01))
for bit in bin(exp)[2:]:
result = (result * result) % mod
if bit == '1':
result = (result * base) % mod
# 每轮增加随机延迟
time.sleep(random.uniform(0.0001, 0.001))
return result洗牌(Shuffling):在分组密码的轮函数中,随机打乱 S 盒计算的顺序:
def shuffled_sbox_round(state, round_key, sbox):
"""洗牌 S 盒计算顺序"""
indices = list(range(len(state)))
random.shuffle(indices) # 随机计算顺序
result = list(state)
for i in indices:
result[i] = sbox[state[i] ^ round_key[i]]
return result功耗隐藏——双轨预充电逻辑(WDDL):
在硬件层面,使用特殊的逻辑门设计,确保无论处理 0 还是 1,功耗消耗都相同:
WDDL (Wave Dynamic Differential Logic):
→ 每个周期分为预充电阶段和求值阶段
→ 预充电阶段:所有节点充电到中间电平
→ 求值阶段:根据输入放电到正确电平
→ 每个逻辑门的充放电量恒定,与输入无关方案三:故障注入防护
针对 DFA 等主动攻击,需要在算法层面加入检测机制:
def rsa_crt_sign_with_fault_detection(message, p, q, dp, dq, qinv):
"""带故障检测的 RSA-CRT 签名"""
# 正常 CRT 计算
m_p = pow(message % p, dp, p)
m_q = pow(message % q, dq, q)
h = (qinv * (m_p - m_q)) % p
result = m_q + h * q
# 故障检测:验证签名
if pow(result, e, p * q) != message % (p * q):
raise FaultInjectionDetected("签名验证失败,可能遭遇故障注入攻击")
return resultGM/T 0028 标准中的侧信道要求
GM/T 0028-2014《密码模块安全技术要求》定义了 4 个安全等级,对抗侧信道攻击的要求逐级提升:
| 安全等级 | 侧信道要求 |
|---|---|
| 一级 | 无特殊要求 |
| 二级 | 抵抗简单计时分析 |
| 三级 | 抵抗 SPA 和简单功耗分析 |
| 四级 | 抵抗 DPA/CPA 和高级侧信道分析 |
- 泄漏检测法(Leakage Detection Test)
- 抗攻击能力测试法(Attack Rating Test)
工程实践建议
软件实现侧信道防护清单
- 恒定时间实现:所有密码算法的核心操作必须是恒定时间的
- 随机化:
- 掩码方案:
- 代码审查检查项:
# 检查密钥相关的分支
grep -rn "if.*key" crypto_impl/
# 检查密钥相关的数组索引
grep -rn "sbox\[key" crypto_impl/
# 检查非恒定时间比较
grep -rn "memcmp\|bcmp" crypto_impl/硬件实现侧信道防护清单
- 逻辑设计:
- 物理防护:
- 随机数:
总结
侧信道攻击是密码系统面临的最隐蔽也最危险的威胁之一。攻击者不需要破解数学难题,只需要"观察"密码设备运行时的物理特征就能恢复密钥。
核心防护思路可以总结为两句话:
- 掩码:把秘密拆成多份,让攻击者无法从任何单份中获取信息
- 隐藏:让功耗/时间变得随机,让攻击者无法建立相关性
记住:密码算法的数学安全性只是基础,物理实现的安全性同样重要,甚至更加重要——因为数学上安全的算法,可能因为一行代码写得不对就被攻破。
参考来源
- P. Kocher - Timing Attacks on Implementations of Diffie-Hellman, RSA, DSS, and Other Systems (CRYPTO 1996)
- ISO/IEC 17825 - Testing methods for the mitigation of non-invasive attack classes against cryptographic modules
- GM/T 0028-2014 密码模块安全技术要求
- GB/T 18336 信息技术 安全技术 信息技术安全性评估准则
- 欧长海等 - 功耗侧信道分析,科学出版社,2026
- 中国信通院 - 密码算法侧信道分析技术的发展应用研究
- Let's Encrypt - Ending OCSP Support in 2025
- NIST SP 800-140B - FIPS 140-3 Implementation Guidance