
简介这套源代码对应NIST SP800-90B标准用于评估随机数生成器RNG的熵质量适合密码学开发、信息安全测评与随机数应用研究人员。压缩包共21个文件包含13个Python脚本如iid_main.py、noniid_main.py、markov.py、maurer.py等统计测试模块、4个bin随机数样本1bit、4bit、8bit以及PDF用户指南、Markdown说明和DOCX文档整体仅2.07MB。目前已有1201人学习。源码覆盖近似熵、最小熵等核心计算并内置数据预处理、统计测试、阈值设置和结果判定等模块可对独立同分布与非独立同分布数据执行完整熵评估借助附带样本和说明文档能复现检测流程也可在此基础上扩展自定义RNG的熵分析。对于需要验证真随机数生成器TRNG或伪随机数生成器PRNG安全性的团队这套代码提供了可直接运行和改造的测评参考。 做真随机数发生器TRNG的朋友基本都会被同一个问题卡住你说你的熵源质量好拿什么证明NIST SP800-90B就是那个几乎所有密码模块认证都会引用的判定标准而围绕它开源的熵评估源代码则把标准里那些概率统计公式变成了能直接运行的工具。这篇文章我会从源码结构、核心算法、实用操作和避坑经验几个角度把这一套工具真正讲透。如果你是做安全芯片、嵌入式安全或者自己折腾硬件熵源应该会有收获。尤其是想自己动手验证TRNG输出是否达标的人这份代码能帮你省掉大量写统计程序的功夫。1. 为什么需要SP800-90B熵评估1.1 熵在随机数安全里的基石作用密码学里的随机数不是“看起来乱”那么简单。密钥、nonce、初始化向量全都依赖不可预测性。熵就是对这种不可预测性的度量一个8比特的变量如果均匀分布它的熵就是8比特如果某个取值出现概率过高熵就会下降密钥空间随之缩小攻击者就有可能用穷举或者预测的方式击穿安全机制。很多人容易混淆“伪随机数生成器”和“真随机数熵源”。伪随机数生成器PRNG的随机性来自种子和算法只要种子足够好输出可以做到统计上很均匀但它的熵并不会凭空增加。真正的随机性必须来自物理熵源比如热噪声、抖动采样、放射性衰变等。这些物理过程天然带有不确定性但工程实现上往往受温度、电压、工艺偏差影响实际熵值可能远低于理论值。所以不能“觉得”自己采样的数据够随机必须用标准方法去测。SP800-90B的定位就是给熵源“体检”。它不是用来测PRNG输出而是用来测熵源提供的原始噪声数据。标准要求最终交出的是“最小熵”也就是在所有可能输出分布中取最保守的估计值这个值决定了你能从源里放心地榨取多少随机性。如果最小熵是7.2比特你采一个8比特样本最多只能当7.2比特密钥熵用剩下的0.8比特是虚的。1.2 源代码承担的角色标准文档里写满了公式但把公式变成能跑的程序是另一回事。NIST官方开源了一套熵评估工具也就是我们经常说的“SP800-90B熵评估源代码”项目地址是github上的usnistgov/SP800-90B_EntropyAssessment。这套代码把IID检测、非IID检测、熵估计、条件化处理等流程全部实现成可直接调用的命令行工具和Python API。在实际项目中我通常把它当成一个“裁判尺”。流程大概是先从自己的TRNG电路里采集一批原始样本存成二进制文件然后喂给评估工具跑出各个估计器的熵值最终取最小值作为该熵源的保守熵估计。如果这个值能达到设计需求比如要求8比特熵就说明熵源基本可信如果达不到就要调整采样电路、提高过采样倍数或者对原始数据进行“条件化”处理。这套源代码最值钱的地方在于它完整实现了标准里所有评估路径。自己写一个卡方测试很容易但要复现标准里那么多估计器、还要考虑边界情况工作量非常大。直接用官方代码至少能保证评估口径和权威机构的一致免得自己写了一套统计程序结果认证机构不认。2. 源代码整体设计与模块拆解2.1 仓库结构与核心文件官方代码仓库到手之后先别急着运行。我习惯先把目录结构过一遍知道每个文件是干什么的。整体上它是Python脚本配合C扩展的混合项目Python负责业务流程和参数解析C负责计算密集的统计量这样既能快速开发又能保证大样本场景下的性能。核心文件大致有这些python/iid_main.pyIID假设下的熵评估入口。负责读取样本、执行IID测试、计算熵估计值。python/non_iid_main.py非IID假设下的熵评估入口。里面包含了多个非IID估计器最终输出保守的最小熵。python/ea_conditioning_no_header.py条件化处理评估工具用于评估熵源输出经过某种算法比如哈希、CRC压缩之后的熵。python/tls_tool.py用于生成符合特定格式的随机比特流配合FIPS 140-2里的自检流程使用。cpp/C核心实现包括熵估计所需的各种统计函数通过setup.py编译成Python扩展。data/官方提供的一些示例数据文件可以用来验证工具是否安装成功。这个结构很清晰平时使用只要理解iid_main.py和non_iid_main.py就够了。真正写文档或者调试的时候会更多翻cpp/目录下的代码因为Python层只是包装关键数学逻辑都在C里。2.2 从输入到输出的完整数据流把评估工具看成一个管道它的输入是“原始随机样本”输出是“熵估计报告”。这个过程可以拆成四步第一步样本读取。工具会按指定比特宽度-b参数解析输入文件。比如-b 8表示每个样本是8比特文件每个字节是一个样本-b 1表示每个比特算一个样本。这一步看似简单但很容易出问题后面我会专门讲。第二步IID判断。样本进来之后工具会先跑一组统计测试判断这些样本是否满足“独立同分布”Independent and Identically DistributedIID。这组测试包括卡方测试、自相关测试、排列测试等。如果数据过不了IID测试就会走非IID评估路径。第三步熵估计。在IID路径下工具会计算基于“最公共值”“碰撞”“局部计数”等多个统计量的熵估计值然后取最小的那个作为保守结果。在非IID路径下会跑更多复杂的估计器比如基于压缩的估计、基于Markov链的估计等。第四步输出报告。结果会打印到终端包含每个估计器的熵值、最终最小熵以及计算所用的样本量、符号集大小等信息。如果用了--verbose还会把每个中间统计量的数值都打出来方便排查问题。理解了这条数据流你再去看源码时会发现代码结构基本就是按照这个流程组织的读文件、跑测试、算熵、输出。知道了主线就不会在函数堆里迷路。3. 核心算法在源代码中的落地3.1 IID测试与熵估计的实现思路IID测试在iid_main.py里有个很直观的函数调用链。它会先读入所有样本然后针对不同情况跑测试。其中最关键的是判断数据是否IID因为IID和非IID的熵估计方法完全不同。源码里IID测试的几个统计量值得关注。第一个是卡方检验它会计算每个符号出现的频次然后和均匀分布对比看看偏差是否在可接受范围内。第二个是“连续均值差”相关测试它会比较前后样本之间是否有关联性。第三个是“最长重复子串”相关测试用来捕捉周期性模式。这些测试的原假设都是“样本是IID”当p值低于阈值时就会拒绝原假设认为样本不是IID的。如果数据通过IID测试工具会计算熵。官方文档里IID路径下有多个估计器包括基于最公共值的熵估计它只统计出现频率最高的符号给出一个很保守的熵上界。基于碰撞概率的熵估计它统计样本中重复出现相同符号的情况碰撞越多熵越低。基于部分收集的熵估计它将样本分割成不同窗口观察字符出现的新鲜度。最后取这些估计器结果里的最小值作为IID数据的最小熵。这个“取最小值”的设计是标准刻意为之的因为熵估计必须保守宁可低估不能高估。3.2 非IID估计器的多样性大多数真实物理熵源并不会满足严格的IID条件所以非IID路径才是更常见的情况。non_iid_main.py会调用多个估计器每个估计器从不同角度试图估计熵然后同样取最小值作为最终结果。源码里有几个比较有代表性的非IID估计器压缩估计器。它利用通用压缩算法比如gzip对样本进行压缩压缩比越高说明可预测性越强熵越低。它的数学逻辑是压缩率反映了样本的冗余程度和香农熵相关。碰撞估计器。这个不同于IID下的碰撞测试它是基于Markov链模型计算的碰撞概率。它会把样本看成马尔可夫过程考虑当前状态对下一状态的影响。部分收集估计器。它模拟“采样一部分就停止”的场景计算要达到某个多样性需要采集多少样本间接反映熵。这些估计器都有对应参数其中最烦人的是内存占用。比如样本量设到100万个某些估计器要建立转移矩阵内存轻松爆掉。源码里其实做了限制如果样本量过大它会提示减少样本数。所以实际操作时我一般先在100万样本下试跑如果内存吃紧就降到50万但不要低于标准的推荐值。3.3 源码里那些默认参数到底该怎么改直接运行工具时有几个参数是必须理解的不然结果没有意义。最重要的两个是-i输入文件和-b比特宽度。假设你对8比特符号进行熵评估如果误把-b设成1那么每个字节会被拆成8个比特统计结果完全不一样熵值会被压得很低。还有一个隐藏参数是-a或--add之类的选项用来追加数据我经常用它做多组数据合并评估。官方默认的样本量要求其实在标准文档里写了非IID测试至少需要100万个样本吗我没有那么死板但低于1万个样本时结果波动很大不太可信。另外源码里的--verbose参数我非常推荐打开。默认输出只给你一个最终熵值很多中间统计量是被吞掉的。开启之后你会看到每个估计器算出来的熵值、每个测试的p值、以及样本统计信息。调试的时候这些数值能帮你判断是哪一步出了问题。4. 实操复现从源代码到熵值4.1 环境准备与编译在Linux环境里跑这套工具最省心。我的常用流程是git clone https://github.com/usnistgov/SP800-90B_EntropyAssessment.git cd SP800-90B_EntropyAssessment python setup.py build_ext --inplacesetup.py会自动编译C扩展。这里有个容易踩的坑就是系统缺编译工具链。Debian系需要先装g和python3-devsudo apt install g python3-dev如果你用的是macOS装了Xcode Command Line Tools基本就能编译。项目本身不需要安装第三方Python包numpy等工具会在需要时自动调用系统自带版本不过建议提前装好numpy有些辅助脚本会用到。编译完成后目录下会出现一个build文件夹和编译好的.so文件。如果你的Python版本比较新可能还会遇到编译警告一般不影响使用。我建议先用官方data目录下的示例数据跑一遍确认工具能正常运行再换成自己的数据。4.2 准备一份“合格”的原始样本熵评估的输入必须是熵源的“原始噪声”也就是没有被哈希、裁剪或加密处理过的数据。这一点很关键因为任何后处理都会在熵源和输出之间加一层“搅拌”让数据看起来更均匀但评估工具测的是搅拌之前的状态如果喂进去的是后处理输出熵值往往会被高估或低估没法反映熵源本身的质量。自己生成测试样本时可以用一段小脚本从文件或设备节点读取原始数据。比如从/dev/urandom读100万个字节作为演示import os with open(noise.bin, wb) as f: f.write(os.urandom(1000000))注意这只是为了验证工具流程/dev/urandom输出已经是系统处理过的随机数用它跑出来的熵值毫无意义。真正的使用场景是你在自己的TRNG电路上通过SPI/I2C把采样数据传给主机然后原样存成二进制文件不做清洗。如果数据是ASCII十六进制文本需要先转成二进制再喂给工具否则工具会把字符本身的ASCII码当作样本统计结果完全跑偏。4.3 运行评估并解读输出运行IID评估和NON-IID评估的命令分别如下python python/iid_main.py -i noise.bin -b 8 python python/non_iid_main.py -i noise.bin -b 8如果你的样本是比特流把-b 8改成-b 1样本量对应变成原来的8倍。运行结束后终端会打印类似下面的信息Assumption: IID Estimated entropy: 7.983462 bits per sample在非IID路径下你会看到多个估计器的输出例如Compression estimate: 7.95 Collision estimate: 7.82 Markov estimate: 7.71 ... Min entropy: 7.71这个Min entropy才是最终要用的值。判断是否通过需要看它和你系统要求的熵预算之间的关系。比如你后续要用一个256位密钥如果最小熵是每样本7.7比特那么至少需要256 / 7.7 ≈ 34个样本才能提供足够的种子熵。标准还要求熵源在健康测试中能验证这个值所以不要盯着输出里的最大值看一定要看最小熵。如果输出里出现了“Failed”或者“Error”多半是输入数据格式问题或者样本量不够触发估算失败可以直接翻到后面的排查章节。5. 常见问题与排查技巧实录5.1 输入数据格式与字节序问题我遇到最多的问题是输入文件格式搞错。工具默认按无符号字节整数解析如果你的原始数据是带符号的ADC采样值半字节或者12位打包格式直接读进来会把符号位当成数据位导致符号集大小异常熵估计结果失真。正确做法是先把ADC采样值转换成无符号字节流或者用-b参数按实际有效位解析。字节序也很容易踩坑。如果数据来自小端序MCU但你在PC上直接把uint32数组写进文件读取时如果不按小端解析样本会被错位切分。解决方式是统一用二进制文件并在写入时用struct.pack明确指定字节序代码里配合--endian之类的参数如果源码支持调整。实在不确定跑通数据后用官方示例数据对比一下结果就知道格式是否处理对了。5.2 样本量、内存与运行时间非IID评估对大样本特别吃内存。我曾经用100万个12位样本跑某个估计器直接占满8GB内存机器几乎卡死。后来把样本降到20万个内存占用可控但熵值波动变大。实际使用中我建议在满足标准要求的前提下样本量不小于100000但也不要盲目上千万。官方文档对每种估计器的最低样本量有说明按照那个下限附近跑既能保证统计可靠性又不会让电脑爆炸。如果运行时间太长可以通过调低样本量或者只跑特定的估计器来加速。源码里提供了一些选项控制启用哪些估计器比如--no-compress之类的开关可以按需关闭计算量大的模块。但要注意关闭估计器会影响最终最小熵的保守性只能用于快速试探正式评估还是要全跑。5.3 编译失败与Python版本兼容问题老版本工具在Python 3.9以上编译时有时会遇到Cython生成的C代码报错。遇到这种情况最省力的办法是先升级setuptools和cythonpip install -U setuptools cython如果仍然失败检查是不是缺少numpy/random相关头文件装一下python3-dev基本就能解决。还有一个小坑官方仓库里某些脚本用的是相对路径导入如果你在仓库根目录之外执行Python命令会出现模块找不到的错误。解决方法是先cd到仓库目录再运行或者用绝对路径调用脚本。5.4 源码定制的几个方向官方工具更多是给评估用如果想把熵评估嵌到产品里做在线自检可以借鉴它的思路但不需要全量代码。常见的做法是自己实现几个核心估计器比如碰撞估计和卡方检验用C语言写进固件里。此时要注意版权头保留官方代码是公共领域但出于尊重我会在代码里注明来源。如果想要提高评估速度可以把Python层换成C调用直接用cpp/目录下的函数库编译。我试过把IID路径单独抽出来编译成命令行工具跑1万组样本只用了不到100毫秒远比走Python解释器快。对于产线测试来说这种定制是值得的。5.5 关于评估结果的心态问题最后说几句个人经验。熵评估结果不是“及格万岁”我遇到过不止一次原始数据跑出来最小熵很高但实际设备在高温或低电压下输出明显退化。因为熵源输出受环境影响很大评估时最好覆盖多种温度、电压和老化条件多采几组独立数据分别评估而不是把不同条件的数据混在一起跑。每组数据都能达标才说明系统余量足够。另外评估工具给出的最小熵是一个统计估计不是数学证明。如果你的产品面向安全认证这个结果只能作为参考文档的一部分最终还是要通过实验室的现场测试。但拿着这套源代码自己先跑通一遍能大大节省后续送测反复返工的周期。这个价值我觉得比工具本身的数值更有意义。本文还有配套的精品资源点击获取