☰
NIST STS随机数测试套件:从下载编译到结果解读的完整实战指南
2026/10/7 9:06:59 网站建设 项目流程

拿到一个随机数生成器,或者自己写完一段PRNG代码,第一件事是什么?我一般不做功能自测,而是直接丢给 NIST 随机数测试软件,跑一遍那十几个统计测试项,看它能不能撑住 100 万 bit 的序列检验。NIST STS(Statistical Test Suite)在随机性评估领域基本就是默认标准,不管是做密码学模块、硬件真随机数源(TRNG)、还是嵌入式系统的熵源验证,最后拿出手的报告总得有几行 P-value 和通过率。这篇教程我会从下载渠道、编译安装、参数选择到报告解读全部过一遍,兼顾刚入门的新手和已经在项目里用过的老手,争取不让你走我当年走过的弯路。

这个工具能解决的问题很直接:随机数“看起来乱”和“统计学上合格”完全是两码事。你在终端里打印一串数字觉得挺随机,但拿到频数检测、游程检测、块内最大游程检测里跑一圈,可能立刻露馅。NIST SP 800-22 定义的这套测试套件,就是用来判断一个二进制序列是否具备随机序列应有的统计特征,评估对象包括密码算法产生的伪随机数、硬件 RNG 采样数据以及各类熵源输出。适合参考这篇教程的人包括:正在做安全模块开发的软件工程师、需要验证硬件熵源质量的嵌入式开发者、研究随机数算法的学生,以及任何需要在项目验收文档里附上一份正式随机性测试报告的人。

1. 随机数测试这件事,为什么绕不开 NIST

1.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 有几个选择。最简单的是启用 WSL(Windows 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...,先忽略,那是程序在找默认文件,通常不影响启动流程。要强制退出交互界面,直接按Ctrl+C就行。

如果提示segmentation fault或者error while loading shared libraries,大概率是环境还是有问题。前者常见于内存不足或者输入数据格式不该传的情况下发生了越界,后者通常是缺少动态库。解决办法在我第 6 节的问题排查表里有整理。

3.3 常见的编译报错与处理

编译过程中最常见的三类报错和应对方案如下:

错误现象常见原因解决办法
make: gcc: Command not found系统没有装 gccsudo apt install build-essential或sudo yum groupinstall "Development Tools"
cannot find -lm缺少数学库或链接路径不对检查libm.so是否存在,尝试sudo apt install libc6-dev,必要时手工指定-L/usr/lib/x86_64-linux-gnu
undefined 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 不少于 10
Input 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 LANG=C后重跑
Non-overlapping Template 测试耗时异常久序列过长或模板数太多适当减少测试流数量,或用-i默认参数跑

这些问题的共同特征是“程序本身没错,但环境和配置不匹配”,排查时把眼光放到输入数据和系统环境上,别一开始就怀疑源码。

6.2 数据格式与测试结果异常

如果测试跑完了但结果异常,先确认数据生成和读取格式一致。我曾经遇到一个案例:同事用openssl rand生成了二进制随机数,但保存文件时用xxd转成了十六进制文本,然后在assess里选格式 1(ASCII 十六进制),导致测试一直报“数据太少”。原因是 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,而且至少要复测三次取一致的结论。希望你在自己的项目里起步时就养成这个习惯,省掉后面返工的麻烦。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询