侧信道:从能量轨迹里读比特
一道模板攻击题的完整拆解——欧几里得距离、阈值判断、比特还原。出题和解题是同一件事的两面。
侧信道在考什么
正常密码学题目,给你密文和算法,让你破解。侧信道题目反过来:算法可能是安全的,但你拿到了运算过程泄露的物理信息。
功耗、时间、电磁辐射、声波、缓存命中率——这些都算侧信道。CTF 里最常见的是功耗轨迹,因为它容易用程序模拟。
四种分析方法
| 方法 | 思路 | 需要什么 |
|---|---|---|
| SPA | 看单条轨迹的形状,直接读操作 | 对设备内部实现有了解 |
| DPA | 多条轨迹做差分统计,从噪声里挖信号 | 大量轨迹 |
| CPA | 建功耗假设模型,算相关系数 | 功耗模型 + 轨迹 |
| 模板攻击 | 事先建好「0 的轨迹」和「1 的轨迹」,比对相似度 | 同型号设备 + 模板 |
CTF 里出得最多的是模板攻击,因为它可以完全用 numpy 造出来,不需要真实硬件。
题目长什么样
给五个附件:
energy_traces_with_flag.npy # 嵌了 flag 的能量轨迹
energy_traces_with_flag.png # 同上的可视化
template_trace_0.npy # 比特 0 的模板
template_trace_1.npy # 比特 1 的模板
flag 被转成二进制位串,每个位选一条模板,加噪声,拼成一条长轨迹。
解题要做的,就是把这条长轨迹切回一个个比特。
解题:三步
第一步,切分。模板轨迹的长度就是单比特的长度。长轨迹按这个长度等分,得到 N 段。
第二步,判距离。每段分别算到 template_0 和 template_1 的欧几里得距离:
import numpy as np
t0 = np.load('template_trace_0.npy')
t1 = np.load('template_trace_1.npy')
traces = np.load('energy_traces_with_flag.npy')
seg = len(t0)
bits = []
for i in range(0, len(traces), seg):
chunk = traces[i:i+seg]
d0 = np.linalg.norm(chunk - t0)
d1 = np.linalg.norm(chunk - t1)
bits.append('0' if d0 < d1 else '1')
第三步,转字符。8 位一组,转 ASCII:
flag = ''.join(chr(int(''.join(bits[i:i+8]), 2)) for i in range(0, len(bits), 8))
三步写完不到二十行。这道题的难点不在代码,在于想明白「为什么距离能判比特」——噪声是加性的,模板是固定的,加了噪声的轨迹离原模板最近。
出题视角
自己出这类题,要注意三件事:
噪声别太小。噪声为零,题目变成送分。噪声太大,模板匹配失效。噪声幅度取模板幅度的 10%–20%,既需要判断,又能算出来。
模板别做成常数。如果 template_0 全是 0、template_1 全是 1,那不用算距离,看一眼就知道。模板要有形状,且两条形状要像——像到必须算距离才能分。
提示要给够,但别给答案。给模板文件、给轨迹文件、给一个「怎么转换」的函数骨架,就是不给判断方法。这是 CTF 出题的基本分寸。
声学侧信道
把「功耗」换成「声音」,思路完全一样。
真实世界里的经典案例是:用麦克风录键盘敲击声,做频谱分析,按每个键的声学特征分类,还原输入内容。笔记本风扇转速、CPU 负载引起的主板振动,也能泄露运算强度。
CTF 里的实现通常是给一段音频,让选手做短时傅里叶变换(STFT),看频谱图里的峰值分布,按峰值位置分帧、聚类,映射回字符。
判断顺序和能量轨迹一样:先确定单符号的时间长度,再切分,再比对。
参考
- 《CTF 密码学之侧信道攻击——声学攻击出题思路》,公众号 攻防SRC
- 《CTF 密码学之侧信道攻击——声学攻击出题指南》,公众号 攻防SRC