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 是密钥生成的“操作手册”。它把密钥生成分为三个层次:

  1. 原始密钥材料:来自 TRNG 或熵源,长度至少等于密钥强度(比如 AES-256 需要至少 256 位熵)。
  2. 密钥生成方法:包括直接生成、通过 KDF 派生、或者从共享秘密中提取。
  3. 密钥格式:指定密钥的编码方式(如 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 知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以把它贴在墙上,每次设计密钥生成方案时对照一下:

密钥生成技术知识体系 密钥生成技术体系 物理熵源 (TRNG) 系统熵源 (OS) 辅助熵源 (时间/传感器) 熵池混合与健康检测 CSPRNG (CTR_DRBG / Hash_DRBG / ChaCha20) 密钥材料 (对称/非对称/派生密钥) 密钥验证与审计日志 NIST SP 800-133 覆盖全流程 TRNG 健康检测是强制要求

这张图的核心逻辑是:熵源 -> 混合 -> DRBG -> 密钥 -> 验证。每一步都有对应的标准和最佳实践。你设计密钥生成方案时,按这个链路走,基本不会漏掉关键环节。

最后说一句:密钥生成不是“写几行代码调个 API”那么简单。它涉及硬件选型、合规要求、性能权衡。我建议你在项目初期就把密钥生成方案写进安全架构文档,而不是等到上线前才补。否则,你可能会像我一样,在安全评审会上被审计师问得哑口无言。