ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

NIST SP 800-90B熵评估源码实战:随机数质量验证与最小熵计算

NIST SP 800-90B熵评估源码实战:随机数质量验证与最小熵计算 简介本资源是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-develmacOS 上可以用 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 ofrandom.bin bs1024 count1024这会生成 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], stdinf, capture_outputTrue, textTrue, timeout600 ) print(result.stdout)这种方式适合自动化评估流程。如果你想做得更底层可以把ea_non_iid对应的统计函数编译成库暴露一个接口输入样本数组、样本数量返回熵估计值。不过这需要梳理源码里依赖的全局状态改动量有点大。对于多数场景命令行包装已经够用。还有一点评估过程是无状态的同一个文件跑多次结果应该一致。如果你的代码里准备长期反复调用可以考虑把工具输出解析成结构化数据比如 JSON方便后续比较多次测试结果。我在自己的测试平台上就是这么做的每次提交版本后自动跑一遍熵评估把结果记录到报告里。5. 常见问题与排查技巧5.1 编译与部署问题我先把编译期容易踩的坑整理成一个速查表方便对照现象原因解决办法找不到 openssl/sha.h缺少 OpenSSL 开发头文件Ubuntu 装libssl-devCentOS 装openssl-develmake 报错 “recipe for target failed”源码依赖旧版编译器特性升级 gcc/make或检查 Makefile 的 CFLAGS可执行文件运行报段错误输入文件格式不匹配或样本数过大先检查是否二进制文件样本数是否合理在 Windows 下编译失败依赖 POSIX 标准输入和信号处理建议用 WSL 或 Cygwin 编译运行如果编译时看到一些跟sha256相关的报错说明 OpenSSL 库加载有问题。可以去确认一下当前默认的库搜索路径必要时用LIBRARY_PATH环境变量指定。我试过在旧版 Ubuntu 18.04 上编译发现默认 gcc 版本稍老使用-stdc99参数编译就能解决问题但新版工具可能早已默认开了所以遇到问题先看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这样可以验证工具能不能发现“坏数据”最后才拿真实熵源做正式评估并且至少做多组测试观察稳定性。在正式报告中我会把一次评估的所有输出、使用的版本、命令行参数、样本采集环境都记录下来。这样如果后来输出有变化能快速定位是熵源变了还是工具版本变了。很多团队因为少了环境记录排查问题时要重复做大量实验白白耽误时间。最后分享一个小技巧如果你只是想确认一个熵源是否满足某个设计指标可以写个简单的门槛判断脚本从输出里抓取最小熵值然后和你的需求阈值做比较低于阈值就报警。我在每次硬件改版后都会自动跑一遍评估脚本发现了两次因为电路焊接问题导致的熵值暴跌省了大量人工观察。用这种自动化方式就不需要每次手动盯着一大段输出看投入产出比很高。本文还有配套的精品资源点击获取
返回列表