KLJN协议统计随机数生成器攻击的Matlab仿真与防御分析

发布时间:2026/10/1 9:49:31
KLJN协议统计随机数生成器攻击的Matlab仿真与防御分析
如果你和我一样最开始看到“基尔霍夫-洛-约翰逊噪声KLJN安全密钥交换协议”这个名字第一反应多半是这不是物理课上的热噪声吗怎么和密钥交换扯上关系真正把协议在 Matlab 里完整仿真一遍之后你会发现物理层安全协议的薄弱点往往不在信道上而在实现它的随机源上。今天要说的统计随机数生成器攻击就是瞄准这个实现弱点的一种典型攻击方式。这篇文章不是把 KLJN 的安全证明重新抄一遍而是站在攻击者角度讲清楚三件事KLJN 协议到底把“秘密”藏在哪里统计随机数生成器攻击为什么能威胁到它以及如何用 Matlab 把这条攻击链路完整实现出来。适合正在做物理层安全、密码协议仿真、或者准备把协议落地到硬件原型的朋友参考。代码基于 Matlab R2026b 跑通但核心函数从 R2019a 开始都能直接用。1. 先从攻击视角重新理解 KLJN 协议1.1 一句话原理热噪声就是密钥载体KLJN 协议的名字来自三个人名和一个物理定律基尔霍夫Kirchhoff、洛Law这里指基尔霍夫定律、约翰逊Johnson发现了电阻热噪声。物理基础其实不复杂任何处于绝对零度以上的电阻都会因为内部电子的随机热运动产生一个随机电压这就是约翰逊噪声。它的功率谱密度在很宽的频带内近似平坦大小由下面的公式决定[ S_V(f) 4 k_B T R ]其中 (k_B) 是玻尔兹曼常数(T) 是绝对温度(R) 是电阻值。也就是说电阻越大噪声功率越大温度越高噪声功率也越大。这个特性在密码学里有一个非常值钱的用途电阻值可以作为密钥比特的物理载体。在 KLJN 协议中通信双方 Alice 和 Bob 各自准备两个已知电阻一般记作 (R_L) 和 (R_H)分别对应二进制 0 和 1。每一轮密钥交换双方各自按自己的随机比特选择一个电阻接到同一根信道上。由于约翰逊噪声的功率和电阻值直接相关所以当信道两端电阻配置不同时本地测到的电压、电流统计量会携带“对方选了哪个电阻”的信息。而窃听者 Eve 只能看到信道上的整体噪声在特定的 0/1 对称配置下她无法区分比特到底是哪一边产生的。这就是 KLJN 的基本思路不是把密钥比特放在数字信号里传输而是把它藏在物理电阻的噪声统计特性中。1.2 攻击面的位置物理层没崩崩的是随机源我第一次跑通 KLJN 的理想信道模型时最大的感受是数学上确实漂亮。Eve 在理想条件下能看到的信道统计量是对称的协议的安全性建立在“完美随机源”这个假设上。但工程实现里这个假设很容易被破坏。Alice 和 Bob 不可能真的去掷硬币也不可能每次都用硬件热噪声源来生成比特。大多数原型系统为了便宜和方便会用一个伪随机数生成器PRNG比如线性同余生成器LCG、线性反馈移位寄存器LFSR甚至直接用一些商用芯片内部的伪随机序列。问题就出在这里。伪随机生成器如果参数选得不好、种子空间太小、或者输出比特有偏那么 Alice 和 Bob 每一轮选择的电阻值就会带有可被统计检测出来的规律。Eve 不需要破解热噪声本身她只需要识别出“电阻选择序列”的统计缺陷就能以高于 50% 的概率猜测密钥比特。这种攻击方式就是标题里说的统计随机数生成器攻击。换句话说攻击者打的是协议实现层不是物理层。这个区别决定了整个 Matlab 代码的构造方式信道模型可以保持理想化随机源模型必须做得足够“差”差到能暴露统计规律。1.3 这条攻击线适合谁如果你只是想知道 KLJN 能不能防住窃听那看协议论文就够了不需要写代码。但如果你要做下面这几件事这条攻击仿真线就很有价值评估一个实际 KLJN 系统的安全性而不是纸面上的安全性在 Matlab 里复现攻击实验给协议设计者提供量化指标测试不同随机数生成器对密钥交换成功率的影响为硬件原型设计“随机源健康检测”模块。我自己的经验是先实现攻击再回头看协议的安全假设往往能发现很多论文里没有展开的工程细节。下面我把攻击思路和 Matlab 实现逐步拆开。2. 统计随机数生成器攻击的完整思路拆解2.1 为什么“统计攻击”能成立很多第一次接触这个攻击的人会问Eve 不是只看到热噪声波形吗她怎么从噪声里知道随机数生成器弱不弱答案在于热噪声波形本身不是一个孤立信号。每一轮密钥交换波形里都包含了 Alice 和 Bob 所选电阻的统计特征。如果随机源弱那么“哪一轮选了哪个电阻”这件事就不是均匀独立分布的而是存在概率偏差、序列相关性、甚至周期性。Eve 把大量观测到的噪声片段收集起来按时间顺序排列再统计每一片的幅度、频谱、自相关等特征就能反推出随机源的部分状态。我把攻击者能利用的统计弱点归纳成三类随机源弱点在信道观测中的表现攻击者可利用方式输出概率有偏某些电阻配置出现频率明显偏高用频率统计直接猜高频比特短周期或状态重复噪声片段的统计特征呈周期重复按周期分组建立模板库序列相关性前后比特之间存在可预测转移关系用马尔可夫模型预测下一比特这三类弱点不是互斥的实际弱随机源往往同时具备多个问题。比如一个 16 位 LCG周期只有 65536而且连续输出之间线性相关Eve 只需要抓足够多的帧就能把整个状态轨迹重建出来。2.2 攻击者的知识假设与能力边界在实现攻击之前必须先明确 Eve 能知道什么、不能知道什么。否则很容易把攻击仿真做成一个“开卷考试”结果虚高。我给出的仿真模型采用以下知识假设Eve 知道协议细节包括 (R_L)、(R_H) 的具体阻值、采样率、帧长Eve 能被动窃听信道获取每一轮密钥交换的完整电压波形Eve 知道对方使用的随机数生成器类型但不知道当前种子Eve 不能主动注入信号也不能物理断开任何一方的线路。这个假设比较接近真实场景。因为协议参数、阻值和采样率通常是公开的随机数生成器类型也可能通过设备型号、开源代码或侧信道信息推测出来。真正需要攻击者付出代价的是恢复当前的随机源状态。2.3 攻击流程总览整个攻击在 Matlab 里可以拆成四个模块正好对应后面的四段代码观测采集模拟 Eve 从信道上截获 N 帧电压波形特征提取对每帧波形计算统计特征比如整流均值、标准差、差分平方均值、功率谱特征状态重放用候选随机数生成器和候选种子生成 Alice、Bob 的电阻选择序列统计判决把观测特征和候选序列做匹配输出对每帧密钥比特的预测。四个模块串起来后攻击成功率是最终输出指标。我在实验中最关心的不是“能不能破”而是“在什么参数条件下能破、什么条件下不能破”。这样才能给防御方一个明确的边界。3. Matlab 仿真环境与模块设计3.1 仿真总体架构我没有用 Simulink也没有调用复杂的电路仿真工具箱因为这里要验证的是攻击逻辑不是电阻网络的瞬态响应。用纯 Matlab 脚本反而更容易控制随机源、信道噪声和统计检测这三个环节。工程目录结构可以这样组织kljn_attack/ run_demo.m % 主脚本跑完整攻击实验 weak_rng.m % 弱随机数生成器模型 kljn_channel.m % KLJN 信道观测模型 attack_features.m % 统计特征提取 attack_decision.m % 判决与成功率统计这样的好处是每一层都可以单独替换。比如你想测试不同的弱随机源只需要改weak_rng.m你想测试不同信道带宽只需要改kljn_channel.m的入口参数。整个实验做完不用推倒重来。3.2 参数设置仿真参数我建议固定成一组基线值后面做对比时才不会乱。下面这组参数是我在实验里用的参数取值说明采样率 fs1 MHz模拟采样率远高于噪声带宽即可单帧时长 Twin1 ms每轮密钥交换的观测窗口单帧采样点 N1000fs 乘以 Twin 得到低温电阻 RL1 kΩ对应比特 0高温电阻 RH10 kΩ对应比特 1温度 T300 K室温假设仿真帧数 M200 到 1000视攻击稳定性需求调整电阻比例取 10主要是为了让不同配置在特征空间里有明显区分度。如果比例太小信道统计量混在一起不仅攻击困难合法双方解调也会很难做。3.3 弱随机数生成器的建模攻击仿真里最重要的一步是构造一个“真实可信的弱随机源”。我选了线性同余生成器因为它结构简单、周期可控、统计缺陷一目了然。function bits weak_lcg_bits(N, a, c, m, seed) % 经典线性同余生成器用于模拟弱随机源 % N: 输出比特数; a, c, m: LCG参数; seed: 初始种子 x zeros(N, 1); x(1) seed; for k 2:N x(k) mod(a * x(k-1) c, m); end % 映射到 [0,1)再转成比特 u x / (m - 1); bits double(u 0.5); end这里我故意把 m 设成 65536也就是 16 位。它的周期只有 65536连续输出之间还有很强的相关性。只要 Eve 知道 a、c、m她完全可以通过暴力搜索 65536 个种子来重放整条序列。这个函数虽然简单但它抓住了真实弱随机源最核心的问题可预测性。3.4 KLJN 物理信道模块信道模块不需要做晶体管级建模。我的做法是把 Alice 和 Bob 的电阻分别看成一个独立热噪声源然后按照电阻分压关系合成信道上的观测电压。function Vseg kljn_channel(RA, RB, N, fs, T) % RA, RB: 两端电阻值 % N: 采样点数; fs: 采样率; T: 温度 kB 1.380649e-23; bw fs / 2; % 仿真中的等效噪声带宽 sigmaA sqrt(4 * kB * T * RA * bw); sigmaB sqrt(4 * kB * T * RB * bw); VnA sigmaA * randn(N, 1); VnB sigmaB * randn(N, 1); % 信道节点电压两个电阻热噪声的加权叠加 Vseg (VnA * RB VnB * RA) / (RA RB); end这个模型忽略了很多高频寄生参数但用于统计攻击验证已经足够。关键在于不同电阻组合会让 Vseg 的幅度和频谱产生差异而统计特征提取就是要捕捉这个差异。3.5 统计检测与分类模块特征提取不能只用标准差因为不同电阻组合的标准差容易受随机波动影响。我通常取三个互补特征function feat attack_features(Vseg) % 从一段电压波形中提取统计特征向量 feat zeros(1, 3); feat(1) mean(abs(Vseg)); % 整流均值反映幅度水平 feat(2) std(Vseg); % 标准差反映噪声功率 feat(3) mean(diff(Vseg).^2); % 差分平方均值反映高频能量 end整流均值和高频能量这两个特征对采样率变化比较敏感但对电阻差别的响应更稳定。只用标准差的话一旦信道白噪声出现小幅波动分类器就容易产生误判。4. 核心代码实现与关键细节4.1 生成 Alice 和 Bob 的电阻选择序列在攻击仿真里我们需要先按真实协议生成 Alice 和 Bob 的比特再生成对应的噪声波形。这里要注意攻击者并不知道这些比特代码里保留它们只是为了最后评估预测准确率。% 生成 Alice 和 Bob 的随机选择序列 M 400; [bitsA, bitsB] deal(zeros(M, 1)); % 预分配 rng(2024); % 为了方便复现 seedA 12345; seedB 54321; % 这里假设 Alice 和 Bob 各自使用独立的弱 LCG bitsA weak_lcg_bits(M, 1103515245, 12345, 65536, seedA); bitsB weak_lcg_bits(M, 1103515245, 12345, 65536, seedB);实际协议中 Alice 和 Bob 的随机源应该是独立的否则攻击面会更大。我把种子设为不同值模拟双方使用各自独立的伪随机生成器但参数相同、周期相同的场景。4.2 模拟窃听者看到的信道观测接下来进行主循环每一轮根据双方比特计算电阻值生成电压波形提取特征并保存真实标签。RL 1e3; RH 10e3; fs 1e6; Twin 1e-3; N round(fs * Twin); T 300; feats zeros(M, 3); trueBits zeros(M, 1); for k 1:M RA RL (RH - RL) * bitsA(k); RB RL (RH - RL) * bitsB(k); Vseg kljn_channel(RA, RB, N, fs, T); feats(k, :) attack_features(Vseg); trueBits(k) bitsA(k); % 攻击目标是恢复 Alice 的比特 end这里我选择恢复 Alice 的比特因为只要恢复 Alice 的比特再结合协议规则就能推断出实际使用的密钥比特。如果你想把攻击目标设为 Bob逻辑完全一样。4.3 特征分类与攻击判决得到特征矩阵后把问题转换成一个监督分类问题。这里需要特别说明真实攻击中 Eve 没有标签但在仿真阶段我们作为“上帝视角”的评估者可以用标签训练和验证分类器看看信道观测到底泄露了多少信息。% 用 10 折交叉验证评估分类准确率 model fitcdiscr(feats, trueBits); cvmodel crossval(model, KFold, 10); loss kfoldLoss(cvmodel); attackAccuracy 1 - loss; fprintf(攻击准确率: %.2f%%\n, attackAccuracy * 100); fprintf(随机猜测基线: 50.00%%\n);如果攻击准确率显著高于 50%说明 Eve 确实能从电压波形的统计特征里恢复出 Alice 的随机比特。再进一步如果 Eve 能同时恢复 Alice 和 Bob 的比特她就能直接重建整个密钥交换过程。4.4 完整的攻击主脚本调用把上面的片段串起来就是run_demo.m的主体。我习惯在最后加一个“随机源替换”的对比实验把 Alice 和 Bob 的弱 LCG 换成 Matlab 自带的强随机数生成器再跑一次同样的流程作为攻击有效性的对照。% 对照组使用强随机源 bitsA_strong randi([0 1], M, 1); bitsB_strong randi([0 1], M, 1); featsStrong zeros(M, 3); trueBitsStrong zeros(M, 1); for k 1:M RA RL (RH - RL) * bitsA_strong(k); RB RL (RH - RL) * bitsB_strong(k); Vseg kljn_channel(RA, RB, N, fs, T); featsStrong(k, :) attack_features(Vseg); trueBitsStrong(k) bitsA_strong(k); end modelStrong fitcdiscr(featsStrong, trueBitsStrong); lossStrong kfoldLoss(crossval(modelStrong, KFold, 10)); fprintf(强随机源对照准确率: %.2f%%\n, (1 - lossStrong) * 100);对照组的结果应该非常接近 50%。如果强随机源也得到很高的分类准确率那说明你的特征或信道模型出了问题而不是攻击有效。这个对照实验是我调试时最重要的一步。5. 实验结果怎么看指标与判据5.1 攻击成功率的定义攻击成功率不是“破解整个密钥”的概率而是“单个比特被正确预测”的概率。在 200 到 1000 帧的规模下每提高 5 到 10 个百分点对整个密钥的安全性影响都会非常显著。我的典型实验结果如下实验配置分类准确率结论弱 LCG未知种子需暴力搜索100% 或接近 100%随机源状态完全可恢复协议失效弱 LCG只攻击单帧无历史帧约 60% 到 70%有统计泄露但不至于直接重建强随机源randi/Mersenne Twister约 50%攻击失效协议回到理想安全状态弱 LCG但增加白噪声干扰55% 到 80% 不等攻击效果取决于信噪比从这些结果可以得出一个工程结论KLJN 协议的安全性非常依赖随机源质量。你甚至不需要让随机源“完全不可预测”只需要让它的输出接近均匀独立分布就能把攻击准确率压回 50% 附近。5.2 要排除的两个假阳性调试过程中最容易出问题的是两种假阳性。第一种是特征过拟合。如果你的特征里包含太多跟电阻绝对值直接相关的信息分类器可能不是学到随机源规律而是纯粹学到“电阻大噪声大、电阻小噪声小”这种物理映射。粗看准确率很高其实换个种子就不灵了。解决办法就是用交叉验证并且同时跑强随机源对照组。第二种是帧边界对齐错误。Eve 必须知道每一轮密钥交换从哪个采样点开始、到哪个采样点结束。如果对齐错误特征会被相邻帧混淆攻击准确率反而下降。这个不能算攻击失败只能说明你的观测采集假设太乐观。实际场景里帧同步信息往往是协议公开的一部分Eve 可以拿到。5.3 关键参数对结果的影响在我反复调参的过程中有三个参数对攻击成功率影响最大单帧采样点数 (N)(N) 越大特征估计越稳定弱随机源的信息泄露越容易被检测到。但 (N) 太大意味着密钥交换速率变慢合法系统也不会愿意牺牲性能。电阻比例 (R_H/R_L)比例越大不同电阻配置在幅度特征上分得越开攻击准确率越高。所以协议设计者不能为了追求物理层区分度而把电阻比例无限拉大。随机源周期周期越短Eve 积累少量帧之后就能看到重复模式。周期超过观测帧数时单纯靠周期检测就不再有效这时要看序列相关性和分布偏差。这三个参数形成了一条三角约束想提高合法解调性能就可能削弱安全性想提高安全性就必须忍受更慢的密钥速率或者更复杂的随机源。6. 常见问题与避坑指南6.1 为什么我的攻击准确率上不去我见过最多的原因是随机源还不够弱。如果你用的是 Matlab 内建的rand或randi默认 Mersenne Twister 的统计质量很好分类器当然上不去。请先换成周期较短、输出相关性明显的 LCG或者截断低位的 LFSR先把攻击链路跑通再逐步加强随机源。第二个常见原因是特征选得不对。单帧幅度特征在小样本下波动很大我建议至少加入差分能量特征或者对功率谱的低频段做积分。很多随机源缺陷会在高频能量或者自相关结构上暴露出来而不是简单体现在标准差里。第三个原因是帧数太少。如果只跑几十帧分类器没有足够样本准确率波动会很大。建议至少跑 200 帧以上并用交叉验证稳定评估。6.2 采样率、位数和窗口长度怎么配一个经常被忽略的细节是热噪声仿真里的等效带宽。离散仿真里如果采样率是 (f_s)那么能表示的最高频率是 (f_s/2)所以热噪声标准差里用的带宽可以取 (f_s/2)。这个处理在理论上是近似的但能让不同采样率下的结果可比。窗口长度和采样率要一起调。窗口长度决定特征估计的方差采样率决定你能否捕获电阻噪声的频谱差异。高采样率配合短窗口和低采样率配合长窗口虽然采样点数相同但包含的物理信息不一样。我常用的一组经验值(f_s 1\text{ MHz})窗口 1 ms电阻 1 kΩ 和 10 kΩ温度 300 K。这组参数计算量小特征区分度足够跑 1000 帧在普通笔记本上只要几秒钟。6.3 防御视角如何在工程上补这个洞攻击仿真做下来最该带走的是防御视角。统计随机数生成器攻击之所以成立根本原因是随机源的输出被直接用于电阻选择。防御可以从三个方向入手。第一换掉弱随机源。工程上至少要用密码学安全随机数生成器最好配合硬件真随机源。第二增加后处理。即使随机源有轻微偏差也可以通过哈希、抽取、冯·诺依曼矫正等方式把输出压到接近均匀。第三在协议层加隐私放大。每一轮密钥交换后不直接使用原始比特而是做一次哈希压缩这样即使 Eve 能猜中少量原始比特也拿不到最终密钥。我个人在实际操作中的体会是KLJN 这种物理层安全协议最怕的不是攻击者数学很强而是实现者默认“物理安全等于全部安全”。你把统计随机数生成器攻击的 Matlab 代码跑一遍再去看协议里的安全假设就会明白随机源质量不是纸上谈兵它是整个安全链条里最容易被忽略、也最容易被攻击者利用的一环。最后再分享一个小技巧这套攻击代码不止能拿来攻击也能拿来当“随机源体检工具”。接入一个真实随机源之前先按同样的流程采集一段它驱动的 KLJN 电压波形跑一遍分类准确率。如果准确率明显高于 50%说明这个随机源不合格趁早上后处理别等到密钥交换出问题再回头查。