我第一次把示波器探头接到正在跑AES-128加密的板子上时,说实话挺失望的。屏幕上是一条巨大而重复的波形,每一轮加密看起来都长得差不多,完全没有想象中“密钥呼之欲出”的戏剧感。后来我才真正理解,侧信道功耗分析(Side-Channel Power Analysis)这件事,从来就不是靠肉眼盯着波形来拿密钥的,而是靠统计学的力量,把藏在噪声和宏大体量信号里那些极其微弱的物理相关性,一点点挖出来。
这篇文章我就从一次完整的CPA(相关功耗分析)攻击链路讲起,把功耗模型、采集环境、相关系数计算、常见坑、以及防御方视角都铺开聊一遍。适合正在做芯片安全评估的工程师、嵌入式安全研究员、密码学实现相关开发,以及想入门硬件安全但对数学细节发怵的学生。文章里的所有参数和流程都是我在真实实验环境里验证过的,你可以直接照着搭一套自己的分析平台。
1. 功耗分析到底是什么:一次完整攻击链路的基本构成
1.1 从SPA到DPA/CPA:为什么“看得见”的泄露反而藏在平均里
很多第一次接触侧信道的人,最先听说的是SPA(简单功耗分析),也就是直接看单条功耗曲线的形态差异。比如某些密码实现里,如果密钥位是1就执行乘法,是0就跳过,那功耗曲线上肉眼可见会多出一块高功耗区域——这属于最简单的“看得见”的泄露。
但现实中的目标基本都做了或多或少的防护,哪怕没做防护,现代MCU主频动辄几十上百MHz,单条指令功耗窗口只有几十纳秒,就算是SPA,也需要在波形细节上有非常深的经验才能读出东西。而DPA(差分功耗分析)和它的进阶版CPA,做法完全不同:我不需要分析单条曲线的形态,而是采集成百上千条曲线,按照某个假设把曲线分成两组或多组,再比较组间功耗差异的统计显著性。
CPA更直接一点,它不对曲线做分组,而是为每个密钥候选计算一个“假设功耗值”,然后计算这个假设功耗和所有实测曲线每个时间点采样值之间的皮尔逊相关系数。相关系数出现峰值的位置,往往就是泄露发生的时间点,而对应峰值最高的那个密钥候选,就是正确的密钥字节。
这套思路的根本前提是:芯片在某个时钟沿翻转的晶体管数量越多,从电源吸取的动态电流就越大。这个电流变化经过测量电阻或电流探头转换成电压信号后,就隐藏在采集到的整条功耗曲线里。CPA做的,就是把“某个中间值与功耗之间的线性关系”从大量噪声中提取出来。
1.2 攻击者的物理前提:可控制明文、可测量功耗、密钥固定
要复现CPA,需要满足四个基本条件,缺一个链路都走不通。
第一,攻击者能控制或至少能观察输入明文。在大多数使用场景里这是成立的——数据要发给设备加密,报文内容本来就是攻击者可以构造的。如果是解密方向,就需要能控制密文,原理一样。
第二,设备在加密过程中使用的密钥是固定的。这听起来是废话,但实际操作中很多目标设备会做密钥切换、会话密钥协商、随机数过程混入等,如果你采集的窗口跨越了密钥变化,后续相关性分析就会崩。所以正式实验前,我会先固定一个测试密钥,或者把采集窗口对准单次加密内固定密钥参与的那一段。
第三,攻击者能同步采集功耗。这是工程上最麻烦的一环。板子需要引出电源测量点,或者能放一个低阻采样电阻在供电回路上,又或者能用电磁近场探头贴近芯片表面。同步指的是触发信号的时间基准要稳定,否则每条曲线的相位都对不齐,相关性就被时间抖动抹掉了。
第四,攻击者知道目标算法结构。CPA不是黑盒暴力破解,它需要知道算法内部在哪个位置会产生一个同时依赖明文和密钥的中间值。AES-128的经典目标就是第一轮SubBytes的输出。如果你对算法结构不清楚,就算采集了十万条曲线,也不知道该对哪个中间值建模。
1.3 为什么默认目标是AES-128第一轮SubBytes输出
选第一轮SubBytes输出,有两层原因。
第一层是数学上的:AES第一轮里,每个字节 Sbox(p⊕k) 同时依赖一个明文字节和对应的一个密钥字节,它们是一一对应的关系。我每次猜一个密钥字节,就能算出一个假设中间值,再把256个假设和实测功耗做相关性比较。这个搜索空间只有256,一台普通电脑几分钟内就能遍历完。相比之下,如果要对完整轮函数建模,中间值包含多个字节之间的线性扩散关系,假设空间会变成天文数字,基本没法做。
第二层是工程上的:第一轮SubBytes在整个AES执行流程里位于最前端,此时数据总线上的活动最“干净”——没有大量中间状态的累积,也没有后续轮次复杂运算的干扰。现代MCU里,同一块数据总线要服务内存读写、外设、中断,越靠后的轮次,泄漏叠加越混乱。第一轮的Sbox查找表操作通常是一个明确的访存序列或查表指令,功耗特征明显,触发窗口也容易对准。
选定了这个中间值,后续的所有分析就是围绕 Sbox(p⊕k) 展开的假设建模。
2. 建链路之前必须理解的底层功课:功耗模型与中间值设计
2.1 CMOS翻转与动态功耗:功耗和“变化”绑定
很多人以为芯片功耗就是“运行时的静态电流”,但在分析侧信道时,最有价值的恰恰是动态功耗。CMOS电路里,静态功耗主要来自漏电流,和数据处理几乎无关,而动态功耗来自逻辑门输出电容的充放电。
一个CMOS反相器的输出从0变为1,或从1变为0,都会对负载电容充电或放电,这个过程的能量消耗近似为:
P_dynamic = α · C · V² · f
其中α是翻转活动因子,即每个时钟周期内发生翻转的逻辑门占的比例。这个公式里C是负载电容、V是核心电压、f是时钟频率。对侧信道分析来说,最关键的是α——它直接由当前正在处理的数据决定。
也就是说,芯片在一个时钟周期里翻转的晶体管数量越多,从电源引脚抽取的动态电流的瞬态尖峰就越大。这个电流尖峰在经过测量电阻或电感时,就会呈现为一个电压尖峰。数据中的每一位对翻转数量的贡献是线性的,而CPA的线性相关性恰好能抓住这种关系。
这也是为什么功耗模型都围绕“翻转了多少个比特”来构建,而不是围绕“当前电平是什么”。电平本身只反映静态状态,翻转才反映瞬态活动,而示波器采到的功耗尖峰本质上是瞬态活动的累积结果。
2.2 汉明重量与汉明距离:用哪种模型更贴近真实
CPA建模时最常用的两个概念是汉明重量(Hamming Weight,HW)和汉明距离(Hamming Distance,HD)。
HW是计算一个二进制数里“1”的个数。比如 0xA5 的二进制是 10100101,HW就是4。对于8位MCU来说,每次数据总线上的值无论是0x00还是0xFF,最终体现到功耗上的,往往取决于这个值所对应的总线上“驱动为1”的线的数量。很多情况下,芯片数据总线上的每一位都有一个上拉或下拉结构,值为1的位会导通相应的晶体管,总体上HW与功耗呈正相关。
HD则衡量两个连续状态之间变化的位数。比如数据总线先是0xA5,下一个周期变成0x5A,两者异或后是0xFF,HD就是8。HD模型更精细,它假设功耗正比于两个状态之间的翻转数量。这在寄存器、总线、存储器接口上更贴近物理事实。
实际选择哪一个模型,要看目标设备的微架构。我做过一个小实验:同一块板子,用HW模型对Sbox输出建模,相关系数峰值只有0.02左右;换成HD模型,也就是把上一周期的状态考虑进去后,峰值直接涨到0.07以上。但也有反过来的场景——某些智能卡芯片在数据总线上做了预充电逻辑,每个周期总线都会被预先重置为0,这时候HD退化成了HW,用HW反而更合适。
所以在没有芯片手册的情况下,建议两个模型都跑一遍,哪个峰值显著就用哪个。这在工程上是几行代码的事,不要一开始就押宝在一个模型上。
2.3 选择SubBytes输出作为中间值的原因
中间值必须满足两个条件:一是在某个时刻被芯片真实处理过,二是同时依赖于已知变量(明文/密文)和未知变量(密钥)。
我前面已经提过,第一轮SubBytes输出 Sbox(p⊕k) 是这两个条件的完美交集。但这里还有个容易被忽略的细节:Sbox输出之后,紧接着是ShiftRows和MixColumns操作。MixColumns把四个字节做矩阵乘法混合,如果我选MixColumns输出作为中间值,理论上也能做,但那个输出的每个字节会同时依赖四个明文字节和四个密钥字节,假设密钥空间会变成 2^32 甚至更大,普通穷举根本不现实。
选Sbox输出还有一个好处:很多软实现里,Sbox是一张256字节的查找表,查表操作是“从内存地址取数据”的过程。这个过程的功耗不仅取决于查表结果,还取决于查表地址本身。查表地址是 p⊕k,也是一个同时依赖明文和密钥的值。于是有些实现里,即使我不建模查表结果,只建模查表地址的HW,也能拿到相关性。这说明Sbox这一步的物理泄漏往往是双重的,成功概率更高。
还有一点值得注意:对于第一个字节,你可以选择“用正确密钥算出来的假设功耗”和“用错误密钥算出来的假设功耗”做个对比。正确假设和实测功耗之间应该存在明显相关性,而错误假设的相关性分布应该趋近于零。这个对比是整个CPA的统计学校验基础。
3. 采集环境搭建中的关键取舍:从探头到触发的一整套工程问题
3.1 电流探头 vs 串阻取样 vs 近场耦合:三种方式的取舍
采集功耗信号,实际上是在测量芯片消耗的动态电流。电流本身没法直接被示波器读取,需要转换成电压。工程上有三种主流方式,各有适用场景。
第一种是电流探头,比如Tektronix TCP0030或Keysight N2783B这类高频电流探头。它的优点是非侵入,直接夹在供电线缆上就能测,不会改变电路工作点。缺点是贵、体积大,而且带宽通常到几百MHz就到头了,对纳秒级的瞬态电流尖峰响应有限。适合做低频段的整体功耗轮廓观察,或者对精度要求不高的验证。
第二种是串阻取样,也是我做实验最常用的方式。在芯片电源引脚或开发板供电回路里串联一个1欧姆到10欧姆的低感电阻,用示波器差分探头或普通探头测电阻两端的压降。压降等于电流乘以电阻值,所以波形直接反映了电流变化。好处是成本低、带宽高、信号幅度可控;坏处是会引入额外的压降,对低电压芯片(比如1.2V内核供电)可能影响供电稳定性,需要确认压降不超过几十毫伏。
选择串阻值有个经验法则:先看静态电流。如果芯片正常工作电流是20mA,用10欧姆电阻会产生200mV压降,这个压降已经可能把核心电压拉低,导致芯片工作异常。正确的做法是用1欧姆甚至0.5欧姆,先看静态压降是否在可接受范围内,再决定是否增大阻值。
第三种是近场电磁耦合,用一个直径几毫米的线圈贴近芯片表面或电源走线,感应磁场变化。这种方式完全无接触,适合没法改硬件的黑盒评估场景,但信号幅度小、噪声大,而且受探头位置影响极大,需要非常精密的机械固定才能稳定复现。
3.2 示波器参数:采样率、带宽、存储深度怎么定
采集环节最容易犯的错误,是按“看波形”的标准来设置示波器。如果只是观察整体功耗轮廓,1MS/s采样率就够了,但做CPA远远不够。
先算一笔账:假设目标MCU主频是16MHz,一个Sbox查表操作可能持续4个时钟周期,也就是250ns。如果采样率是2GS/s,在这250ns窗口里能采到500个点,足够捕捉到功耗尖峰的形状;如果采样率降到100MS/s,只能采到25个点,很容易就把尖峰给漏掉了。
我自己常用的起点是2GS/s采样率,带宽选500MHz以上。带宽太高也没必要,因为功耗信号本身是低频为主的,过高带宽反而会把高频开关噪声一起采进来,压低信噪比。如果示波器支持带宽限制,我会开启20MHz或100MHz的低通滤波选项,先把带外噪声滤掉。
存储深度取决于你要采多长的窗口。一次AES-128运算在16MHz MCU上大约需要几百微秒,在2GS/s采样率下就是几百万个点,示波器存储深度至少要10Mpts以上才不会导致采样率被自动拉低。如果存储深度不够,示波器会偷偷降低采样率来保持窗口长度,这是很多“莫名采集不到有效信号”的罪魁祸首。
更精细的做法是用分段存储或仅记录目标窗口。先抓一段完整的AES加密过程,找到第一轮SubBytes发生的时间窗,然后把示波器时基缩小到只采这个窗口周围的2到5微秒,这样在同样存储深度下能显著提高有效采样率。
3.3 触发信号:为什么它决定分析成败
触发是整个采集链路里我认为最容易被低估的一环。CPA分析要求每条曲线的起始时间点对齐到同一个算法执行阶段的起始时刻,如果触发抖动超过几十纳秒,后续的相关性计算就会被时间抖动“稀释”,峰值会显著下降甚至消失。
最可靠的触发源是目标板上的一个GPIO引脚,在加密开始前由固件主动拉高、结束后拉低。我在测试固件里一般直接加一段代码,在AES函数入口处写一个IO寄存器拉高,在函数出口处拉低。这个IO边沿作为示波器的触发源,就能保证每条曲线从同一个指令边界开始采集。
如果没有办法改固件,次优方案是用芯片的某个总线上固有的电平跳变来触发,比如SPI片选信号的下降沿。但要注意,实际加密运算可能在命令接收完成之后的几百微秒才开始,触发沿和功耗信号之间的延迟会不稳定,需要留足预触发深度,并在后处理时做对齐。
还有一种情况是目标板上有实时操作系统或中断系统,AES函数执行过程里可能被中断打断。这种场景下必须用触发信号把窗口精确限定在AES的执行区间内,否则采集到的窗口里有大量无关功耗片段,严重影响相关性。
3.4 波形预处理:对齐、平均、滤波到底要不要做
采集完成后,不要急着丢进分析脚本,先把波形质量检查一遍。
第一步是可视化检查。把几十条曲线叠加显示,如果同一时刻的波形包络很宽,说明时间对齐有问题。时间对齐问题可能来自触发抖动、也可能是内部RC时钟的温漂。此时需要用互相关或特征点识别的方法把所有曲线对齐到同一个参考波形上,再做后续分析。
第二步是降噪。如果单条曲线的噪声太大,可以对同一个输入明文重复加密多次、取平均。但这里有个陷阱:如果密钥固定,重复加密同一条明文能有效降低随机噪声;如果明文本身在每次加密时不同,就不能直接平均。实际攻击中一般是采集大量不同明文各一条曲线,此时平均策略不适用,需要依靠更大的样本量来让随机噪声在统计上互相抵消。
第三步是滤波。我会用一个截止频率合适的FIR低通滤波器,把高于芯片时钟频率的毛刺滤掉。但要注意滤波不能做得太狠,否则会把功耗尖峰本身也抹平。经验是截止频率设为时钟频率的3到5倍比较稳妥,然后对比滤波前后的相关峰值来决定是否保留。
预处理做完后,最终的曲线集就是CPA分析的输入了。预处理本身不会制造信息,但能让信息更容易被提取出来——这一点和拍照后修图是一个道理,修图不能凭空造出细节,但能让细节更清楚。
4. CPA分析核心计算过程:相关系数怎么一步步把密钥挖出来
4.1 构造假设功耗矩阵
假设已经采集了N条功耗曲线,每条曲线有T个采样点,它们组成一个 N×T 的实测功耗矩阵 P。现在要对每个密钥候选 k(0到255)构造一个假设功耗向量。
具体流程是这样:对第 i 条曲线对应的明文字节 p_i,计算中间值:
m_i,k = Sbox(p_i ⊕ k)
然后计算中间值的汉明重量:
h_i,k = HW(m_i,k)
这里的 h_i,k 就是一个标量,代表“如果密钥是k,那么芯片在处理这条明文时,Sbox输出到总线上的数据应该有这么多位是1”。遍历所有N条曲线后,对每个k都得到一个长度为N的假设功耗向量 H_k。最终得到一个 256×N 的假设功耗矩阵。
向量长度N就是样本量。在实际攻击里,N的取值和信噪比直接相关。理想无噪声环境下,几百条曲线就够;真实环境有噪声、有测量误差、有非线性因素,通常要几千到几万条。
构造矩阵的时候要注意,计算Sbox要按目标设备的实际实现来。如果目标跑的是表查找,就按标准Sbox表算;如果目标跑的是bitslice实现,那HW模型可能不再适用,需要换用HD模型或者基于比特的模型,否则相关性会非常弱。
4.2 皮尔逊相关系数的实际计算
有了实测矩阵 P 和假设功耗矩阵 H,CPA的核心就是计算皮尔逊相关系数。对于第 j 个采样时间点,第 k 个密钥候选,相关系数定义为:
r_k,j = cov(H_k, P_j) / (σ(H_k) · σ(P_j))
其中 cov 是协方差,σ 是标准差。这个式子的直觉理解是:如果假设功耗和实测功耗出现了“同涨同落”的线性关系,那么r的绝对值就会远离零。如果假设密钥错了,H_k 和 P_j 之间就没有这种同步关系,r就会在零附近随机波动。
实际操作时,不要对每个(k,j)组合都用原始公式循环计算,那样256×T次协方差计算会慢到怀疑人生。我一般先用矩阵运算把H和P中心化,然后用向量内积一次算出所有组合的相关矩阵。对256个候选和几万条曲线、几千个采样点,Python里用NumPy一次矩阵乘法就能在几十秒内算完。
算完之后,每个密钥候选对应一条相关系数曲线。正确密钥候选的那条曲线应该在某段时间窗内出现明显峰值,错误密钥候选的曲线基本是一条贴近零的噪声带。如果把256条曲线画在同一张图上,那张明显“伸出来”的曲线所在的候选,就是答案。
为了验证稳健性,我还会记录每个候选的全局最大相关系数绝对值,按降序排列。正确密钥至少要进入前几名,更理想的情况是遥遥领先。如果正确密钥排不到第一,说明数据质量或模型选择有问题,需要回头检查。
4.3 用曲线数量观察相关性收敛:实验验证和阈值
CPA分析最需要耐心的地方在于确定“采多少条曲线足够”。我不建议一开始就猛采几万条,而是应该从少量开始,逐步追加,观察相关性收敛的情况。
具体做法是:先分析1000条曲线,记录正确密钥候选的相关系数峰值;然后增加到2000条、5000条、10000条,分别记录。你会看到正确密钥的峰值逐渐上升并趋于稳定,而错误密钥候选的相关系数最大值基本保持在一个低位噪声水平。
我印象很深的一次实验里,3000条时正确密钥候选的峰值只有0.03,完全淹没在错误候选里;加到8000条时峰值涨到0.09,勉强能分辨;加到15000条时峰值到0.12,正确候选已经断层领先。峰值绝对值看起来很小,但统计学上p值已经低于10⁻⁶,完全可信。
阈值判断上,经验法则是:正确密钥候选的相关系数峰值至少要是错误候选最大值的3倍以上,才算是“可信恢复”。如果只有1.5倍,很可能需要补采曲线或调整预处理。有一种快速校验方法,是把正确密钥的中间值做一次随机置换,再重新计算相关系数,如果置换后峰值不下降,说明之前的峰是巧合,不是真实泄漏。
4.4 常见失败模式:为什么有时候峰值不出来
我见过太多人卡在“跑了完整流程但相关系数没有明显峰”这一步,几乎都是几个固定原因。
第一个原因是触发对齐问题。前面说过,时间抖动会让相关性被拉平。这种情况在波形叠加图上就能看出来,对齐后重跑一遍就能解决。
第二个原因是采样窗口没对准。如果只采集了整个AES的前半段,但Sbox操作发生在触发之后很久,或者被中断子程序挤到了窗口外,那么相关性自然为零。遇到这种情况,我会用SPA先看一遍曲线的整体结构,找到一轮AES大概在什么位置,再重新设置采集窗口。
第三个原因是模型选错。有的芯片数据总线有预充电逻辑,HW模型不再适用;有的Sbox是通过查表实现的,查表地址比查表结果泄露更大。如果HW和HD模型都没有峰,我会换一个中间值构造方式,比如把Sbox输入 p⊕k 直接作为建模对象,看有没有相关。
第四个原因是样本量不足。噪声大、信噪比低的环境下,几千条曲线可能远远不够。这时候不要纠结算法,回到采集端去改善信噪比,比如优化探头位置、降低供电噪声、提高采样率,比盲目堆更多曲线更高效。
5. 防御方的视角:掩码、隐藏和泄漏评估的工程实践
5.1 掩码:让中间值与密钥的一阶关系断开
要做安全的密码实现,第一条防线就是掩码。掩码的核心思想很简单:让实际计算过程中的中间值被一个随机数“覆盖”掉,使得攻击者观察到功耗与真实中间值之间不再存在固定的映射关系。
具体到AES的第一轮SubBytes,普通实现计算 m = Sbox(p⊕k)。掩码方案的思路是:先生成一个随机掩码 M,实际计算时先算 m_masked = Sbox(p⊕k) ⊕ M,或者更精细地把Sbox输入也掩码。这样在实际数据总线上流动的是带掩码的值,其与密钥k之间的关系被M随机化掉了。
如果攻击者不知道每条曲线对应的M值,他的假设功耗就是基于真实中间值 HW(Sbox(p⊕k)) 构建的,而这个假设与实际处理的数据之间的相关性会因为掩码的随机性而趋近于零。这是最简单的布尔掩码,它能有效抵抗一阶CPA。
但掩码不是免费的午餐。软实现里引入随机数生成器、Sbox的表要重新构造(掩码Sbox表或拆分Sbox表),运行时间和存储器开销都会显著增加。硬件实现里还要考虑毛刺效应——即使逻辑上引入了掩码,物理上的glitch仍可能泄露部分数据。我在评估一个带掩码的智能卡时发现,一阶统计确实压得非常好,相关性峰值降到噪声水平,但用稍高功耗的统计模型仍然能在特定时钟沿看到残留泄漏。
想彻底防住一阶功耗分析,需要的是“掩码+隐藏”的综合方案,而不是单点防御。
5.2 隐藏技术:把信号噪声化、随机化、均衡化
如果说掩码是“让相关性消失”,隐藏就是“让信号更难被提取”。隐藏的思路有两条线:一是随机打乱操作顺序,二是让功耗曲线变得“平坦”。
随机打乱操作顺序,也被称为Shuffling。比如AES里16个字节的Sbox操作本来顺序执行,如果每次都随机洗牌,攻击者的假设功耗和实际功耗之间的时间对应关系就被打乱了。这个方法的成本比掩码低很多,只需要一个随机数和一个洗牌逻辑,但对时间窗非常窄的攻击仍有回旋余地,并不能完全消除泄漏。
第二类方法是对功耗本身做均衡。常见手段包括在计算的同时让额外的逻辑单元做等量翻转,使总体功耗对不同数据保持近似恒定。还有硬件上的双轨逻辑(如WDDL),让每个逻辑门同时产生原信号和互补信号,无论数据是0还是1,翻转数量都一致。这类方案对硬件设计的改动极大,面积和功耗开销接近翻倍,主要用在支付芯片等高安全等级场景。
软件层面,还有一类实用的隐藏手段是“伪指令填充”——在加密执行的关键路径里插入大量与数据无关的运算,让Sbox操作的功耗尖峰被淹没在随机计算的背景活动里。这个方法成本低,可以在固件层实现,但效果不如硬件方案稳定,只适合做辅助防御。
5.3 泄漏评估:TVLA和t检验的用法
做防御方评估,最怕的是自认为安全但实际上漏洞百出。TC-SCART和TVLA这类方法,就是为了回答一个更客观的问题:这条功耗曲线里到底有没有可检测的统计差异,而不需要知道密钥是什么。
TVLA(Test Vector Leakage Assessment)的核心工具是Welch的t检验。操作上,采集两组功耗曲线:一组使用固定明文,另一组使用随机明文,各采集几千条。对每个采样时间点计算一个t统计量:
t = (μ₁ - μ₂) / sqrt(s₁²/n₁ + s₂²/n₂)
其中μ、s²、n分别是两组曲线在该时间点的均值、方差和样本量。如果两组数据的均值在某个时间点有显著差异,t值就会冲出阈值±4.5。这个阈值来源于统计学上“极不可能由随机噪声产生”的经验值,被很多安全评估标准广泛采纳。
我在实际评估里会用TVLA跑一遍所有采集到的数据,画出每个时间点的t值曲线。如果t值在某个区间内反复超出±4.5,说明该算法实现存在可检测的数据相关泄漏,这时候再回去做CPA定位具体泄露点,效率会高很多。
需要强调的是,TVLA通过不代表绝对安全,只代表当前样本量和测量条件下没有检测到一阶泄漏。高阶泄漏、组合通道泄漏、特定输入模式下的泄漏,都可能绕过简单的TVLA。但作为快速筛查工具,它足够有用了:如果在TVLA阶段就是一片红,那后面的密码实现大概率直接判负,根本没有精细化评估的必要。
6. 我在多次实操中踩过的坑:给后来者的实在建议
6.1 工程环境对结果的影响远大于分析算法
如果你调了一整天CPA算法,相关性还是没有任何峰值,先怀疑的不是代码,而是你的测量环境。
我踩过最深的一个坑,是目标板的电源去耦电容。有一块评估板上,内核电源附近焊了两个10uF陶瓷电容,我直接在板子供电入口处串联采样电阻测功耗,结果曲线平滑得像心电图,任何相关性都测不出来。原因是那两个10uF电容把功耗突刺全“吸收”了,示波器看到的只是电源的缓慢波动。后来我把采样点移到电容之后、芯片电源引脚之前,放在一个几乎靠近VDD引脚的位置,信号立刻变得有“毛刺感”,CPA马上就有了峰。
类似的经验还有:用USB供电的板子,纹波噪声会直接叠在功耗信号上,导致信噪比恶化。如果条件允许,给目标板用线性电源单独供电,或者用电池供电,结果会好很多。我甚至遇到过,把示波器探头的地线夹子换成极短的地弹簧后,相关系数峰值从0.04涨到0.08,效果立竿见影。
这些细节没人会在教科书里提醒你,但它们往往比特调分析算法更关键。
6.2 数据分析中的伪相关陷阱
数据分析阶段,伪相关是个很大的坑。我曾经在只有300条曲线的小样本集上跑CPA,结果256个候选里有十几个相关系数都超过了0.1,完全分不清谁对谁错。后来把样本量增加到3000条,那些伪峰才纷纷降回噪声水平。
这个小样本伪相关的原理很简单:样本量N很小时,皮尔逊相关系数的方差很大,即使两个完全不相关的随机向量,也可能碰巧算出很高的相关系数。解决办法一是保证样本量足够,二是对比错误候选的相关系数分布。正确候选的峰值应该是孤立的,如果一堆候选都有类似高度的峰,大概率是伪相关。
另一种伪相关来自操作系统的调度或中断干扰。如果目标板在执行AES期间被系统节拍中断打断,并且在固定的相对时间点执行中断处理,那么所有功耗曲线在同一个时间窗都会多出一段无关但重复的功耗活动——这本身就会造成明显的统计差异,但它不代表密钥泄漏。判断方法是在分析窗口外跑一次TVLA,如果在非密码运算区间出现了大t值,说明存在系统性的时基噪声,需要重新设计触发窗口或分析范围。
6.3 如何快速验证自己的攻击流程是可信的
每次写完一整套CPA流程,我都会先做一个“对照组”实验,用来验证工具链本身没有问题。
方法是:用同样的明文数据集,但把密钥换成一个已知的测试密钥跑一遍,观察正确候选是否被恢复;然后把密钥解析逻辑故意改错一位,确认相关性分析结果是否会随之变化。如果正确密钥能稳定恢复,再刻意改变密钥后能稳定地恢复出另一个“错误”密钥,说明整个链路从采集到分析都是可信的。
还有一个快速小技巧:只在分析里使用其中一部分曲线,比如只取前100条,看看结果是否稳定。如果100条就能恢复出正确密钥,说明信噪比非常好;如果10000条仍然恢复不出来,问题一定出在前面采集或预处理的某个环节。这个递进式验证帮我节省了大量排查时间,建议头一次搭平台的人从这一步开始。
最后我个人的一点体会是:侧信道功耗分析这个方向,门槛不在于数学公式,而在于把“电磁学、微电子、密码学、信号处理”这几块知识拼在同一个实验台前,并且愿意为了一条干净的曲线折腾好几个晚上。只要你有耐心把采集环境一步步调稳,让正确密钥候选的相关系数从噪声里浮现出来的那一瞬间,你会觉得之前所有的折腾都值了。接下来无论是继续深入对齐攻击、模板攻击,还是转向防御侧的掩码方案设计与评估,这套基础的“看得见功耗”的能力,都是你所有后续工作的基石。