简介:本资源是NIST SP800-90B随机数熵评估标准的开源实现代码包,面向密码学工程师、安全研究人员及嵌入式随机数源开发者,用于对硬件/软件熵源进行合规性验证与不可预测性量化分析。压缩包共21个文件,含13个Python主程序(如maurer.py、noniid_main.py、chi_square_tests.py等),覆盖近似熵、最小熵、马尔可夫依赖、碰撞测试等核心评估模块;4个二进制测试数据样本(1/4/8位真随机序列),1份PDF用户指南与1份Word操作说明,辅以README和工具函数库,结构完整、开箱即用。资源大小为2.07MB,轻量易部署。已有1024人学习下载,提供从数据加载、预处理、多维度统计测试到结果汇总的全流程脚本支持,特别适合开展熵源实测、算法对比或教学演示,是落地NIST权威标准的关键实践工具。 做随机数质量评估的人,第一眼看到 NIST SP 800-90B 这个编号,就知道这不是一个简单跑一遍就能交差的工具。它的全称是 Recommendation for the Entropy Sources Used for Random Bit Generation,专门用来回答一个问题:你的随机源到底能提供多少真实随机性?这里说的随机源,不是库函数里的 rand(),而是硬件熵源、物理噪声、或者是从系统环境里收集到的不可预测信息。而源代码,指的是美国国家标准与技术研究院连同标准一起发布的熵评估工具实现,它把标准里那套统计检验变成了可以在本地编译运行的程序。这篇文章我会直接基于这套源代码,讲清楚熵评估的来龙去脉、怎么编译运行、怎么解读输出,以及我踩过的坑。如果你在做真随机数发生器验证、密码学产品安全评估,或者只是单纯好奇随机数熵是怎么被量化的,这篇应该能给你省不少时间。
1. 熵评估到底在解决什么问题
1.1 为什么随机数的“熵”这么重要
密码学里,密钥必须不可预测。通常我们使用随机数来生成密钥、盐值、初始向量。假如随机数的熵不够,攻击者就可以缩小搜索空间。之前听说过一些门锁、智能卡产品,因为用了伪随机发生器导致密钥被复现,安全体系直接崩溃。所以一个随机源是否真的“随机”,不是靠感觉,而是需要量化。信息论里用熵来表示某个随机变量不可预测的程度。Shannon熵是平均值,而密码学更关心的是最坏情况,也就是“最小熵”。简单说,最小熵刻画的是攻击者在知道所有统计规律后,仍然无法确定的比特数。NIST SP 800-90B 的评估目标,就是给出这个最小熵的下限。
很多人以为拿随机数去跑一下 NIST SP 800-22(统计测试套件),通过十几个测试就说明随机数没问题。但那些测试只能发现明显的模式,不能直接给出熵值。比如一个输出“01010101”循环的序列,某些统计测试可能误判为随机,但它的熵其实非常低。SP 800-90B 的源代码不会只告诉你“通过”或“不通过”,它会给你一个具体数字:每个样本有多少比特熵。这个数字可以拿去给密钥生成器做安全归约,也可以用来计算需要采集多少原始数据才能生成 256 位密钥。所以对于做安全产品的人,这个数比测试 P 值有用得多。
1.2 NIST SP 800-90B 在这件事里的位置
NIST 的随机数标准谱系大致是这样的:SP 800-90A 定义“确定性随机位生成器”(DRBG),它负责把种子扩展成伪随机序列;SP 800-90B 负责评估“熵源”的熵,也就是喂给 DRBG 之前那个原始随机源;SP 800-90C 则规定基于熵源直接生成随机比特的完整随机位生成器。可以说,90B 是整条链条最上游的质检环节。它的全称里“Entropy Sources Used for Random Bit Generation”就点明了目标:评价熵源,而不是评价 DRBG 的输出。
90B 标准的评估分成两类:一类是熵源产生的样本是否独立同分布(IID),另一类是非 IID 情况下的保守估计。为什么这么分?因为许多硬件熵源的物理过程存在温度漂移、噪声相关、采样周期抖动等,样本之间往往不是严格独立的。如果盲目套用 IID 的统计模型,熵估计会偏乐观。NIST 工具包里同时提供ea_iid和ea_non_iid两个评估器,就是为了区分这两种情形。对于最终认证,一般更看重非 IID 评估给出的下界。
这套标准的源代码是开放提供的,任何人都可以检查实现细节和统计公式,这对于安全评估来说很重要,因为封闭算法很难让人放心。也正因如此,我可以在自己的测试平台上直接编译、修改、验证,而不是只能依赖某个厂商给的黑盒报告。
2. 源代码的整体设计思路与核心概念
2.1 工具链构成:IID、非IID、条件化评估
从官方仓库拉下来之后,代码目录不算复杂。主要可执行程序有ea_iid、ea_non_iid,另外还有针对“条件化”环节的辅助工具。所谓条件化,是指熵源原始输出经过某种不可逆变换(比如 SHA-256、CRC、纠偏算法)之后再输出。这个过程能压缩数据、消除部分偏差。90B 标准允许评估条件化后的输出,而不是只看原始熵源,这给了设计者一些灵活性。辅助工具就是用来评估这类“后处理”输出熵的。
ea_iid和ea_non_iid的差异,在于它们对数据内在结构的假设不同。IID 工具默认每个样本独立且服从同一个分布,计算快,统计检验种类也多。非 IID 工具则放弃这个假设,用更保守的方法估计,计算量明显更大。我在评估一个硬件熵源时,处理 100 万个样本有时要等半分钟以上,如果样本宽度设为 4 字节,等待时间还会进一步增加。所以不能把ea_iid的耗时经验套到ea_non_iid上。
为了让你快速理解工具定位,我把它们的应用场景整理成了下面这个表:
| 工具 | 适用场景 | 输出特点 | 计算量 |
|---|---|---|---|
| ea_iid | 已知或已验证样本独立同分布 | 给出每样本熵估计 | 相对小 |
| ea_non_iid | 通用熵源,不做独立性假设 | 给出每样本最小熵下界 | 较大 |
| 条件化辅助工具 | 评估经过后处理的输出 | 给出条件化后熵估计 | 介于两者之间 |
这里要提醒一句:工具名里的“IID”指的是待评估数据本身的统计性质,不是说你选的熵源一定是 IID。如果事实不符合 IID 假设,ea_iid的结果会偏乐观。所以很多评估流程会先做一个独立性检验,再决定用哪套工具。更稳妥的做法是直接用ea_non_iid,它的结果更保守,也更适合作为最终安全设计的依据。
2.2 源代码里的关键统计量在算什么
非 IID 评估器那一长串测试,名字听起来很抽象——最常值(Most Common Value)、部分碰撞(Partial Collisions)、压缩(Compression)、最长重复子串(LRS)、T 元组等。其实核心思想都差不多:想办法从样本里找出“某种可预测性”,然后换算成熵的下限。
拿最常值估计举例。假设我从一个随机源采了 10 万个字节,如果出现次数最多的那个字节出现了 1000 次,那攻击者猜某个新样本等于这个值,概率至少是 1/1000。对应的最小熵就是 -log2(1/1000) ≈ 9.97 比特。实际上真实熵可能更高,但我们只能保守地说,最低不会低于这个值。MCV 方法还会结合频率排序,给出更紧的下界。
部分碰撞则换了个角度:统计两个样本出现相同值的频率。如果独立均匀分布,相同的概率大约是 2^-8(按字节算)。如果实际碰撞频率明显更高,说明分布有偏或者样本之间有关联,需要降低熵估计。压缩算法更直观:一个文件如果能被 gzip 压得很小,说明它包含大量可预测结构,熵一定高不了。工具里用的压缩估计不是直接调用 gzip,而是基于 LZ 算法设计的一类估计器,目的是一样的。
T 元组方法会看连续 T 个样本组成的短序列出现的模式,用来捕捉样本之间短程的相关性。这些都是从不同角度去“攻击”数据,最终取所有估计里面最低的那个,作为每样本熵的下界。所以你会看到输出结果里有多个估计值,最终报告的是最小估计。这种“取最保守”的设计,正是安全评估该有的态度。
3. 用源代码做一次完整评估的实操记录
3.1 环境准备与编译
我用的环境是 Ubuntu 22.04,代码是 C 语言写的,依赖 OpenSSL 的哈希库。安装依赖和编译的命令如下:
sudo apt update sudo apt install build-essential git libssl-dev git clone https://github.com/usnistgov/SP800-90B_EntropyAssessment.git cd SP800-90B_EntropyAssessment make如果你的系统是 CentOS/RHEL,把libssl-dev换成openssl-devel;macOS 上可以用 Homebrew 安装openssl,并设置CFLAGS指向头文件目录。make成功后,目录下会生成ea_iid、ea_non_iid等可执行文件。如果只想评估原始熵源,核心就是这两个。
编译过程中最常遇到的问题是找不到openssl/sha.h。这是因为系统里只有 OpenSSL 运行时库,没有安装开发头文件。Ubuntu 下安装libssl-dev就能解决。另一个问题是交叉编译或平台差异导致make里某些默认参数不对,这时候不要硬改源码,先看Makefile里的CFLAGS,把头文件路径加进去一般就好。
编译完成后,可以用./ea_iid或./ea_non_iid不带参数运行,会打印用法。我实际操作时发现,它默认从标准输入读取二进制数据,也可以直接把文件名作为参数传给部分命令。具体以当时的帮助信息为准。
3.2 准备待评估的随机数样本
工具需要一个二进制样本文件。它默认把每个字节当作一个样本,所以文件长度就等于样本数量。这个细节很关键:如果你的熵源输出 16 位或 32 位的数据,要么改源码里的样本宽度,要么先按字节拆分。我建议先看一下工具的说明,通常会要求每个样本为 1 字节,并且至少提供几十万个样本,否则统计结果没有意义。
第一次做实验时,可以用系统熵源/dev/urandom生成一个测试文件:
dd if=/dev/urandom of=random.bin bs=1024 count=1024这会生成 1MB 的数据。注意,/dev/urandom的输出本身已经经过了内核的伪随机化处理,并不适合拿来评估一个“原始熵源”。它更适合用来验证工具流程是否能跑通,或者作为对照样本。真正的评估对象应该是你自己设计的硬件熵源输出、光电噪声采集数据,或者任何未经过密码学后处理的物理随机数据。
在采集真实熵源时,有几个建议:样本要连续采集,不要用过滤掉特定模式的数据;记录采集时的环境参数,比如温度、电压;建议多批次采集,分别评估,观察熵估计是否稳定。单一一次评估结果不能代表熵源在所有条件下的表现,所以这里我通常至少采 10 组数据,每组 100 万个样本。
3.3 执行评估命令与结果解读
假设文件已经准备好,接下来就是执行评估。我通常会把文件通过标准输入喂给工具,这样避免处理命令行参数里的路径转义问题,而且无论文件放在哪,命令都一样。命令本身很简单:
./ea_non_iid < random.bin程序会先读取全部数据,然后输出一串统计信息。我这边跑出来的输出大致长这样,不过不同版本措辞会有差异:
Reading 1048576 samples of 1 byte... Running non-IID entropy tests... Most Common Value Estimate: 7.931 Partial Collection Estimate: 7.902 ... H_original = 7.902 bits per sample重点看每样本熵有多少比特。如果样本是 1 字节,理论上限是 8 比特。如果评估结果接近 8,说明数据看起来足够不可预测;如果结果明显小于 8,说明熵源有偏差、有相关性或者采集条件有问题。H_original这类的字段代表原始样本的最小熵估计,也就是最终要用的下界。
ea_iid的输出会包含更多传统的统计检验名称,比如频率检验、块内频率、游程检验等。它给出的熵估计通常比ea_non_iid高一些,因为 IID 假设本身更强。如果你发现两者的结果差异很大,不要急着惊讶,这恰恰说明你的数据并不独立同分布,最终报告应该采用非 IID 的结果。
评估工具不直接判断“通过”或“失败”,它给的是一个熵值。如何判断是否达标,要看你的应用需求。比如你想用熵源生成 256 位密钥,并希望每个密钥至少有 256 比特的安全强度,那么如果每样本熵是 8 比特,你就至少需要采集 32 个样本;如果每样本只有 4 比特,你就要采集 64 个样本。这类计算在密码方案设计时很常见。
4. 源代码阅读与二次开发要点
4.1 主流程与数据读取
如果你不只是想用工具,还想改算法或者嵌入到自己的测试系统里,读懂源码结构就很重要。按照我读这份源代码的经验,主流程比较清晰:程序入口接收参数,然后从标准输入或文件读取二进制数据到内存缓冲区;接着根据是 IID 模式还是非 IID 模式,调用不同的统计计算模块;最后把各个估计结果汇总,输出到标准输出。数据读取部分通常会用动态分配的方式,因为样本量可能很大,不能预先固定上限。
代码里核心的统计函数集中在几个 C 文件里。文件名往往能直接看出作用,比如randomness_tests.c放的是各类统计测试的实现,utility.c放的是数据读取和辅助函数。ea_non_iid对应的主文件会依次调用 MCV、部分碰撞、压缩估计等函数,然后把结果放在一个数组里,排序后取最小值作为最终输出。
读这份源码不需要一次看懂所有统计公式,我觉得更好的方式是从数据流入手:先看数据怎么读进来,再找一个简单统计量比如 MCV 的实现,顺着函数调用看它怎么计算概率。其他的算法结构都差不多。这样读下来,至少能搞清楚“输出里的那串数字是从哪些统计量来的”,比逐行啃数学推导效率高。
4.2 如何把评估工具嵌入自己的代码
实际项目中,我一般不会每次手动跑命令,而是写一个脚本或者封装成工具调用。最简单的嵌入方式是直接调用命令行,在启动评估时读取输出。比如用 Python 打包执行:
import subprocess with open("random.bin", "rb") as f: result = subprocess.run( ["./ea_non_iid"], stdin=f, capture_output=True, text=True, timeout=600 ) print(result.stdout)这种方式适合自动化评估流程。如果你想做得更底层,可以把ea_non_iid对应的统计函数编译成库,暴露一个接口,输入样本数组、样本数量,返回熵估计值。不过这需要梳理源码里依赖的全局状态,改动量有点大。对于多数场景,命令行包装已经够用。
还有一点,评估过程是无状态的,同一个文件跑多次结果应该一致。如果你的代码里准备长期反复调用,可以考虑把工具输出解析成结构化数据(比如 JSON),方便后续比较多次测试结果。我在自己的测试平台上就是这么做的,每次提交版本后自动跑一遍熵评估,把结果记录到报告里。
5. 常见问题与排查技巧
5.1 编译与部署问题
我先把编译期容易踩的坑整理成一个速查表,方便对照:
| 现象 | 原因 | 解决办法 |
|---|---|---|
| 找不到 openssl/sha.h | 缺少 OpenSSL 开发头文件 | Ubuntu 装libssl-dev,CentOS 装openssl-devel |
| make 报错 “recipe for target failed” | 源码依赖旧版编译器特性 | 升级 gcc/make,或检查 Makefile 的 CFLAGS |
| 可执行文件运行报段错误 | 输入文件格式不匹配或样本数过大 | 先检查是否二进制文件,样本数是否合理 |
| 在 Windows 下编译失败 | 依赖 POSIX 标准输入和信号处理 | 建议用 WSL 或 Cygwin 编译运行 |
如果编译时看到一些跟sha256相关的报错,说明 OpenSSL 库加载有问题。可以去确认一下当前默认的库搜索路径,必要时用LIBRARY_PATH环境变量指定。我试过在旧版 Ubuntu 18.04 上编译,发现默认 gcc 版本稍老,使用-std=c99参数编译就能解决问题,但新版工具可能早已默认开了,所以遇到问题先看Makefile再动手。
另外,不要直接把跨平台编译出来的可执行文件到处拷贝。这个工具对数据读取和内存分配有一定平台相关性,A 机器上编译出来的二进制,拿到 B 机器上可能因为动态库版本不同直接启动失败。我见过有人在服务器上编译完拷到嵌入式设备里,结果缺了 GLIBC 符号,白折腾半天。在目标机器上重新编译是最稳妥的。
5.2 运行与结果判断问题
运行阶段的问题比编译更多。最典型的是:数据量不足。工具会提醒你样本太少,统计估计不可靠。我建议至少提供 100 万个样本(1MB),越多越好。但也不是越多越慢,非 IID 评估的复杂度会随数据量上升,测试时间会明显增加,所以如果你的硬件熵源可以连续输出,先采 1MB 试跑,再决定要不要增加。
另一个常见情况是输出的熵估计接近 0。这通常不是工具坏了,而是输入数据质量太差,比如熵源根本没工作、输出恒定为同一个值,或者数据被截断成了 ASCII 文本。有次我拿一个串口采样文件直接喂给工具,结果熵值只有 0.3,后来发现串口波特率配置错了,采回来的全是重复字节。所以出现低熵值,先检查采集链路。
还有个容易误解的点:输出有多个估计值,最终应该取最小值作为“最小熵”,而不是取平均值。很多人看到某个估计是 7.9,另一个是 7.8,心里默默想“平均一下差不多 7.85”,这在安全评估里是绝对不允许的。SP 800-90B 之所以设计成取保守下界,就是为了防止攻击者利用“平均值高但某些部分可预测”的弱点。
如果遇到运行时间异常长,可以看一眼是不是把样本宽度搞大了,比如每个样本 4 字节会导致统计复杂度成倍增加。另外确认评估模式是不是ea_non_iid,这个模式本身比ea_iid慢,我很早以前误以为程序卡住了,后来发现是机器本身在跑一个很重的压缩估计。
6. 个人经验与建议
6.1 我对这套评估工具的真实感受
用过几次之后,我的最大感受是:这工具不像普通开源软件那样“开箱即用”,它在设计上非常接近标准文本,每个输出都和标准里的某个公式对应。所以如果你只是需要“大概看下随机数好不好”,用ea_non_iid跑一次就够了;但如果你要给产品做安全论证,恐怕得结合标准原文逐项核对,甚至自己补充一些测试,比如物理层复现性检测。工具本身不关心你的熵源是热噪声还是振荡器抖动,它只对着数据说话,这一点反而让我省心。
还有一点,这个工具包的统计量设计比较保守。我在评估一个表现良好的硬件熵源时,ea_non_iid给出的最小熵比项目组内部的模型预测低了不少。一开始大家怀疑工具是不是有问题,后来我们细读公式,发现保守下界是故意把“最坏情况”考虑进去。安全评估里,低估熵比高估熵安全得多,所以最终产品文档里还是采用了工具的结果。
6.2 给初学者的几个实操建议
如果你刚接触这份源代码,我建议按下面这种方式推进,可以少走弯路:
- 先跑通
ea_iid和ea_non_iid的完整流程,确认环境没问题; - 准备一份已知质量的对照数据,比如系统随机数文件,用来观察工具输出是否和直觉一致;
- 再准备一份明显有规律的数据,比如全零或递增序列,看看熵估计是否接近 0,这样可以验证工具能不能发现“坏数据”;
- 最后才拿真实熵源做正式评估,并且至少做多组测试,观察稳定性。
在正式报告中,我会把一次评估的所有输出、使用的版本、命令行参数、样本采集环境都记录下来。这样如果后来输出有变化,能快速定位是熵源变了还是工具版本变了。很多团队因为少了环境记录,排查问题时要重复做大量实验,白白耽误时间。
最后分享一个小技巧:如果你只是想确认一个熵源是否满足某个设计指标,可以写个简单的门槛判断脚本,从输出里抓取最小熵值,然后和你的需求阈值做比较,低于阈值就报警。我在每次硬件改版后都会自动跑一遍评估,脚本发现了两次因为电路焊接问题导致的熵值暴跌,省了大量人工观察。用这种自动化方式,就不需要每次手动盯着一大段输出看,投入产出比很高。
本文还有配套的精品资源,点击获取