4. 真随机数发生器(TRNG):原理、熵源设计与随机性测试
真随机数发生器,圈里人习惯叫 TRNG。它跟伪随机数发生器(PRNG)最大的区别在哪?说白了,TRNG 的随机性来源于物理过程——热噪声、抖动、亚稳态这些不可预测的物理现象。而 PRNG 再厉害,本质也是个确定性的算法,种子一旦泄露,整个序列就完了。
我在安全芯片项目里吃过 TRNG 的亏。第一次流片回来,随机数测试 FIPS 140-2 死活过不了,查了两个月才发现是熵源电路的电源噪声被同步了。嗯,从那以后我对 TRNG 的设计就格外较真。
4.1 TRNG 的核心架构
一个完整的 TRNG,通常由三部分组成:
- 熵源(Entropy Source):产生原始随机信号,是 TRNG 的根基
- 数字化电路(Digitizer):把模拟的随机信号转成 0/1 比特流
- 后处理(Post-Processing):消除偏差、提取纯随机性
你想想看,如果熵源本身不够随机,后面做再多处理也是白搭。所以我个人习惯把 70% 的精力放在熵源设计上。
核心原则:TRNG 的安全性不取决于算法复杂度,而取决于物理熵源的质量。一个简单的环形振荡器 TRNG,只要熵源设计得当,比一个花哨的量子 TRNG 更可靠。
4.2 常见熵源设计
我见过不下十种熵源方案,真正能在量产中稳定工作的其实就几种。下面是我个人比较推荐的方案对比:
| 熵源类型 | 基本原理 | 速率 | 抗攻击能力 | 面积开销 |
|---|---|---|---|---|
| 环形振荡器 | 振荡器相位抖动采样 | 中(~Mbps) | 中等 | 小 |
| 亚稳态采样 | 触发器亚稳态判决 | 低(~kbps) | 高 | 极小 |
| 热噪声放大 | 电阻热噪声 + 放大器 | 高(~10Mbps) | 高 | 大 |
| 量子噪声 | 光子散粒噪声 | 极高 | 极高 | 极大(需外部器件) |
我的建议:对于大多数安全 SoC,环形振荡器 + 亚稳态混合方案是最务实的。纯环形振荡器容易被电源噪声注入攻击,纯亚稳态速率太低。两者结合,既保证速率又提升抗攻击能力。
4.3 环形振荡器 TRNG 详解
这是最经典的 TRNG 结构。原理其实不复杂:
- 用奇数个反相器构成环形振荡器,输出一个方波信号
- 这个方波的周期不是固定的——受热噪声和闪烁噪声影响,每个周期都有微小的抖动
- 用另一个低频时钟去采样这个抖动信号
- 采到的值就是随机比特
我曾经犯过一个低级错误:把振荡器的电源跟数字核心共用。结果数字电路一跑起来,振荡器的抖动被电源噪声同步了,随机性大幅下降。后来我强制要求 TRNG 用独立的 LDO 供电,问题才解决。
避坑指南:环形振荡器 TRNG 最怕「确定性抖动」。如果振荡器的抖动主要来自电源噪声或衬底耦合,而不是热噪声,那输出的随机性就是假的。我曾经在测试中遇到过——看起来随机数测试能过,但换到不同工艺角就挂了。后来发现是振荡器结构对工艺太敏感。
4.4 亚稳态 TRNG
这个方案很有意思。它利用的是触发器的亚稳态特性——当建立时间或保持时间不满足时,触发器输出会进入一个不确定状态,最终稳定到 0 或 1 的概率各占 50%。
我参与过一个项目,用亚稳态 TRNG 做密钥生成。设计很简单:两个反相器交叉耦合,再加一个使能信号。面积只有几个门,但随机性非常好。不过要注意:
- 亚稳态的解析时间需要精确控制,太短会偏向 0 或 1
- 温度变化会影响亚稳态窗口,需要做温度补偿
- 老化工况下,器件失配会引入偏差
关键参数:亚稳态 TRNG 的「解析时间窗口」通常需要控制在 50-200ps 之间。太窄了采样不到亚稳态,太宽了输出偏向严重。我习惯在仿真时跑 10000 次 Monte Carlo,确保 3-sigma 范围内偏差小于 1%。
4.5 后处理技术
熵源出来的原始比特流,通常不是完美的。常见的问题有:
- 偏差(Bias):0 和 1 的比例不是 50:50
- 相关性(Correlation):相邻比特之间存在依赖
- 周期性(Periodicity):存在重复模式
后处理就是用来解决这些问题的。我常用的方案:
| 后处理方法 | 原理 | 适用场景 | 吞吐损失 |
|---|---|---|---|
| Von Neumann 去偏 | 丢弃 00/11,保留 01/10 | 偏差大但无相关性 | ~75% |
| XOR 压缩 | 多比特异或 | 相关性较强 | ~50% |
| LFSR 后处理 | 线性反馈移位寄存器 | 通用场景 | ~30% |
| 加密哈希 | SHA-256 / AES 压缩 | 高安全场景 | ~90% |
我的经验:不要一上来就用加密哈希做后处理。先看看熵源质量——如果偏差在 5% 以内,一个简单的 Von Neumann 去偏就够了。哈希虽然安全,但吞吐损失太大,而且会引入额外的功耗和面积。我一般只在 FIPS 140-2 Level 3 以上的场景才用哈希后处理。
4.6 随机性测试
设计完 TRNG,怎么验证它真的随机?光靠仿真不够,必须上标准测试。我常用的测试套件:
- NIST SP 800-22:15 项统计测试,行业标准
- FIPS 140-2:4 项基本测试,适合快速筛查
- Dieharder:更严格的测试集,适合深度验证
- TestU01:学术圈常用,覆盖更全面
我个人习惯的测试流程:
- 在线测试:芯片运行时实时监测,发现异常立即报警
- 离线测试:采集 1M 比特以上数据,跑 NIST 全套测试
- 工况测试:在高温、低温、高电压、低电压下重复测试
- 老化测试:加速老化后再次测试,确保寿命期内稳定
重要提醒:随机性测试通过 ≠ TRNG 安全。我见过一个案例——TRNG 在正常工况下所有测试都过了,但攻击者用电磁注入干扰后,输出变成了可预测的序列。所以除了统计测试,还要做「主动攻击测试」:尝试用电源噪声、时钟毛刺、激光照射等手段攻击 TRNG,看它是否还能保持随机性。
4.7 一个完整的 TRNG 设计示例
下面是我在一个 IoT 安全芯片中用过的 TRNG 代码片段。它用环形振荡器做熵源,加上 Von Neumann 去偏:
// 环形振荡器 TRNG 核心
module trng_core (
input wire clk, // 采样时钟
input wire rst_n, // 复位
input wire enable, // 使能
output wire [7:0] rnd_data, // 随机字节
output wire valid // 数据有效
);
// 环形振荡器:3级反相器
wire osc_out;
ring_osc #(.STAGES(3)) u_osc (
.enable(enable),
.osc_out(osc_out)
);
// 亚稳态采样
reg [1:0] sample_ff;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sample_ff <= 2'b0;
end else begin
sample_ff <= {sample_ff[0], osc_out};
end
end
// Von Neumann 去偏
wire raw_bit = sample_ff[1];
reg prev_bit;
reg valid_bit;
reg [7:0] shift_reg;
reg [2:0] bit_cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
prev_bit <= 1'b0;
valid_bit <= 1'b0;
shift_reg <= 8'b0;
bit_cnt <= 3'b0;
end else begin
// Von Neumann: 01 -> 0, 10 -> 1, 00/11 -> 丢弃
if (raw_bit != prev_bit) begin
valid_bit <= 1'b1;
shift_reg <= {shift_reg[6:0], raw_bit};
bit_cnt <= bit_cnt + 1'b1;
end else begin
valid_bit <= 1'b0;
end
prev_bit <= raw_bit;
end
end
assign rnd_data = shift_reg;
assign valid = (bit_cnt == 3'b111) & valid_bit;
endmodule
设计要点:这个代码里我用了两级采样(sample_ff)来避免亚稳态传播。Von Neumann 去偏的逻辑很简单——只保留相邻比特不同的情况。这样即使原始比特有 60:40 的偏差,输出也能恢复到接近 50:50。实测下来,这个设计在 0.18um 工艺下能跑到 2Mbps,面积只有 0.01mm²。
4.8 常见问题与调试方法
做 TRNG 设计,总会遇到各种奇怪的问题。我整理了几个高频问题:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| NIST 测试全挂 | 熵源未工作 / 采样时钟同步 | 用示波器看振荡器波形,检查是否有周期性 |
| 偏差 > 10% | 器件失配 / 温度影响 | 跑 Monte Carlo 仿真,检查工艺角 |
| 高温下失效 | 振荡器频率漂移 | 加温度补偿电路,或改用温度不敏感结构 |
| 攻击后输出可预测 | 缺少主动检测机制 | 加电源毛刺检测、频率监测 |
调试技巧:我习惯在 TRNG 输出端加一个「健康监测」模块——实时统计 0 和 1 的计数,如果连续 1024 个比特中偏差超过 15%,就触发告警并重新初始化熵源。这个模块面积很小,但能避免很多线上问题。
嗯,TRNG 设计就是这样——看起来简单,但坑很多。核心就一句话:尊重物理,敬畏随机。熵源是根,后处理是叶,测试是眼。根扎稳了,叶子自然茂盛。