3、密钥生成技术:TRNG 与 PRNG 的博弈
密钥生成,说白了就是整个密码体系的起点。你算法选得再强,密钥本身是弱的那就全白搭。我在不少安全审计项目里见过,系统用了 AES-256,结果密钥是从一个固定字符串哈希出来的——嗯,这种“金库配纸锁”的案例,真不少。
这一章我们聚焦一个核心问题:随机数到底怎么来? 以及,什么样的随机数才能当密钥用。
3.1 真随机 vs 伪随机:本质差异
很多人以为“随机”就是“猜不到”。其实在密码学里,这两个概念差得很远。
| 特性 | 真随机数生成器 (TRNG) | 伪随机数生成器 (PRNG) |
|---|---|---|
| 熵来源 | 物理过程(热噪声、量子效应、放射性衰变) | 初始种子值 + 算法 |
| 可预测性 | 理论上不可预测 | 知道种子和算法即可复现 |
| 输出速率 | 慢(通常几十 Kbps ~ 几 Mbps) | 快(可达 Gbps 级别) |
| 硬件依赖 | 需要专用硬件 | 纯软件即可 |
| 典型用途 | 生成种子、密钥材料、IV | 会话密钥、批量生成 |
核心原则:TRNG 负责“产生不确定性”,PRNG 负责“把不确定性放大到足够长”。两者是搭档,不是替代关系。
我在一次金融系统的密钥初始化方案评审中,看到设计者直接用 /dev/urandom 生成长达 4096 位的 RSA 密钥对。当时我就问了一个问题:系统刚启动时,熵池够不够?对方愣住了。后来我们加了一个硬件 TRNG 作为种子源,才通过合规审查。
3.2 真随机数生成器(TRNG)的实现原理
TRNG 的核心是物理熵源。常见的几种方式:
- 热噪声放大:利用半导体 PN 结的散粒噪声,放大后采样。成本低,但易受温度影响。
- 环形振荡器抖动:多个反相器环的相位抖动累积,采样时钟边沿的不确定性。FPGA 上常用。
- 量子效应:比如光子通过分束器的随机路径,或者量子隧穿效应。这是目前公认的“真·真随机”。
- 混沌电路:利用非线性电路的混沌行为,输出不可预测的模拟信号。
避坑指南:我曾经见过一个 IoT 设备,为了省成本,用 ADC 读取悬空引脚的电压值当随机数。结果同一批次的芯片,因为 PCB 布局差异,输出有明显的相关性。这种“伪 TRNG”比 PRNG 更危险——它给你一种“我很随机”的错觉。
3.3 伪随机数生成器(PRNG)与密码学安全
PRNG 不是不能用,关键是要用密码学安全的 PRNG(CSPRNG)。普通的 rand() 函数,你猜得到种子,就能猜出整个序列。
常见的 CSPRNG 算法:
- 基于分组密码的 CTR_DRBG(NIST SP 800-90A):用 AES 加密计数器,简单高效。
- 基于哈希的 Hash_DRBG:用 SHA-256 迭代,抗量子攻击潜力更好。
- 基于流密码的 ChaCha20:Linux 内核的
getrandom()就用它,性能极佳。
我的习惯:在服务器端,我倾向于用 getrandom() 系统调用(Linux 5.6+),它直接从内核熵池取数,且不会阻塞。在嵌入式环境,我会用硬件 TRNG 种子 + CTR_DRBG 的组合,既保证熵质量,又满足吞吐需求。
3.4 密钥生成标准:NIST SP 800-133
NIST SP 800-133 是密钥生成的“操作手册”。它把密钥生成分为三个层次:
- 原始密钥材料:来自 TRNG 或熵源,长度至少等于密钥强度(比如 AES-256 需要至少 256 位熵)。
- 密钥生成方法:包括直接生成、通过 KDF 派生、或者从共享秘密中提取。
- 密钥格式:指定密钥的编码方式(如 PKCS#8、RAW、X.509 SubjectPublicKeyInfo)。
标准里特别强调了一点:密钥生成过程必须包含熵源的健康检测。也就是说,TRNG 不能“裸奔”,必须实时监测输出质量。
关键要求(摘自 NIST SP 800-133 Rev.2):
- 密钥生成必须使用经认证的熵源。
- 生成的密钥必须通过统计随机性测试(如 NIST SP 800-22)。
- 密钥材料在生成后,必须立即从内存中清除临时副本。
- 支持密钥的“即时验证”——生成后立即检查是否符合预期格式。
3.5 生成最佳实践:从理论到落地
理论说完了,咱们聊聊实际怎么干。我总结了一套“四步法”,在多个项目中验证过:
第一步:选对熵源
- 服务器:优先用 Intel SGX 或 AMD PSP 内置的硬件 TRNG。
- 云环境:用云厂商的 KMS 生成密钥材料,别自己从虚拟机里取数。
- IoT/嵌入式:至少用 MCU 内置的 RNG 外设,并做在线自检。
第二步:混合熵源
单一熵源有单点故障风险。我习惯的做法是:
// 伪代码:混合熵源生成种子
entropy_pool = []
entropy_pool.append(hardware_trng.read(32)) // 硬件源
entropy_pool.append(os_getrandom(16)) // 系统源
entropy_pool.append(timestamp_ns()) // 时间源(辅助)
seed = SHA256(concat(entropy_pool)) // 混合后哈希
第三步:使用标准化的 DRBG
别自己发明随机数算法。用 NIST 批准的 DRBG 实现:
// OpenSSL 示例:使用 CTR_DRBG 生成 AES-256 密钥
EVP_RAND_CTX *rng = EVP_RAND_fetch(NULL, "CTR-DRBG", NULL);
EVP_RAND_instantiate(rng, 256, 0, NULL, 0, NULL);
unsigned char key[32];
EVP_RAND_generate(rng, key, sizeof(key), 0, NULL, 0);
// 注意:用完要清理敏感数据
OPENSSL_cleanse(key, sizeof(key));
EVP_RAND_free(rng);
第四步:验证与审计
- 生成后立即用
EVP_PKEY_param_check验证密钥对。 - 记录生成日志(谁、什么时间、用什么熵源、生成了什么类型的密钥)。
- 定期用 NIST SP 800-22 测试套件对 TRNG 输出做离线检测。
我曾经踩过的坑:有一次在容器化部署中,多个容器共享宿主机的熵池。结果某个容器疯狂调用 /dev/random,导致其他容器的密钥生成阻塞了整整 3 秒。后来我们给每个容器分配了独立的 virtio-rng 设备,才解决这个问题。你想想看,生产环境里 3 秒的延迟,可能引发连锁超时。
3.6 知识体系总览
下面这张图,把本章的核心逻辑串起来了。你可以把它贴在墙上,每次设计密钥生成方案时对照一下:
这张图的核心逻辑是:熵源 -> 混合 -> DRBG -> 密钥 -> 验证。每一步都有对应的标准和最佳实践。你设计密钥生成方案时,按这个链路走,基本不会漏掉关键环节。
最后说一句:密钥生成不是“写几行代码调个 API”那么简单。它涉及硬件选型、合规要求、性能权衡。我建议你在项目初期就把密钥生成方案写进安全架构文档,而不是等到上线前才补。否则,你可能会像我一样,在安全评审会上被审计师问得哑口无言。