maliibun/side-channel-demo

GitHub: maliibun/side-channel-demo

一个基于浏览器的侧信道攻击交互式演示工具,涵盖计时攻击和功率分析,无需任何硬件即可直观展示密钥提取过程。

Stars: 0 | Forks: 0

side-channel-demo

基于浏览器的侧信道攻击演示 —— 从时间和功率轨迹中提取密钥,无需任何硬件。
包含幻灯片、现场演示和详细说明。


目录

  1. 攻击方式
  2. 架构
  3. 文件计划
  4. 注意事项
  5. 构建顺序

1. 攻击方式

1a. 计时攻击 (现场演示)

漏洞模式是带有提前退出的逐字节字符串比较:

function naiveCompare(a, b) {

  if (a.length !== b.length) return false;

  for (let i = 0; i < a.length; i++) {

    if (a[i] !== b[i]) return false;   // 提前退出泄露了位置

  }

  return true;

如果你猜错了第一个字节,循环在 1 次迭代后退出。如果第一个字节猜对了但第二个猜错了,它会在 2 次迭代后退出。响应时间 ≈ 匹配前缀的长度。这就是信息泄露点。

恢复算法 —— 逐字节

  1. 将字节 0..i-1 固定为你已经恢复的前缀。
  2. 对于位置 i 处的每个候选值 c ∈ 0..255,向服务器发送 prefix || c || padding N 次,记录服务器端的处理时间。
  3. 具有最高中位数时间的候选值就是正确的字节。(使用中位数而非平均值,因为来自 GC/调度器的异常值会主导平均值。)
  4. 将其追加到前缀中;对 i+1 重复此过程。
现实检验:在互联网上针对 hmac.compare 的真实网络计时攻击需要数百万个样本。在 30 秒的演示中,你无法诚实地做到这一点。有两种选择:
  • 放大信号:在服务器端为每个匹配的字节添加一个故意的 busyWait(50µs)。告诉观众:“这放大了真实的影响;如果没有它,你需要多 10,000 倍的样本。”
  • 仅限 Localhost:在没有网络抖动的情况下,即使不放大,只需每个字节几百个样本就能恢复泄露的信息。
建议:两者都用 —— 使用一个滑块来设置放大系数,这样你就可以展示 关闭 → 噪声 → 开启 → 瞬间恢复 的过程。

1b. 模拟功率轨迹 (CPA)

CMOS 泄露模型:加载了值 v 的寄存器所消耗的功率大致与 popcount(v)(即汉明重量,Hamming Weight)成正比。对于 AES,教科书式的攻击点是第一轮 S-box 的输出:

v = sbox[plaintext[i] XOR key[i]]

模拟过程:选取一个密钥字节 k,生成 N 个随机明文 p,并为每个明文创建一个假的“轨迹”——一个包含约 100 个样本的数组——其中一个特定样本等于 HW(sbox[p XOR k]) + Gaussian(σ),其余的都是随机噪声。

SPA(简单功率分析)= 眯着眼睛看一条轨迹并识别其结构。在演示中主要作为讲故事:“如果轮次可见,你会看到 10 个凸起。”

CPA(相关性功率分析)是真正的攻击。对于每个候选密钥猜测 g ∈ 0..255

hypothesis_g = [ HW(sbox[p[t] XOR g])  for t in 0..N-1 ]

正确的 g 会产生高相关性(约 0.5–0.9,取决于噪声);错误的猜测会产生约等于 0 的相关性。绘制所有 256 个猜测的 |corr| 图表 → 其中一个柱状图会远高于其他。

如果你不知道泄露样本的位置,可以计算轨迹在每个样本点的相关性并找到峰值。这就是在未知硬件中定位泄露点的方法——值得将其作为热图展示。

1c. 真实轨迹 (ASCAD)

ASCAD 是标准的公共数据集:来自运行 AES 的 ATMega8515 的真实功率测量值,打包为 HDF5 格式。使用与 1b 相同的 CPA 代码——这就是点睛之笔。这个模拟并不是玩具;它使用的是相同的算法。

在浏览器中处理 HDF5 非常痛苦。需要离线预处理:在已知泄露窗口周围提取约 2000 条轨迹 × 约 700 个样本,转储为原始的 Float32Array 二进制文件加上一个明文的 Uint8Array(总共约 5–10 MB),通过 fetch → arrayBuffer() → typed-array view 加载。浏览器端完全不需要处理 HDF5。


2. 架构


┌─────────────────────────────────────┐

│  client/  (Vite + React, port 5173) │

│    Tab 1: Timing Attack → server    │

│    Tab 2: Simulated traces (pure JS)│

│    Tab 3: Real traces (loads .bin)  │

└─────────────┬───────────────────────┘

              │ fetch POST /verify

              ▼

┌─────────────────────────────────────┐

│  server/  (Express, port 3001)      │

│    POST /vulnerable/verify          │

│    POST /safe/verify    (control)   │

│    POST /reset                      │

└─────────────────────────────────────┘

  • 为什么拆分:Tab 1 需要一个真实的服务器进行攻击。Tab 2 和 3 是纯客户端的——即使服务器在台上崩溃,你也可以演示它们。
  • 为什么用 Express:大约 30 行代码,零繁文缛节,大家都认识。
  • 通信方式:REST。攻击是由请求驱动的;不需要 WebSockets。

3. 文件计划

服务器 (server/)

文件用途
index.js 入口文件(约 20 行)。为 localhost:5173 配置 CORS,JSON 主体解析,挂载 /vulnerable/safe 路由,暴露 POST /reset 以重新生成 secret,接受 amplificationUs 查询参数。
routes/vulnerable.js 从主体读取 { guess },使用 process.hrtime.bigint() 计时比较过程,使用带有可选 busyWaitnaiveCompare,返回 { ok, serverTimeNs }
routes/safe.js 结构相同,使用 crypto.timingSafeEqual。作为对照:同样的攻击,但无法恢复密钥。
busyWait 使用了 while (process.hrtime.bigint() - start < target) —— 而不是 setTimeout,因为它的分辨率只有大约 1 毫秒。

客户端 (client/)

文件用途
vite.config.js 开发服务器代理:/api → localhost:3001(避免在开发中处理 CORS)。
src/App.jsx 标签页状态(useState('timing' | 'sim' | 'real')),渲染一个页面组件。约 30 行。
src/pages/TimingAttack.jsx 控制面板(目标、每次猜测的样本数、放大滑块、开始/停止)。每个候选值中位数的实时条形图 + 恢复出的密钥十六进制显示。
src/pages/SimulatedTraces.jsx 控制面板(密钥字节、N 条轨迹、噪声 σ)。生成合成轨迹,运行 CPA,渲染:一个样本轨迹、相关性条形图和相关性热图。
src/pages/RealTraces.jsx 一次性加载 /data/ascad_subset.bin,运行相同的 CPA,显示恢复的与真实密钥字节的对比。
src/lib/aes.js 硬编码的 SBOX: Uint8Array(256) 和预计算的 HW: Uint8Array(256) popcount 查找表。
src/lib/cpa.js 核心 CPA:基于 Float32Array 的两遍 Pearson 相关性计算,每计算约 50 个样本点就会让出控制权给 UI 以更新热图。
src/lib/timing.js 攻击驱动器:recoverKey({ endpoint, keyLength, samples, onProgress })

4. 注意事项

  • CORS:从一开始就配置 cors({ origin: 'http://localhost:5173' }),而不是等到在台上第一个请求失败时才去弄。
  • 服务器测量时间:在服务器端使用 hrtime.bigint() 进行测量并将其返回。客户端的 Date.now() 包含了网络抖动,在微秒级放大下会将信号淹没。
  • 中位数,而非平均值:GC 暂停可能达到 10 毫秒以上——是信号的 1000 倍。使用中位数或低百分位数(例如,5 次中的最小值)。
  • CPA 使用类型化数组:始终使用 Float32Array,绝不使用 []。由于装箱数字的影响,常规数组的内层循环会慢 50 倍。
  • Plotly 体积:约 3 MB。使用 Plotly.react() 语义——复用 layout 对象引用,否则每次更新都会重建 SVG。
  • <>后台标签页节流:Chrome 会将非活动标签页中的 setTimeout 节流到 1 Hz。保持演示标签页处于焦点状态。
  • ASCAD 文件大小:不要附带完整的 HDF5 文件(约 7 GB)。离线进行预处理,只打包你需要的切片。
  • hrtime.bigint() 返回 BigInt:原生不支持 JSON.stringify。在发送前转换为字符串,或使用 Number()(这是安全的——值是纳秒级而非皮秒级)。

5. 构建顺序

  1. 搭建两个包的脚手架(在 server/ 中执行 npm create vitenpm init)。
  2. 让计时接口正常工作——用 curl 验证,对于更长的匹配前缀,放大后的响应时间明显变慢。在终端中验证通过之前不要进入下一步。
  3. 编写 lib/timing.js 和一个最小的 CLI 测试(运行攻击并打印恢复出的密钥的 Node 脚本)——独立于 UI 验证攻击逻辑。
  4. 围绕它构建 React 标签页。
  5. 编写 lib/aes.jslib/cpa.js。在一个极小的合成用例上进行单元测试(4 条轨迹,无噪声,验证正确候选值的相关性 = 1.0)。
  6. 构建 SimulatedTraces 标签页。
  7. 离线预处理 ASCAD 子集(Python + h5py → .bin 文件)。
  8. 构建 RealTraces 标签页——大部分复用了第 5 步的 CPA。
每个步骤都可以独立隔离验证。当准备过程中出现问题时,你能确切知道是哪一层出了错。
标签:ASCAD, CMS安全, CPA, JavaScript, meg, Web安全, Web演示, 代码示例, 侧信道攻击, 信息安全, 安全教学, 密码学, 密码密钥提取, 差分能量分析, 手动系统调用, 攻击模拟, 数据分析, 数据可视化, 时间攻击, 网络安全, 能量分析攻击, 自定义脚本, 蓝队分析, 计时攻击, 隐私保护, 驱动签名利用