文章目录1. 椭圆曲线群2. 椭圆曲线的加法乘法3. 对称加密和非对称加密公钥加密4. 椭圆曲线数字签名算法ECDSA5. FLUSHRELOAD 攻击5.1 攻击思路5.2 攻击者测量时段的选择6. OpenSSL 实现的 ECDSA有漏洞6.1 OpenSSL 的 ECDSA实现论文攻击的就是它6.2 初读时我的疑问7. 蒙哥马利阶梯8. 攻击者怎么用 FLUSHRELOAD 攻击获取 k8.1 攻击步骤8.2 为什么这样就能恢复出K9. 论文中提到的FLUSHRELOAD 攻击的三个局限性9.1 时间分辨率不够9.2 内存访问与探测重叠9.3 CPU 缓存优化的干扰10. 密钥越小对攻击的抵御能力越强11. 恢复完整的K、攻击的局限性和影响、防御方法12. 结论与未来工作总结补充一些预备知识1. 椭圆曲线群2. 椭圆曲线的加法乘法3. 对称加密和非对称加密公钥加密4. 椭圆曲线数字签名算法ECDSA5. FLUSHRELOAD 攻击5.1 攻击思路第一步flush攻击者先用 clflush 指令清空指定cache行的数据。第二步wait清掉之后如果受害者在攻击者 reload之前访问了同一块物理内存比如执行了那段 if 分支的代码这时CPU就会因为cache未命中而从主存重新加载数据加载过程就会把数据再次放进cache。第三步reload然后攻击者如果再去访问它刚才清空的那个位置因为受害者刚刚把数据加载进了缓存所以如果攻击者读取时速度很快cache命中说明受害者访问过反之如果慢cache未命中说明受害者没访问过。最终攻击者通过“速度快慢”就能知道受害者程序刚才访问了哪块内存OpenSSL 的实现里虽然运算序列固定但内部有一个 if 判断根据密钥的当前比特决定走哪个分支。攻击者通过追踪程序执行时走了哪个分支就能反推出来这个比特值。5.2 攻击者测量时段的选择当受害者的内存访问与攻击者的测量时段重叠时攻击者会错过该次访问。因此增加时段长度会缩短攻击者进行测量的时间占比从而降低访问被错过的概率。受害者的签名运算一直在跑而攻击者的监听是一段一段、断断续续的测完一个窗口要等下一个窗口再测。如果受害者访问内存的时间刚好卡在两个监听窗口的间隙里攻击者就完全抓不到这次访问相当于漏了 k 的一个比特。时间槽越短 → 窗口间隙越多 → 漏抓的概率越高时间槽越长 → 窗口间隙越少 → 漏抓的概率越低另一方面攻击者无法在单个时段内区分对同一内存行的多次访问也无法确定在同一时段内对不同内存行的内存访问顺序。因此延长时段会降低攻击的分辨率。时间槽就像一个「大篮子」你只能知道「篮子里有没有东西」但不知道同一个篮子里东西放了几次比如同一行内存被访问了 2 次你只能看到「有访问」看不到次数篮子里的东西是先放 A 还是先放 B比如两个不同缓存行的访问你分不清先后顺序时间槽越短 → 篮子越小 → 能精准区分每一次访问细节拉满分辨率高时间槽越长 → 篮子越大 → 多次访问混在一起细节全糊了分辨率低由此可见选择时段长度需要在攻击分辨率和内存访问被错过的概率之间做出权衡。6. OpenSSL 实现的 ECDSA有漏洞6.1 OpenSSL 的 ECDSA实现论文攻击的就是它AI调整后加注释的代码// 核心逐位处理 秘密随机数 kfor(;i0;i--){// 取出 k 的一段32位或64位wordscalar-d[i];// 从最高位开始一位一位往下检查maskBN_TBIT;while(mask){// // 最关键一行看 k 的当前位是 0 还是 1// if(wordmask){// ----------------------// k 的当前位 1// ----------------------gf2m_Madd(...);// 点加运算gf2m_Mdouble(...);// 点倍运算}else{// ----------------------// k 的当前位 0// ----------------------gf2m_Madd(...);// 点加运算gf2m_Mdouble(...);// 点倍运算}mask1;// 检查下一位}}6.2 初读时我的疑问这段代码是干嘛的这是在算 ECDSA 签名里最核心的一步计算 k × G攻击者的目标就是获取这个K 恢复ECDSA签名时的随机数从而就可以算出私钥k的类型是什么这里为什么要一比特一比特的逐位处理k 是一个 “超级大整数”大到电脑普通变量存不下。电脑里普通整数int 只有 32 位但 ECDSA 的 k最少 256 位论文摘要部分提到K的长度约为 571 位这么大的数一个变量存不下所以 OpenSSL 用 数组一串小数字 来存它scalar-d[i];// d 是一个unsigned int 数组每一格存 32位为什么要一比特一比特地取因为椭圆曲线乘法 kG 只能按位算没有别的办法每一位代表要不要加这一段 G是 1 → 要加是 0 → 不加初始化结果0循环每一位 结果结果 ×2点倍 如果当前位是1 结果结果G 点加你必须一位一位看 k才能决定这一步要不要做加法mask BN_TBITmask是掩码的意思BN_TBIT是啥BN_TBIT 一个 “最高位是 1、其他全是 0” 的固定数字作为掩码掩码用来一位一位 “扫描” k 的工具怎么做到一位一位扫描呢if (word mask)——按位与有0则0全1才1word是这一轮循环取出的K的当前一位与mask开始为1000…000两者进行按位与之后就取出了K的最高一位从最高位开始然后mask 1右移一位那就变成了0100…000循环上去再进行(word mask)就取到了K 的次高比特位后续也是如此就可以依次取到K的每一位进行判断关键点如果是1那么word mask结果为真执行if对应的分支如果为0执行else进行的分支if分支和else分支里面具体做了什么if 和 else 里面做的事情几乎一模一样唯一区别操作的变量顺序 / 对象换了一下但 —— 它们是两段不同的代码在内存里位置不同 → 这就是漏洞都在做椭圆曲线的点加 点倍数学功能完全一样但 CPU 执行的指令不一样内存地址不一样缓存行不一样psCPU 要执行某一行代码必须先让这段指令加载到 CPU 的缓存Cache中然后 CPU 才能从缓存里读取并执行。如果不在缓存里CPU 会通过硬件自动从内存把它加载到缓存然后才执行。为什么这就是漏洞论文核心k 是 0 还是 1 → 决定 CPU 访问哪一段内存 → 攻击者靠缓存就能看出来k1 → 访问内存 Ak0 → 访问内存 BFLUSHRELOAD 攻击只要看你访问了 A 还是 B就知道 k 这一位是 0 还是 1k 是秘密但代码用 k 的每一位来选择走哪条路等于直接把秘密泄露给了攻击者7. 蒙哥马利阶梯这是计算 kG一个点乘一个数的一种算法特点每轮循环都做相同的操作先加后倍或先倍后加不依赖 k 的比特值理论上抗侧信道攻击计算 kG 最直接的方法是用“倍点-加法”算法但它的操作会根据 k 的比特变化容易被攻击者看出来。蒙哥马利阶梯是一种更安全的算法它不管 k 的比特是0还是1每一轮都执行一次加法和一次倍点操作序列固定。所以从运算时间上看不出区别。但是OpenSSL 的实现里虽然操作序列一样但程序会走不同的 if 分支。这个分支可以被攻击者看到。8. 攻击者怎么用 FLUSHRELOAD 攻击获取 k8.1 攻击步骤第一步FLUSH攻击者先把 if 和 else 两段代码从缓存里强行删掉clflush( 地址A );clflush( 地址B );第二步等待等待受害者签名程序运行代码开始依次处理K的每一位k 某一位是 1 → 跑 if → 把地址 A 加载进缓存k 某一位是 0 → 跑 else → 把地址 B 加载进缓存第三步reload攻击者再去读读地址 A看快不快读地址 B看快不快规则读得快 在缓存里 刚才访问过 走了这条路读得慢 不在缓存里 没走过8.2 为什么这样就能恢复出K根据走的分支不同就可以判断 k 这一位是 0 还是 1情况 1地址 A 快地址 B 慢→ 刚才走了 if → k 这一位 1情况 2地址 A 慢地址 B 快→ 刚才走了 else → k 这一位 0进而就一比特一比特的恢复出K了然后用 k 就可以算出私钥私钥 d (s*k - e) / r私钥直接到手→ 可以无限伪造签名9. 论文中提到的FLUSHRELOAD 攻击的三个局限性9.1 时间分辨率不够CPU 执行指令超级快两次内存访问可能只隔几纳秒FLUSHRELOAD 是周期性探测不是全程盯着如果两次访问挨太近 → 攻击分不清谁先谁后9.2 内存访问与探测重叠即攻击者在 “测量” 的时候刚好受害者在 “访问内存”攻击流程是清缓存FLUSH等待读内存测时间RELOAD如果在第 3 步攻击者正在测的时候受害者刚好在访问同一段内存攻击者的测量会被干扰它会分不清这块内存数据是之前就放到cache中了还是探测的同时受害者访问了而放入的攻击者的结果就会不准甚至漏掉这次访问。9.3 CPU 缓存优化的干扰CPU 有个优化叫 空间预取如果你访问了内存行 ACPU 会自动把相邻的 B 也读进缓存为了跑得快这就会影响到攻击者攻击者本来想监视 A结果 B 也被加载了攻击分不清是程序真的执行了 B还是 CPU 预取的即该攻击不是万能的它会看不清、会漏抓、会被 CPU 干扰。10. 密钥越小对攻击的抵御能力越强密钥越小分组操作的时间越短密钥短→秘密数k位数少→算kG签名核心运算的步骤少、跑的时间飞快。从而迫使时间片更短运算太快攻击者来不及慢悠悠监听只能把监听的时间槽时段缩得特别短。更短的时间片增加了重叠概率时间槽越短攻击者测速、清缓存的操作越容易和受害者的内存访问撞在一起即上面提到的内存访问与探测重叠。进而增加比特位丢失的概率一重叠→测量被干扰→这一位k的 0/1 直接漏抓漏的位一多最后补全密钥的难度就增大了11. 恢复完整的K、攻击的局限性和影响、防御方法完全恢复由于攻击已经恢复了绝大部分比特论文中提到剩下的部分可以通过经典的“大步小步法”在不到1秒内补齐。也就是说攻击者最终能得到完整的k进而算出私钥。局限性定点打击该攻击针对的是OpenSSL的标量乘法实现因此仅适用于使用该实现以及类似Montgomery梯形实现的场景。此实现的漏洞源于秘密随机数的位数决定了程序执行哪条条件分支。影响这种攻击不仅适用于密码软件也可能威胁到其他类型的软件比如通过缓存访问模式泄露用户按键、网络流量等信息。防御方法FLUSHRELOAD攻击利用的是X86架构允许非特权进程使用 clflush 指令清空缓存行的特性如果能限制clflush指令的使用权限可以在一定程度上防御这种攻击。12. 结论与未来工作结论论文研究的结果表明当标量乘法步骤涉及秘密参数时在所有椭圆曲线协议的实现中都应避免使用 OpenSSL 的蒙哥马利梯级实现。密钥越短越安全建议避免使用这种实现正如NaCl 库不依赖分支的恒定时间算法采用的那种方法未来工作将攻击扩展到其他密码算法研究对非密码软件的威胁评估这种攻击对隐私的实际影响总结这篇论文干了什么用FLUSHRELOAD攻击OpenSSL的ECDSA签名恢复了随机数k拿到了私钥为什么能成功因为OpenSSL的实现里k的每个比特会影响程序走哪个if分支分支对应不同内存地址FLUSHRELOAD能探测到我知道了这种缓存侧信道攻击是怎么做的后面我是要研究如何防御这种攻击的