简介:本资源是NIST SP800-90B随机数熵评估标准的开源实现代码包,面向密码学工程师、安全研究人员及嵌入式随机数源开发者,用于对硬件/软件熵源进行符合国家标准的熵值量化与合规性验证。压缩包共21个文件,含13个Python主程序(如maurer.py、noniid_main.py、iid_main.py等,覆盖近似熵、最小熵、马尔可夫依赖、碰撞测试等核心算法)、4个二进制测试数据样本(1/4/8位真随机序列)、1份PDF用户指南、1份Word版操作说明及配套工具脚本,整体大小2.07MB。已有1024人学习下载,资源结构清晰,模块化设计便于快速集成至熵源验证流程;提供完整可运行的评估链路,支持从原始比特流输入到熵率输出的端到端分析,并内置NIST推荐的统计检验套件与预测器评估模块,显著降低SP800-90B落地门槛。 写随机数发生器的人最怕什么?不是算法实现复杂,而是你拿不出证据证明自己的随机源是安全的。密钥、nonce、盐值、挑战量,任何一个环节如果底层熵不够,整个密码体系就是纸糊的。可“熵够不够”这种问题,不能靠拍胸脯,得用统一标准去量。这就回到安全圈绕不开的 NIST SP800-90B 标准——专用于熵源评估的推荐规范,NIST 官方还放出了配套的熵评估源代码。这篇文章就从这份源代码出发,聊聊怎么把它编译跑起来、怎么解读报告、以及文档里不会写的一堆坑。无论你是做硬件随机数发生器、嵌入式安全,还是只是好奇“随机数到底怎么量化”,这篇都能给你点实在的东西。
1. SP800-90B到底在解决什么问题:熵源评估不是随机数跑分
1.1 随机数测试有两条线:90A管产线,90B管原料
很多人在接触 SP800-90B 之前,先接触的是 SP800-22。那套测试包含频数、游程、块内频数等十几项,用来检验一个输出序列“看起来是否随机”。这个工具很有名,但也容易让人误入歧途——SP800-22 通过只能说明序列在统计形态上没有明显缺陷,并不能证明这个序列背后的不可预测性足够。
如果打个比方,SP800-22 更像给产线成品做外观抽检,而 SP800-90A、SP800-90B 管的是另一层东西。90A 规定的是确定性随机比特生成器(DRBG),也就是把一小段种子扩展成大量随机输出的算法框架。90B 则完全不同,它管的是 DRBG 上游那个更原始、更底层的部件——熵源。熵源可以是芯片里的热噪声放大电路、环形振荡器采样、系统中断时间抖动,甚至是你手动收集的某种物理过程。这类东西输出的是“含熵的原始样本”,而不是均匀分布的完美随机数。
90B 要回答的核心问题只有一个:这个熵源在每次采样中,到底能提供多少个不可预测的比特。注意,是“每次采样的最小熵值”,而不是“一整批数据的总熵”。这个区别非常重要,因为密码协议里只要某一次密钥生成的熵不足,攻击者就可能精准地缩小搜索空间,其余九千九百九十九次再安全也救不了。
1.2 为什么密码学只认最小熵,而不是香农熵
信息论里经常提香农熵,它描述的是一个随机变量的平均不确定度。但对密码学来说,平均意义不够用。攻击者往往只关心“最容易猜中的那个值猜中的概率有多大”,这个量对应的是最小熵。公式很简单:H_min = -log2(p_max),其中 p_max 是出现概率最高的那个取值的概率。
举个例子,一个随机变量有三个取值,概率分别是 0.9、0.05、0.05,它的香农熵大约 0.569 比特,听起来还行。但最小熵只有 -log2(0.9) ≈ 0.152 比特。攻击者只要一直猜最可能的值,猜中的概率接近九成。密码学里必须按最坏情况设计,所以 SP800-90B 的整个评估框架都以最小熵为核心指标。官方源代码最终给出的也是“每样本最小熵”和“每比特最小熵”,而不是告诉你“这批随机数质量不错”。
这个设计理念贯穿了整份标准,也决定了源代码里那些测试为啥选得如此刁钻——它不是为了证明你的随机数好看,而是为了逼出最坏情况下的熵下限。
2. 源码部署:官方熵评估套件的文件结构与编译排雷
2.1 官方仓库里到底放了些什么
NIST 公布的熵评估源码仓库名是 SP800-90B_EntropyAssessment,核心实现是 C 语言。仓库顶层不是一锅乱炖,结构很清晰:
c/:C 语言实现的主目录,里面有 Makefile 以及ese_entropyEstimation.c、多个c_*.c/.h文件,编译后的可执行文件也在这里。python/:Python 版本封装,适合不想碰 C 或者希望把评估逻辑嵌入自动化脚本的人。tests/或examples/:部分版本带了样例数据和验证脚本,我一般直接用/dev/urandom或者自己采集的熵源裸数据来试,样例数据更主要用于回归验证。README.md:官方使用说明,信息密度很高,值得先看三遍。
我在第一次拿到源码时犯过一个错误:直接盯着entropyEstimation.c看名字,以为这是入口文件。后来发现新版入口已经改叫ese_entropyEstimation.c,旧版那个entropyEstimation可执行文件在后续版本里基本退休了。看源码先看 README 和 Makefile,别凭文件名猜,这是第一个经验。
2.2 编译环境与 Make 报错处理
编译并不难,但有几个依赖绕不开。官方源码里大量使用了 OpenSSL 的哈希函数来做重采样、压缩估计等操作,所以系统里必须装 OpenSSL 开发库。我平时在 Ubuntu/Debian 环境下的准备工作是:
sudo apt install build-essential libssl-dev git git clone https://github.com/usnistgov/SP800-90B_EntropyAssessment.git cd SP800-90B_EntropyAssessment/c make正常情况下,完成后会看到当前目录下多出一个ese_entropyEstimation可执行文件。如果make报找不到openssl/evp.h,基本就是libssl-dev没装上;若报一堆链接错误,多半是 OpenSSL 版本造成的符号兼容问题,常见于老旧系统。我遇到过在 CentOS 7 上编译新版源码时,因为系统 OpenSSL 版本偏低,某些哈希接口的签名对不上。当时我的处理方式是装一个较新的 OpenSSL 到/usr/local/ssl,再用export LD_LIBRARY_PATH=/usr/local/ssl/lib指过去重编,能解决大部分链接问题。
Windows 下编译相对麻烦,我试过用 MinGW-w64 能编过,但 OpenSSL 的路径配置会折腾人。如果只是评估用,更省事的方案是装个 WSL,在 Linux 子系统里走一遍上面的命令,十分钟就能跑通。或者直接用仓库里的 Python 版本,什么都不用编。
2.3 新旧版本参数差异:网上老教程容易坑人
如果你搜索过这个工具的用法,大概率会看到老版本命令:
./entropyEstimation -i /path/to/data.bin -b 8 -a 1000000 --iid这个写法在旧版里能用,参数含义也直观:-i指定原始二进制数据文件,-b指定每个样本的比特数,-a指定读取的样本数量,--iid表示我只想跑 IID 评估。但到了新版里,入口换成了ese_entropyEstimation,而且加了一个必填参数--f,也就是 namespace。刚开始我按照老教程跑,几条命令都被参数解析直接拒掉,才意识到版本差异。
新版推荐命令长这样:
./ese_entropyEstimation -i /path/to/data.bin -b 8 -a 1000000 --f normal --v 3--f normal表示这份数据是在常规工作状态下采集的。除此之外,官方还定义了boot、restart、time等命名空间,分别对应系统重启时、熵源重初始化后、以及带时间戳场景下的采集数据。这个参数不是摆设,它影响 Restart 测试阶段如何组织数据。--v 3是拉高日志详细级别,让工具把每个测试的 p 值、中间结果都打出来。调试时我习惯开--v 3,量产验证时则关掉,只看最终结论。
另外再提醒一个容易忽略的点:数据文件必须是原始二进制格式。你从串口工具导出的是 ASCII hex,那直接喂进去会得到一堆离谱结果。我在帮朋友调一块开发板时,发现他导出的文件是文本格式,工具解析出的样本值全是 0x30~0x39 这种 ASCII 码,最后熵评估当然全部失真。先xxd看一眼文件头,确认是二进制,再干活。
3. 逐项拆解评估逻辑:IID、非IID与Restart到底在测什么
3.1 IID 十大测试:每个子测试都是什么用意
跑评估时,工具会先把整个数据集拿去做 IID 判定。如果数据能被认定为独立同分布,就走 IID 熵估计路径;如果认定不是 IID,就进入更复杂的非 IID 评估。官方源代码里实现了 SP800-90B 中规定的十个 IID 测试,各有针对:
- 压缩测试:用通用压缩算法对序列压缩,如果压缩率异常低,说明序列存在可利用的冗余。
- 卡方测试与分部卡方测试:检查各取值出现频率是否偏离均匀分布。
- 最长重复子串测试(LRS):检测是否存在超长重复片断,这是很多弱随机源的通病。
- 排列测试:对序列做排列变换后观察统计量是否异常。
- 频率测试、累计和测试、运行测试、最长运行测试:这一类都是经典随机性检验,重点捕获序列的结构化偏差。
很多人第一次跑通后,看到工具说“IID tests passed”就以为万事大吉。但标准里有个容易被忽视的细节:这十个测试的判定阈值取 p-value >= 0.0001,而不是常见的 0.05。如果你想复现标准文档里的判定逻辑,别把阈值改成常见的显著性水平。源码里写死的就是 0.0001,这是个相当宽松的门槛——因为 IID 测试的目的是筛选出“明显不合群”的数据,而不是严格证明独立性。真正的熵估计还是要靠后续的最小熵评估兜底。
3.2 非 IID 熵估计器:保守主义的极致表现
当数据被判为非 IID 时,工具会跑另一套熵估计器。这类估计器各自从不同角度估计每样本的最小熵,最后取所有估计器结果的最小值。核心思想很直接:任何一种可能被攻击者利用的规律性,都必须在最终熵值里体现出来。
这部分估计器包括:
- 最公共值估计(MCV):直接看出现频率最高的取值,按最小熵公式计算。
- 碰撞估计:统计不同取值发生碰撞的速率,碰撞过快说明样本空间实际很小。
- 马尔可夫估计:假设样本存在一阶或高阶依赖,估算条件熵。
- 压缩估计:基于 LZ78 等压缩算法,压缩率越高则信息量越低。
- 部分收集估计、图灵估计等:针对小样本或者稀有取值场景做修正。
我在实践里的心得是:非 IID 路径给出的熵往往比 IID 路径低得多,这不是工具出了 bug,而是它默认攻击者知道你的熵源模型,并且能利用所有可观测的统计规律。对于硬件噪声源,如果设计时让原始采样直通不进行任何去相关处理,非 IID 评估结果常常惨不忍睹。这恰恰说明你需要在熵源之后加上合适的后处理,比如哈希抽取或 von Neumann 去偏,才能让输出更接近 IID。
3.3 Restart 测试:系统重启场景下最容易被忽略的软肋
很多人不知道,SP800-90B 的评估不只针对稳定运行状态,还包括重启状态。源码在 2019 年后的版本中强化了 Restart 测试,要求提供不同 namespace 下的数据,比如正常工作数据、重启后立即采集的数据、带时间信息的数据。这是因为很多熵源在系统上电或重新初始化后的一小段时间内,输出质量极不稳定。如果攻击者正好在重启窗口期请求随机数,就可能拿到熵极低的序列。
我第一次意识到这个问题,是在评估一款嵌入式设备时。设备正常采集时熵评估结果很好,但在模拟“冷启动后立即采样”时,输出几乎是常数。原因很简单:芯片刚上电,时钟还没稳定,噪声源电路也没进入正常状态。官方工具里的--f boot和--f restart就是为了把这种脆弱场景暴露出来。如果你做 FIPS 认证或者对接合规审查,Restart 测试绕不开。
4. 完整评估实战:从数据采集到看懂输出的排查链路
4.1 如何构造一个合规的输入文件
采集数据看起来简单,实际有不少讲究。首先要明确你评估的对象是“原始熵源输出”还是“后处理之后的数据”。这两种选择对应的结论完全不同。官方标准建议先评估原始熵源,再评估后处理后的输出,这样你能知道后处理到底贡献了多大的熵提升。
我通常这样构造数据:让熵源连续采样,把每次采样的原始比特原封不动写入文件,不经过任何滤波和格式转换。写文件时用二进制模式,不要加换行符,也不要用文本接口。如果数据采集工具是自研的,优先用fwrite之类直接写内存缓冲,而不是fprintf("%x")转成文本,否则后面解析会非常痛苦。
命令执行示例:
# 假设每个样本是 8 比特,采集 100 万个样本 ./ese_entropyEstimation -i raw_entropy.bin -b 8 -a 1000000 --f normal --v 3如果文件里没有那么多样本,-a可以省略,工具会读到文件末尾。但我建议还是显式指定样本数量,这样既方便复现,也能避免因为文件尾部有残留数据导致误判。
4.2 读懂评估报告:从 H_original 到 H_bit
评估结束后,控制台会输出一大段结果。对我这种习惯跳过过程的人,一开始真被搞晕了。后来我总结出一套阅读路径:
- 先看 IID 测试结论:如果所有测试通过,工具会走 IID 路径,后续熵值会相对高。
- 再看
H_original:这是针对原始样本的熵估计,单位是“每样本多少比特”。 - 然后是
H_bitstring:把样本按比特拆开后重新估计的熵值。如果样本是 8 比特,H_bitstring除以 8 才是每比特熵。 - 最后是
H_IID或H_nonIID:工具会取保守的估计作为最终“每样本熵”,并在报告末尾给出对应的每比特熵。
一个典型的输出片段大致是:
H_original: 7.924156 H_bitstring: 0.990519 H_IID: 0.990519 min entropy: 0.990519 bits per bit看到这种数值,说明这个 8 比特采样的熵源差不多接近“每个样本提供 7.9 比特”的水平,折合每比特约 0.99 比特。当然,H_bitstring是每样本对应的比特熵,如果样本是 8 比特,这里的数值应该接近 7.92/8 ≈ 0.99。工具会直接换算好,不用自己心算。
如果是非 IID 路径,输出里会列出各个估计器的每样本熵估计,最终取最小值。这时候哪怕H_original显示 6.5,最终 min entropy 也可能只有 2.3。别觉得离谱,这是工具在按最坏情况给你打预防针。
4.3 熵评估不合格时的排查链路
评估不通过,输出 p 值一堆 0,或者最终熵值低到没法用。这时候别急着怀疑工具,按这条链路排查,能省下不少时间。
先检查数据格式和采集链路。最常见的坑就是 ASCII 文本文件冒充二进制。用xxd看一眼开头,如果看到一溜0x0a、0x0d之类的换行符,基本就是格式错了。
然后检查采样位数。同一个噪声源,-b 8和-b 1的评估结果可能差别很大。我在一块 FPGA 开发板上测过一款环形振荡器噪声源:按 8 比特采样评估时,每比特熵只有 0.3 左右,数据还非 IID;但按 1 比特采样也就是只取最低位评估,每比特熵反而接近 0.9。原因在于振荡器抖动的主导位集中在低位,高位几乎被电路偏置固定住了。后来我在后处理逻辑里只保留低位几个比特,再用哈希抽取,整体熵才上来。
再往下查环境因素。噪声源周围如果有强电磁干扰、电源纹波过大、探头接触不良,输出序列会呈现周期性重复。这种问题在工具输出里表现为运行测试和累计和测试 p 值极低。我遇到过最邪门的一次,是示波器地线没接好,导致熵源输出变成方波,评估工具直接给出 0 熵。
排除工具误用之后,如果熵仍然不足,那就得从熵源电路设计上找原因了。这时候评估工具其实是在帮你做质量审计,而不是给你判死刑。
5. 把评估工具接进自己的随机源验证流水线
5.1 用 Python 包装器实现批量自动化
评估不能只跑一次就完事,熵源质量会随温度、电压、器件老化漂移。我自己的做法是把评估工具包进 CI 流程,每次固件构建或硬件改版后,自动采集一批数据,跑一次完整评估,失败就阻断发布。
如果不想在每台机器上都编译 C 代码,可以直接用仓库里的 Python 模块。官方提供的 Python 包装器封装了 IID 和 Non-IID 的评估入口,逻辑上跟 C 版本一致。一个简化的自动化脚本可能是这样的:
import subprocess import glob import sys def evaluate_sample(path, bits=8, samples=1000000, verbose=2): cmd = [ "./ese_entropyEstimation", "-i", path, "-b", str(bits), "-a", str(samples), "--f", "normal", "--v", str(verbose), ] result = subprocess.run(cmd, capture_output=True, text=True) return result.stdout, result.returncode for data_file in glob.glob("samples/*.bin"): out, code = evaluate_sample(data_file) # 在这里可以解析输出中的 min entropy 字段 # 低于阈值就标记为 failed实际生产里,我还会用--f boot分别采集冷启动样本,跟正常工作样本一起纳入回归。这样既能检测正常状态下的熵,也能捕获重启状态下的退化。
5.2 三种常见熵源的评估心得
最后聊几句针对不同熵源类型的评估经验。
对于硬件真随机数发生器(TRNG),我的建议是必须同时评估原始噪声源和后处理输出。很多芯片厂商会给一份漂亮的评估报告,但那往往是理想电压温度下的结果。自己拿板子在不同温度、不同供电电压下各采一份数据,跑一遍官方工具,比什么都可信。我做过一次恶劣环境测试:电压降到标称值的 90%,某些批次芯片的熵值直接掉了 40%。这种隐性衰减,单靠功能测试根本发现不了。
对于系统级熵源,比如 Linux 的/dev/urandom底层,评估时需要注意采样窗口。如果采集数据的时间跨度太长,中间系统状态的熵贡献会引入在普通场景下不存在的多样性,导致熵值虚高。更合理的做法是固定采集时间窗口,比如每次只采集 5 秒,模拟实际密码操作中的短时请求场景。
对于嵌入式设备里的软件熵源,比如基于中断时间抖动、内存地址随机化这类方案,评估结果波动会很大。我会多跑几轮,每次重新上电,观察熵值方差。如果两轮之间熵值忽高忽低,说明熵源对初始状态依赖过重,后续加一个 DRBG 做输出抽取几乎是必须的。
把 SP800-90B 评估工具接到自己的验证流水线之后,我对“随机数质量”这件事的认知踏实了不少。以前靠跑 SP800-22 一堆测试撑场面,现在更愿意拿最小熵说话。官方这份源代码虽然编译和使用上有一点门槛,但它把密码学里最抽象的一个概念变成了可以量化、可以复现、可以自动化验证的工程指标。就这一点来说,值得每个跟随机数打交道的人亲手跑一遍。
本文还有配套的精品资源,点击获取