3、真随机数发生器(TRNG):TRNG原理(振荡器采样法、热噪声法),TRNG的熵源评估与在线测试
各位同学,今天我们聊一个芯片安全里最基础、也最容易被忽视的模块——真随机数发生器。
我做了这么多年芯片安全,见过太多系统因为随机数不够随机而被攻破。你想想看,密钥生成、签名、认证,哪一步离得开随机数?如果随机数是假的,那整个安全体系就是纸糊的。
好,我们直接进入正题。
3.1 TRNG 是什么?为什么不能用伪随机数?
真随机数发生器,英文叫 True Random Number Generator,简称 TRNG。它跟软件里用 rand() 函数生成的伪随机数有本质区别。
伪随机数,说白了就是算法算出来的。只要知道种子,就能预测下一个数。这在安全场景里是致命的。我曾在项目中见过有人用 LFSR(线性反馈移位寄存器)当随机源,结果被攻击者通过侧信道分析直接还原了整个密钥流。
TRNG 不一样。它从物理世界的噪声中提取随机性——热噪声、抖动、亚稳态。这些过程本质上是不可预测的,量子力学层面的随机。
核心区别一句话:伪随机数是算出来的,真随机数是「长」出来的。
3.2 振荡器采样法——最经典的 TRNG 架构
振荡器采样法,英文叫 Ring Oscillator Sampling。这是我在实际项目中最常用的方案,简单、可靠、面积小。
原理其实不复杂:
- 用奇数个反相器组成一个环形振荡器,产生高频时钟
- 用另一个低频时钟去采样这个高频时钟
- 由于两个时钟之间存在相位抖动,采样结果会随机出现 0 或 1
嗯,这里要注意。抖动来自哪里?来自电源噪声、热噪声、甚至宇宙射线。这些物理过程天然随机。
我给大家画个简化的 Verilog 模型:
// 振荡器采样法 TRNG 简化模型
module trng_ring_osc (
input wire clk_low, // 低频采样时钟
input wire rst_n,
output reg random_bit
);
wire osc_out;
// 3级环形振荡器
inv u1 (osc_in, osc_out);
inv u2 (osc_out, osc_mid);
inv u3 (osc_mid, osc_in);
// 用低频时钟采样
always @(posedge clk_low or negedge rst_n) begin
if (!rst_n)
random_bit <= 1'b0;
else
random_bit <= osc_out;
end
endmodule
这个代码只是教学演示。实际芯片里,环形振荡器的频率要精心设计,采样时钟也要考虑亚稳态问题。
避坑指南:我曾经在 28nm 工艺上做过一个 TRNG,振荡器频率设得太高,结果采样时钟的建立时间不够,输出全是 0。后来加了两级同步器才解决。记住:采样时钟频率不能超过振荡器频率的 1/3。
3.3 热噪声法——更纯粹的随机源
热噪声法,英文叫 Thermal Noise Based TRNG。这种方法直接利用电阻或晶体管的热噪声,随机性比振荡器采样法更「纯」。
原理是这样的:
- 电阻两端的热噪声电压是随机的,服从高斯分布
- 把这个微弱噪声放大到可比较的电平
- 用比较器量化成 0 或 1
我个人的习惯是,在需要高安全等级的场景(比如金融芯片、HSM)用热噪声法。虽然面积大、功耗高,但熵源质量确实好。
一个典型的模拟 TRNG 结构:
// 热噪声 TRNG 的模拟前端(示意)
// 实际实现需要模拟电路,这里只是逻辑描述
// 噪声源:两个匹配电阻产生热噪声
// 差分放大器:将噪声放大 1000 倍
// 比较器:将模拟信号转为数字 0/1
// 后处理:冯·诺依曼校正器消除偏置
module von_neumann_corrector (
input wire raw_bit,
input wire clk,
output reg corrected_bit,
output reg valid
);
reg prev_bit;
always @(posedge clk) begin
if (raw_bit != prev_bit) begin
corrected_bit <= raw_bit;
valid <= 1'b1;
end else begin
valid <= 1'b0;
end
prev_bit <= raw_bit;
end
endmodule
冯·诺依曼校正器是个好东西。它能消除 0/1 偏置,但代价是吞吐率减半。我一般会在后处理里再加一级 AES-CBC-MAC 做去相关。
重要提醒:热噪声法的模拟电路对工艺偏差非常敏感。我在 55nm 工艺上流片时,同一个版图,不同晶圆角落的噪声幅度能差 30%。所以一定要做工艺角仿真,留足余量。
3.4 熵源评估——你的 TRNG 够随机吗?
好,TRNG 做出来了。但你怎么知道它真的随机?
这就涉及到熵源评估。说白了,就是量化你的随机数到底有多少「不可预测性」。
常用的评估指标:
| 指标 | 含义 | 合格标准 |
|---|---|---|
| 最小熵 (Min-Entropy) | 最坏情况下的熵值 | ≥ 0.5 bits/bit |
| 香农熵 (Shannon Entropy) | 平均信息量 | ≥ 0.95 bits/bit |
| 自相关系数 | 相邻比特的相关性 | |r| < 0.01 |
| NIST SP 800-22 | 15项统计测试 | 全部通过 |
我个人习惯用最小熵来评估。为什么?因为安全设计要考虑最坏情况。攻击者可能通过温度、电压攻击让你的 TRNG 退化,这时候最小熵才是真正的安全底线。
举个例子:你采集了 100 万个比特,统计发现 0 出现了 500,100 次,1 出现了 499,900 次。看起来挺均匀对吧?但最小熵计算出来可能只有 0.98 bits/bit。如果攻击者能通过注入噪声让偏置变大,这个值会迅速下降。
经验之谈:我在评估一个 TRNG 时,会先做 1000 万比特的采集,然后分 100 个窗口分别计算最小熵。如果某个窗口的最小熵低于 0.5,我会直接判定这个 TRNG 不合格。别问为什么,问就是吃过亏。
3.5 在线测试——运行时也要盯着
TRNG 不是流片测试一次就完事了。芯片在运行过程中,温度、电压、老化都会影响随机性。所以必须做在线测试。
在线测试分两类:
- 健康测试 (Health Test):持续监控,发现异常立即报警
- 全测试 (Full Test):定期做完整的统计测试,比如每 10 万比特做一次 NIST 测试
我常用的在线测试方案:
// 在线健康测试:重复计数检测
module health_test (
input wire clk,
input wire random_bit,
output reg alarm
);
reg [7:0] same_count;
reg prev_bit;
always @(posedge clk) begin
if (random_bit == prev_bit) begin
same_count <= same_count + 1;
if (same_count > 8'd64) // 连续64个相同比特
alarm <= 1'b1;
end else begin
same_count <= 8'd0;
end
prev_bit <= random_bit;
end
endmodule
这个检测器很简单,但很有效。如果 TRNG 因为某种原因卡在 0 或 1,它能立刻发现。
更完善的方案还包括:
- 自适应比例测试:统计 1024 比特中 1 的比例,超出 30%-70% 范围就报警
- 游程测试:检测连续相同比特的长度分布
- 傅里叶变换测试:检测周期性成分
我的建议:在线测试的阈值不要设得太紧。我曾经在一个项目中把阈值设成连续 32 个相同比特就报警,结果芯片在高温下频繁误报。后来改成 64,问题解决。记住:在线测试的目的是检测「灾难性故障」,不是检测「轻微退化」。
3.6 总结与思考
好,今天的内容就到这里。我们讲了 TRNG 的两种主流实现方式,也聊了怎么评估和测试。
最后留个思考题:如果你的 TRNG 在低温下输出偏置严重,你会怎么处理?是改电路,还是加后处理?
我个人倾向于两者都做。电路层面加温度补偿,后处理层面加 AES 去相关。安全设计,永远不要只依赖一层防护。
下一章我们讲物理不可克隆函数(PUF),那是另一个有趣的话题。到时候见。