
简介本资源是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, bits8, samples1000000, verbose2): cmd [ ./ese_entropyEstimation, -i, path, -b, str(bits), -a, str(samples), --f, normal, --v, str(verbose), ] result subprocess.run(cmd, capture_outputTrue, textTrue) 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 一堆测试撑场面现在更愿意拿最小熵说话。官方这份源代码虽然编译和使用上有一点门槛但它把密码学里最抽象的一个概念变成了可以量化、可以复现、可以自动化验证的工程指标。就这一点来说值得每个跟随机数打交道的人亲手跑一遍。本文还有配套的精品资源点击获取