ARTICLE DETAIL

资讯详情

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

NIST STS随机数测试套件:从下载编译到结果解读的完整实战指南

NIST STS随机数测试套件:从下载编译到结果解读的完整实战指南 拿到一个随机数生成器或者自己写完一段PRNG代码第一件事是什么我一般不做功能自测而是直接丢给 NIST 随机数测试软件跑一遍那十几个统计测试项看它能不能撑住 100 万 bit 的序列检验。NIST STSStatistical Test Suite在随机性评估领域基本就是默认标准不管是做密码学模块、硬件真随机数源TRNG、还是嵌入式系统的熵源验证最后拿出手的报告总得有几行 P-value 和通过率。这篇教程我会从下载渠道、编译安装、参数选择到报告解读全部过一遍兼顾刚入门的新手和已经在项目里用过的老手争取不让你走我当年走过的弯路。这个工具能解决的问题很直接随机数“看起来乱”和“统计学上合格”完全是两码事。你在终端里打印一串数字觉得挺随机但拿到频数检测、游程检测、块内最大游程检测里跑一圈可能立刻露馅。NIST SP 800-22 定义的这套测试套件就是用来判断一个二进制序列是否具备随机序列应有的统计特征评估对象包括密码算法产生的伪随机数、硬件 RNG 采样数据以及各类熵源输出。适合参考这篇教程的人包括正在做安全模块开发的软件工程师、需要验证硬件熵源质量的嵌入式开发者、研究随机数算法的学生以及任何需要在项目验收文档里附上一份正式随机性测试报告的人。1. 随机数测试这件事为什么绕不开 NIST1.1 一个统计测试套件如何判断“随机”判断“随机”本质上是个逆否命题思维我们没法证明一个序列绝对随机但可以检验它“不随机”的程度。NIST STS 的做法是设计一系列统计量每个统计量对应随机序列某方面的特征然后计算待测序列在这些统计量上出现的概率即 P-value。如果 P-value 小于预设阈值默认 0.01就认为这一项测试失败说明序列在该维度上偏离了随机性预期。整套测试从频数检测Frequency Test开始。这个测试逻辑最简单就是看整个序列里 0 和 1 的比例是否接近 1:1。一个随机序列里出现 60% 的 1 和 40% 的 0直觉上就觉得不对劲频数检测就是把这个直觉量化。做完频数检测后还有针对相邻比特关系的游程检测Runs Test它统计连续相同比特的段长度分布检验序列中是否出现过长的连续 0 或连续 1。再往后还有块内频数检测、离散傅里叶变换检测、非重叠模板匹配检测等一共 15 项各自关注随机序列的不同切面。把这些测试全跑完基本就能覆盖常见的随机性缺陷类型。这里有个重要概念这些测试不是孤立看的。单项测试通过不说明随机性合格但多项测试同时失败基本能确定序列有问题。NIST 官方建议的阈值是显著性水平 α 0.01也就是说即使一个序列真随机理论上也会有约 1% 的概率在某项测试中被误判为失败。所以当你看到某个测试项 P-value 略小于 0.01 时别急着下结论通常需要多跑几组样本看整体通过率。1.2 别把 STS 和 SP 800-22B 搞混了很多人第一次接触 NIST 随机数测试时会在两个文档之间绕晕一个是 NIST SP 800-22全称 “A Statistical Test Suite for Random and Pseudorandom Number Generators for Cryptographic Applications”这是测试套件的算法规范文档另一个是 NIST SP 800-22B晚一些发布的它更像是一个应用指南用来指导用户“如何选择合适的测试项并解释结果”。我们在工程里实际编译运行的那个命令行工具就是基于 SP 800-22 规范实现的 STS 套件。关于版本我得强调一下目前你在各种博客上看到的截图多是早年的 1.6 或 2.0 版本界面和输出格式都比较旧。现阶段最新的主版本是 2.1.2对应 SP 800-22 Rev 1a 的算法。下载源码后编译得到的可执行文件是assess这个程序就是全套测试的入口。2.1.2 版本修复了老版本里 Makefile 找不到 libm.a 的问题还改进了部分测试项的边界处理强烈建议直接用 2.1.2不要再用老版本折腾。下载方式我在下一节展开说。2. 最新版下载与环境准备2.1 从哪个渠道拿到最新版最靠谱NIST 官网的 CSRC 页面虽然还挂着 SP 800-22 的资料但那个下载链接更新频率不高时不时还会因为站点改版导致链接失效。我自己现在习惯去 GitHub 拿源码在 GitHub 上搜索 “NIST STS” 或 “sp800-22” 就能找到若干镜像仓库。需要注意仓库是否包含了完整的sts-2.1.2目录结构和文档有些仓库只放了源码没放论文 PDF不影响编译但建议把 SP 800-22 规范文件也下下来报告里引用时用得上。如果你用的是 Linux 发行版有些系统的软件仓库里也能搜到相关包比如apt-cache search nist在部分源里会出现r-cran-randomfields这类同名但完全无关的包别下错。最稳妥的方式还是 Git clone 源码git clone https://github.com/InsaneMonster/NIST-Randomness-Testsuite.git cd NIST-Randomness-Testsuite/sts-2.1.2如果你访问 GitHub 不稳定也可以用我后面讲到的办法去其他镜像站下载打包好的源码压缩包。总之别下载来路不明的编译好的二进制文件这种工具类软件最好从源码编译既能确认没有被人动过手脚又能根据自己系统环境调整编译参数。2.2 Windows 环境准备WSL 还是原生编译Windows 下跑 NIST STS 有几个选择。最简单的是启用 WSLWindows Subsystem for Linux在 Ubuntu 子系统里直接编译运行这样路径和依赖问题都少还能用 Linux 下的 shell 脚本处理测试数据。我实测过WSL2 下编译和运行完全正常性能损失可以忽略。另一个选择是在 Windows 上装 MinGW 或 Cygwin 环境做原生编译。这条路比较折腾主要是 Makefile 里的-lm链接选项在 Windows 下可能找不到对应的数学库需要手动调整。如果你只是要快速出结果建议别跟这个环境较劲直接用 WSL 或者虚拟机。还有一条路用 Docker。你把 sts-2.1.2 源码挂载进 Ubuntu 容器在容器里编译运行本地只保留测试数据和输出报告。这个方式适合需要多次、批量测试的工程场景环境隔离干净换机器也方便。我在团队内部推荐的就是这个方案后续想要封装成自动化测试流水线也更容易。2.3 Linux 环境准备与依赖检查在 Linux 下编译前需要确认系统装了gcc、make和标准库。Debian/Ubuntu 系可以这样检查sudo apt update sudo apt install build-essential gcc --version make --version只需要这几个基础工具STS 没有其他复杂的第三方依赖。不过有一点要注意老版本的 STS比如 1.6在部分 64 位系统上编译时make会因为缺少libm.a静态库而报错。2.1.2 已经在 Makefile 里处理了这个问题正常情况下直接make就能过。如果编译过程中弹出fatal error: gmp.h: No such file or directory这通常意味着你 clone 的仓库还附带了一些扩展模块。STS 主程序不需要 GMP 库我建议忽略多余模块只进入sts-2.1.2目录编译主程序即可。3. 编译安装全流程3.1 编译命令详解进入sts-2.1.2目录后直接执行make终端会开始编译各个测试模块最后在obj/目录下生成一堆.o文件并在当前目录生成assess可执行文件。整个过程在一分钟内能跑完。如果你在非常老的操作系统上遇到-lm找不到可以手动把源码包里的lib/libm.a复制到系统库目录再重新编译sudo cp lib/libm.a /usr/lib/libm.a make clean make这条命令在旧教程里出现频率极高但 2.1.2 里一般用不到了。我把它保留在这里是因为仍然有人在老系统的兼容模式下会踩到同样的坑。如果你的系统不需要跳过这步。编译完成后可以顺手把assess拷贝到/usr/local/bin方便之后在任何目录直接调用sudo cp assess /usr/local/bin/3.2 验证安装是否成功编译完成后别急着跑测试先确认assess能正常启动。在命令行输入./assess正常情况下程序会进入交互式界面先显示一串版本信息然后进入测试参数设置流程。如果终端只显示一行Could not open file...先忽略那是程序在找默认文件通常不影响启动流程。要强制退出交互界面直接按CtrlC就行。如果提示segmentation fault或者error while loading shared libraries大概率是环境还是有问题。前者常见于内存不足或者输入数据格式不该传的情况下发生了越界后者通常是缺少动态库。解决办法在我第 6 节的问题排查表里有整理。3.3 常见的编译报错与处理编译过程中最常见的三类报错和应对方案如下错误现象常见原因解决办法make: gcc: Command not found系统没有装 gccsudo apt install build-essential或sudo yum groupinstall Development Toolscannot find -lm缺少数学库或链接路径不对检查libm.so是否存在尝试sudo apt install libc6-dev必要时手工指定-L/usr/lib/x86_64-linux-gnuundefined reference to ...源码目录不完整或者编译缓存异常make clean后重新make确认 clone 的仓库包含了全部.c和.h文件做完这步工具本身已就绪接下来关键是搞清楚怎么把待测数据喂给它并理解运行参数的含义。4. 使用流程与参数详解4.1 准备测试数据NIST STS 测试的单位是二进制序列。它支持两种输入文件格式ASCII 字符流和二进制位流。ASCII 格式的文件里每个字节是一个字符0或1文件内容就是一堆010101...这样的文本。二进制格式则直接按位存放每 8 个 bit 拼成一个字节文件大小更小读取效率也更高。assess会自动识别文件格式判断依据是文件开头的字节内容是字符 48/49 还是其他二进制值。为了讲清楚两种格式的区别我写个 Python 示例生成 100 万 bit 的测试数据import os import random # 方法一生成 ASCII 字符流 n 1_000_000 with open(random_ascii.txt, w) as f: for _ in range(n): f.write(str(random.getrandbits(1))) # 文件大小约 1 MB # 方法二生成二进制位流 with open(random_bin.dat, wb) as f: f.write(os.urandom(n // 8)) # 文件大小约 125 KB这里有个细节当你使用默认的流方式测试时assess要求输入文件至少包含待测bit长度 × 数据流数量个比特。举个例子如果你设置 bit length 为 100 万、streams 为 10那就需要 1000 万 bit 的数据。很多人在这一步报错提示数据不足就是因为文件长度不够。一个常见做法是先用dd或 Python 脚本从系统熵源一次性生成足够大的原始数据文件再按需分割。4.2 运行 assess 并逐项配置参数准备好数据文件后启动测试./assess 1000000这里的1000000指每个子序列的比特长度。程序随即进入交互式配置界面按提示操作即可。我整理了一份参数交互流程方便你对照着看交互提示输入值说明Provide input (in bits):1000000单个测试序列的 bit 数命令行参数已传入时这里仍会显示Statistical Testing...后询问How many bit streams?10要测试的独立序列数量recommend 不少于 10Input file format:0 或 10 表示二进制位流1 表示 ASCII 十六进制字节流这里容易混淆见下文后续测试项选择如Select Test (0-15):00 代表执行全套测试也可以输入具体测试项的编号组合这里最容易被坑的就是Input file format的选择。菜单里的说明文字并不直观它实际上在问你的输入文件是原始的二进制 bit 文件还是用 ASCII 字符表示十六进制数值的文件我建议统一使用“直接生成的随机 bit 文件”对应格式选 0。如果你在 Windows 上用文本编辑器新建了文件里面写了字符串101010那这个文件其实是 ASCII 字符流格式要选 1并且文件内容每个字符必须是0或1不能有空格和换行。选错格式最典型的症状是程序跑得很“顺利”但所有测试项的 P-value 全是 0或者直接 segment fault。4.3 修改测试项、运行频率等细节在Select Test (0-15)这一步输入不同编号可以选择要执行的测试。编号对应关系如下编号测试项备注1Frequency Test推荐必测2Block Frequency Test推荐必测3Runs Test推荐必测4Longest Run Ones in a Block推荐必测5Binary Matrix Rank Test对序列长度有要求6DFT Spectral Test推荐必测7Non-overlapping Template Matching耗时较长8Overlapping Template Matching耗时较长9Maurer Universal Statistical Test需要足够长序列10Linear Complexity Test对序列长度有要求11Serial Test推荐必测12Approximate Entropy Test推荐必测13Cumulative Sums Test推荐必测14Random Excursions Test需要序列足够长15Random Excursions Variant Test需要序列足够长输入0表示执行所有测试程序会继续问你是否要调整默认参数比如块长度、模板长度等等。如果你第一次使用直接回车接受默认值即可。想批量跑多个样本时也支持非交互式参数传入核心命令可以这样组合./assess 1000000 -i-i表示忽略交互参数直接使用默认配置。这个方式适合脚本化调用但要注意-i模式仍然需要你事先按前面交互流程设置过一次参数它会把配置缓存到experiments/AlgorithmTesting/目录下的某个配置文件中。5. 测试结果解读5.1 finalAnalysisReport.txt 与 summaryReport.txt测试跑完后assess会在当前目录生成experiments/AlgorithmTesting/文件夹里面有两个关键输出finalAnalysisReport.txt和summaryReport.txt。前者保存的是每个测试项、每个数据流的详细 P-value 列表后者则是一个压缩版摘要。finalAnalysisReport.txt的格式大致长这样RESULTS FOR THE UNIFORMITY OF P-VALUES AND THE PROPORTION OF PASSING SEQUENCES -------------------------------------------------------------------------------- C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 P-VALUE PROPORTION STATISTICAL TEST 0 1 2 0 1 1 3 1 0 1 0.534146 10/10 Frequency 1 0 1 1 2 1 0 2 1 1 0.739918 10/10 BlockFrequency前面 10 列 C1 到 C10 是 P-value 的分布区间计数。一个合格的结果里这 10 个区间的计数值应该大致均匀不会出现某个区间集中了异常多的样本。紧跟着的 P-value 列是均匀性检验的 P-value最后一列PROPORTION表示通过比例例如10/10意味着 10 个数据流全部通过该项测试。5.2 判定标准与细节判定测试是否通过主要看两个指标一是每个子序列的 P-value 是否大于 0.01二是PROPORTION是否落在可接受范围内。对于 10 个数据流的情况至少应通过 9 个也就是比例不低于 9/10。如果通过比例过低基本可以断定待测序列存在明显的随机性缺陷。还有一点容易被忽略finalAnalysisReport里的结果只有在“每个测试项都有足够数量的 P-value 参与均匀性计算”时才是可靠的。NIST 文档建议每个测试项至少收集 55 个 P-value即至少跑 55 个数据流才能对整体均匀性下结论。我们的日常实践里如果只是快速自测10 个流可以接受但如果是为了出正式报告至少跑 100 个数据流每个 100 万 bit。另外summaryReport.txt只列出通过/失败的汇总没有 P-value 明细。我习惯先看 summary如果某项失败再去 finalAnalysisReport 里精确查找是哪个数据流拖了后腿。正式交付报告时两个文件最好都附上。5.3 报告如何输出到自定义目录默认输出在experiments/AlgorithmTesting/但很多项目里你希望在特定目录保存报告。可以手动把experiments路径打包或者做一个软链接指向自己的数据目录。我常用的做法是在跑测试前先把输出目录切换出来mkdir -p /data/rng_test_result ln -sfn /data/rng_test_result experiments/AlgorithmTesting ./assess 1000000 -i这个 symlink 的方式最简单不用改源码。注意切换目录后如果跑过多次测试finalAnalysisReport.txt会被新结果覆盖如果你需要保留历史记录每次跑完后立刻重命名备份。6. 常见问题与排查技巧实录6.1 编译和运行时的环境类错误我在多个平台上编译运行过 STS遇到的环境类问题可以归纳成一张速查表问题现象可能原因解决办法make报错ld: cannot find -lm老版本 STS 的 Makefile 缺陷参考 3.1 节的 libm.a 处理办法运行./assess直接segment fault内存不足或输入文件格式选错用ulimit -a查看内存限制确认数据文件格式匹配交互界面卡在参数输入阶段终端编码问题把终端语言环境设为 C即export LANGC后重跑Non-overlapping Template 测试耗时异常久序列过长或模板数太多适当减少测试流数量或用-i默认参数跑这些问题的共同特征是“程序本身没错但环境和配置不匹配”排查时把眼光放到输入数据和系统环境上别一开始就怀疑源码。6.2 数据格式与测试结果异常如果测试跑完了但结果异常先确认数据生成和读取格式一致。我曾经遇到一个案例同事用openssl rand生成了二进制随机数但保存文件时用xxd转成了十六进制文本然后在assess里选格式 1ASCII 十六进制导致测试一直报“数据太少”。原因是 ASCII 十六进制格式下每个字节只解析一个十六进制字符实际参与测试的 bit 数缩水了一半。还有一个高频问题测试所有 P-value 都是 0。这通常不是随机源的问题而是文件里除了0和1之外还有其他字节比如换行符\n。解决办法是用管道直接去掉所有空白字符或者生成数据时确认每行没有换行tr -d \n random_raw.txt random_clean.txt另外如果你用 Python 的random模块生成测试数据注意getrandbits(1)在 Python 的全局随机种子下产生的序列质量其实足够高用来测试工具本身没问题。但正式评估你自己的随机源时还是应该使用实际待测设备的输出不要混入系统伪随机数否则测出来的结果不能代表待测对象的真实质量。6.3 测试数量与效率的取舍STS 运行速度不算快尤其是 Non-overlapping Template 和 Serial 测试在 100 万 bit、10 个流的情况下可能需要几分钟到十几分钟。如果跑 100 个流做正式报告时间会显著拉长这时候合理规划测试顺序很重要。我的建议是先只跑核心快速项Frequency、Block Frequency、Runs、Longest Run、Cumulative Sums这些测试耗时短能快速暴露严重缺陷。快速项全部通过后再跑全套测试节约时间。命令行里选择测试项时可以直接输入一组编号比如123456表示只跑前六项不需要每一项单独确认。另外不要在一个体积巨大的文件上反复跑同一套测试。更好的做法是先把大文件切成若干个 100 万 bit 的小文件再统一批量处理。写个简单的 shell 循环就可以完成for i in $(seq 1 10); do head -c 125000 /dev/urandom sample_${i}.dat ./assess 1000000 -i sample_${i}.dat cp experiments/AlgorithmTesting/finalAnalysisReport.txt report_${i}.txt done注意我这里用了/dev/urandom做演示这只是为了说明自动化流程。正式测试必须替换成你自己要验证的随机源输出否则整份报告只是对 Linux 内核熵池的测试没有任何工程意义。在实际项目中我最看重的其实不是单个样本的 P-value而是同一个随机源在不同批次数据下结果的稳定性。偶尔一项测试失败可能是统计波动但同一项测试在多个批次中反复失败那基本就是随机源存在结构性缺陷。想当年我第一次用这个工具时以为跑完一遍全是 PASS 就万事大吉后来把测试流从 10 提到 100才发现某个自研 PRNG 在串行测试上时好时坏差点带着隐患上线。这个经验后来也成了我给团队定的规矩凡是随机性相关模块验收数据流数量不能少于 100而且至少要复测三次取一致的结论。希望你在自己的项目里起步时就养成这个习惯省掉后面返工的麻烦。
返回列表