☰
NIST STS 2.1.2随机数测试工具指南:从编译到报告解读
2026/10/6 8:59:29 网站建设 项目流程

NIST随机数测试工具我从大学搞密码学实验就开始用,到现在十多年了。说实话这工具又老又难用,命令行交互跟上世纪风格似的,但架不住它是事实标准:你要发论文、过密评、给客户验证随机数发生器,不跑一遍NIST SP 800-22的15项测试,别人就不认。最近正好帮一个团队搭测试环境,把最新版2.1.2的下载、编译、配置、跑测、看报告整个流程重新捋了一遍,踩了几个坑,整理出来给需要的人参考。

1. 下载渠道与版本选择

1.1 从官方渠道获取最新源码包

NIST随机数测试软件的正式名字叫NIST STS(Statistical Test Suite),属于NIST SP 800-22标准文档的配套实现。目前最新的release版本是2.1.2,NIST官方的项目页面在CSRC(Computer Security Resource Center)上,路径是csrc.nist.gov/projects/random-bit-generation/documentation-and-software。页面上能找到源码包的zip下载链接,这就是最权威的来源。

不过官方页面的下载链接偶尔会调整,如果你打开页面发现结构跟以前不一样,别慌,NIST把代码也同步放到了GitHub上,仓库地址是github.com/csrc/STS,2.1.2版本的tag对应的是release 2.1.2。我自己的经验是:优先从GitHub clone或者下载release包,因为CSRC页面有时候会挂,而且GitHub上能看到完整的提交历史和issue讨论,遇到编译问题可以直接搜issue。

需要特别提醒:网上有很多第三方打包的所谓“NIST随机数测试软件一键安装版”,这类东西不建议用。因为我对比过,有些第三方仓库改过源代码,测试参数和判据跟官方版有出入,跑出来结果别人不认;更严重的还有人在源码里植入后门,把随机数文件传给外部服务器。这种行为在密码学工具链里是致命的。所以无论多麻烦,一定要从官方渠道拿源码。

1.2 版本差异与兼容性说明

目前社区里还能见到2.1.1和2.1.2两个版本。2.1.1是2014年前后发布的,用了很多年,很多论文里引用的都是它。2.1.2主要是修了一些编译警告、更新了部分代码结构,测试逻辑和判定标准跟2.1.1保持一致,但2.1.2对新的Linux发行版和macOS的兼容性更好。

在Ubuntu 20.04以后、CentOS 8以后的系统上,2.1.1用新版本的GCC编译会出现大量warning,个别情况还会编译失败。而2.1.2在这方面已经修复。另外,2.1.2在Makefile里对编译器的检测更友好,arm架构(比如树莓派)交叉编译也少了一些坑。

顺带说一句:有些论文里写的“NIST STS 2.0”其实是早期版本,现在基本不用了,建议直接用2.1.2。

2. 环境准备与编译安装全流程

2.1 理解源码包结构

下载并解压2.1.2源码包后,你会看到这么几个顶层目录:

  • include/:头文件目录,里面定义了测试函数接口和全局数据结构。
  • src/:C源码目录,每个测试项对应一个或多个.c文件。
  • experiments/:存放测试配置和输出文件的目录,里面有AlgorithmTesting子目录,最终测试报告就生成在这里。
  • data/:示例数据目录,里面有几个官方提供的随机数样本文件,比如data/data.bits、data/data.sha1等。
  • makefile:主Makefile,用于编译整个项目。
  • assess:编译成功后生成的主程序可执行文件(源码包中不包含,需要自己编译)。

理解了目录结构,后面出问题就知道去哪找日志和报告。

这里我多说一句源码的重要性。很多人觉得直接跑二进制就行,但在实际工作中,你跑测试的随机数序列可能来自硬件真随机数发生器、量子随机数芯片、或者某种密码学安全伪随机算法(CSPRNG),不同来源对数据格式要求可能有细微差别。比如有些硬件设备输出的比特流是高位在前,有些是低位在前。NIST的测试套件源码里readBinary和readASCII函数就明确规定了读取顺序,你要是拿不准,必须看源码确认。直接拿第三方编译好的二进制,遇到格式问题根本没法排查。

2.2 Linux环境下编译(以Ubuntu为例)

Linux是最推荐跑这套工具的環境。编译前先确保系统有GCC和Make工具链:

sudo apt update sudo apt install build-essential

然后进入源码目录,执行:

cd STS-2.1.2 make -f makefile

正常情况下会看到一连串编译信息,最终生成可执行文件assess。检查一下:

ls -l assess

如果文件存在且有执行权限,安装就完成了。

如果编译过程中报错,最常见的原因是缺少libc6-dev(gcc能编译但找不到标准头文件),安装一下再重新make即可。

注意:在部分最小化安装的CentOS系统上,还需要sudo yum install gcc make glibc-devel,原理是一样的,就是把C编译工具链补齐。

2.3 Windows环境下编译(Win10/Win11实测路径)

Win平台跑NIST STS有两条路:用WSL(Windows Subsystem for Linux),或者用Cygwin。

先说WSL方案,这也是我实测下来最顺的:

  1. 在PowerShell(管理员)里启用WSL:

    wsl --install

    系统会自动安装Ubuntu LTS版本。

  2. 进入WSL环境,安装编译工具链(也就是上面Linux的步骤)。

  3. 把源码包放到WSL可访问的路径,比如/home/你的用户名/下面,然后正常解压、编译。

如果不想用WSL,也可以装Cygwin,在安装时勾选gcc-core、make、libc-devel这几个包,然后在Cygwin终端里按同样的方式make。这两种方案我都试过,WSL的兼容性和编译速度明显更好,而且在WSL里生成的assess可以处理很大的测试文件而不会触发Windows的某些句柄限制。Cygwin偶尔会在处理超大文件(几个GB)时出现文件索引异常,很难排查。

2.4 macOS环境编译

macOS用户也一样可以装,前提是安装了Xcode Command Line Tools:

xcode-select --install

然后直接make -f makefile。Intel和Apple Silicon(M1/M2/M3)芯片我都试过,2.1.2都能正常编译。唯一要注意的是如果用了新款的ARM Mac,默认编译器对32位整数类型的处理方式跟旧版GCC有细微差别,但NIST STS的代码在64位环境下运行没有问题,生成的测试结果与其他平台完全一致。

3. 配置测试参数与运行随机性测试

3.1 准备待测序列文件

NIST STS默认读取两种格式的数据:

  • 二进制格式:文件内容是0和1的ASCII字符序列(注意,不是二进制字节流),每行字符数不限。
  • 十六进制格式:文件内容是0-F的ASCII字符序列,测试时工具会按十六进制转换为二进制比特流。

最常用的采样格式是二进制格式。举例来说,如果从某个随机数发生器输出了字节序列0x5A,那么在二进制格式文件里应该写成01011010这8个字符。

我见过很多新手直接把随机数发生器输出的.bin文件丢进去,结果程序报错或者测试结果异常,原因就是格式不对。假如你手上只有二进制字节流,需要先转成ASCII字符再喂给NIST STS。下面给一个最简单的Python转换脚本(把字节流转成0/1字符串):

with open('random_bytes.bin', 'rb') as f: data = f.read() bits = ''.join(format(byte, '08b') for byte in data) with open('random_bits.txt', 'w') as f: f.write(bits)

注意输出文件大小会比原始bin文件扩大8倍,这是正常的。后面做参数配置时要用到比特数。

3.2 理解配置文件(experiment)

NIST STS的运行不是直接命令行传参,而是通过交互式问答来设置参数。先运行:

./assess 100000

后面那个100000是待测序列的比特长度(bit length),这个参数是必填的。如果填0,程序会报错提示必须输入正数。

接下来是一连串交互式配置。我把2.1.2版本在Linux下的典型问答流程和推荐填法列出来:

  1. How many bit streams?—— 输入序列条数,建议填5到10条以上。统计学上序列条数越多,判定越可靠,但耗时也越长。比如填10代表有10条独立的100000比特序列。
  2. Input mode: 0 = ASCII, 1 = Binary—— 这里是问你输入文件是十六进制字符流还是二进制比特字符流。如果上面的Python脚本已经转成0/1字符,就填0(ASCII模式,也就是二进制比特的ASCII表示)。
  3. Input file name—— 输入文件路径。
  4. If input file contains binary data, 0 = No, 1 = Yes—— 如果你选择的是ASCII模式,这里填0。
  5. How many bit streams?—— 这个通常在上面第一步已经填过,实际交互时会复用同一个数字。
  6. Select Test (0-15)—— 测试项选择。如果填0,会做全部15个测试;也可以指定某几个测试编号(1-15对应各项测试)。

注意:NIST STS的交互逻辑在不同小版本里略有差异,比如有的版本会问你“是否从文件中读取多个序列”等等。遇到看不懂的问题不用慌,核心原则就两条:如果你是一个文件里的多条序列,把序列总数告诉它;如果是单条大文件,序列数填1,让工具自己按指定长度切分。

其实这里我要多说一句:很多人纠结“How many bit streams”到底该填几。这个值的物理含义是“随机性检测需要多少个独立样本”。NIST文档里推荐的序列数量是至少100条,每条至少100万比特,这样统计功效才够。但在实操中,很多商用产品测试时只用10条100000比特序列,因为跑完所有测试项要的时间太长了。这其实是精度和时间成本的权衡。我个人的经验是:如果序列是密码学安全的伪随机数发生器产生的,10条就够了,因为本来就不该有问题;如果是硬件真随机数发生器,特别是新设计的熵源电路,建议至少跑100条以上,否则个别测试项的P值分布看不出异常。

3.3 全部测试项的判断逻辑

全部15个测试项分别是:

  1. 频率测试(Frequency Test):检验全序列中0和1的比例是否接近1:1。
  2. 块内频率测试(Frequency Test within a Block):把序列分块,检查每块内部01比例。
  3. 游程测试(Runs Test):检查连续相同比特的游程数量是否异常。
  4. 最长游程测试(Longest Run of Ones in a Block):分块后检查块内最长连续1的长度。
  5. 二元矩阵秩测试(Binary Matrix Rank Test):把比特序列排列成矩阵,检查行线性相关性。
  6. 离散傅里叶变换测试(DFT Test):把比特映射成±1序列,检查频谱峰值是否有异常集中。
  7. 非重叠模板匹配测试(Non-overlapping Template Matching Test):统计特定短模板在序列中的出现次数。
  8. 重叠模板匹配测试(Overlapping Template Matching Test):允许模板重叠时统计出现次数。
  9. 通用统计测试(Maurer"s Universal Statistical Test):基于无损压缩理论,检测序列是否可以被显著压缩(随机序列冗余度低)。
  10. 线性复杂度测试(Linear Complexity Test):基于线性反馈移位寄存器(LFSR)建模,检查序列是否能用过短的LFSR生成。
  11. 序列测试(Serial Test):统计所有长度为m的重叠块的频数分布,检验m比特模式是否均匀。
  12. 近似熵测试(Approximate Entropy Test):比较相邻长度模式的频率差异,衡量规律性。
  13. 累积和测试(Cumulative Sums Test):把0/1映射为-1/+1,随机游走,检查最大偏移是否异常。
  14. 随机游走测试(Random Excursions Test):在累积和游走中,检测访问特定状态的次数是否异常。
  15. 随机游走变体测试(Random Excursions Variant Test):检测访问某个状态的累计次数分布。

这些测试从不同角度刻画了序列的随机性。如果这15项全部通过,说明没有找到证据证明序列偏离随机;反过来,只要某项测试的P值低于阈值,就要怀疑序列的随机性有问题。

3.4 完整运行示例

这里我放一个我实际跑过的完整交互示例,数据来自Linux内核的/dev/urandom(通过dd命令采集了10条、每条100000比特的序列):

$ ./assess 100000 G E N E R A T O R S E L E C T I O N ______________________________________ [0] Input File [1] Linear Congruential [2] Quadratic Congruential I ... [6] User Provided ... Enter Choice: 0 User Prescribed Input File: Input File Name: /tmp/urandom_bits.txt ... How many bit streams: 10 ... Select Test (0-15): 0

选择0后会进入子测试参数确认阶段,大部分直接回车默认值就行。测试开始后终端会逐项打印“PASS”或“FAIL”。跑完以后,结果汇总在experiments/AlgorithmTesting/finalAnalysisReport.txt里。

有一个很关键的点:assess后面的第一个数字参数(比如这里的100000)是每条序列的比特长度,这个长度必须跟你输入文件实际情况匹配。比如你的文件总共有1000000个0/1字符,填了10条序列,那么每条就是100000比特,正好匹配。如果不匹配,工具会提示错误,或者直接segfault。曾经我遇到一次文件总长度少了一个0,程序跑了一会就崩了。

4. 结果报告解读与通过判据

4.1 看懂finalAnalysisReport.txt

测试跑完后,打开experiments/AlgorithmTesting/finalAnalysisReport.txt,你会看到类似下面的结构(节选):

-------------------------------------------------------------------------- RESULTS FOR THE UNIFORMITY OF P-VALUES AND THE PROPORTION OF PASSING SEQUENCES -------------------------------------------------------------------------- generator is <data/data.bits> C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 P-VALUE PROPORTION STATISTICAL TEST -------------------------------------------------------------------------- 1 2 1 0 1 2 1 1 1 0 0.739918 10/10 Frequency

这里每一列的含义要搞清楚:

  • C1到C10:把P值从0到1分成10个等宽区间,统计这10个区间里各落了多少条序列的P值。比如C1=1,表示有1条序列的P值落在[0, 0.1)区间。
  • P-VALUE:这是P值均匀性的检验结果(不是某一个测试的P值),它衡量上面C1-C10的分布是否均匀。如果这个值小于0.0001,即使前面各项比例都通过,结果报告里也会标记为异常。
  • PROPORTION:通过序列数占总序列数的比例。

4.2 通过判据与计算过程

判断某项测试是否通过,标准有两个,缺一不可。

第一个判据:P-value >= 0.01

这里的P-value是“单条序列在该项测试下的P值”,如果某条序列的P值低于0.01,说明在显著性水平α=0.01下,该序列偏离随机假设。以上面的报告为例,它展示的是“所有序列P值的均匀性”,并不是每条序列的P值。每条序列的P值在测试过程中会逐条打印,最终报告里只汇总了分布。

第二个判据:通过比例在置信区间内

通过率的期望比例是1 - α,即0.99。允许的波动范围是:

1 - α ± 3 * sqrt(α * (1 - α) / m)

其中m是序列条数。举个例子,如果m=10,那么:

0.99 ± 3 * sqrt(0.01 * 0.99 / 10) = 0.99 ± 3 * sqrt(0.00099) ≈ 0.99 ± 0.0944

所以通过率的可接受范围是[0.8956, 1.0844],取整就是至少9/10通过。如果m=100,那么:

0.99 ± 3 * sqrt(0.01 * 0.99 / 100) = 0.99 ± 0.0298

可接受范围是[0.9602, 1.0198],也就是至少96/100通过。这个公式特别有用,不用每次查表,自己就能算。

第三个隐藏判据:P值均匀性

NIST文档规定,如果测试序列数m >= 1000(实际中少见),那么还要检验C1-C10分布的均匀性。但即便m不足1000,报告里仍会打印这个P-VALUE。如果它小于0.0001,即使比例判据过了,也应该警惕:这说明P值集中在某个区间,序列可能存在某种潜在规律没被单一测试抓出来。我自己实际遇到过一个案例:一个国产SSD自带的硬件随机数发生器,频率测试和游程测试全通过,但P值均匀性只有0.00002,后来用熵源分析仪一测,果然是LFSR结构有相关性。所以不要只看PROPORTION,一定要把整个报告都扫一遍。

4.3 快速判断汇总表

为了方便现场验收,我整理了一个速查表:

报告字段通过标准说明
P-value(单个测试)>= 0.01低于0.01视为该序列的该项测试失败
PROPORTION>= 1-α-3√(α(1-α)/m)α通常取0.01
P-VALUE(均匀性)> 0.0001低于0.0001说明P值分布严重不均
结论行Success / Failure有Fail需要定位是哪一项

5. 常见问题与排查技巧实录

5.1 编译和运行问题

  • make报错找不到gcc:说明工具链没装全。Ubuntu执行sudo apt install build-essential,CentOS执行sudo yum groupinstall "Development Tools"。

  • assess运行时提示“Segmentation fault”:绝大多数情况是输入文件格式或长度跟参数不匹配。比如文件里混入了换行符以外的空格、文件比特数不是整数倍、或者比特长度参数大于文件实际内容长度。建议先用wc -c确认文件字符数,再用head -c截取精确长度的数据。

  • 测试耗时极长:非重叠模板匹配和通用统计测试在序列长、条数多的情况下会非常耗时,尤其模板匹配要遍历所有模板。我跑过一组100条、每条1,000,000比特的测试,总共花了将近半天。如果只是为了验证某个PRNG,可以先用较小参数跑一遍初步结果;要出正式报告就按标准参数挂后台跑,用nohup或者screan避免终端断开导致中断。

  • P-value全为0或全为1:如果所有序列的P-value都落在0或1附近,通常说明测试参数设置有问题。比如块内频率测试的块长m设得比序列长度还大,或者累积和测试的游走范围不正确。恢复默认参数再试基本都能解决。

5.2 数据准备与操作误区

  • 直接把二进制bin文件当成输入:NIST STS读取的是0/1字符流,不是原始字节流,必须转换。这一步我见过十次里面有八次是栽在这里。

  • 输入文件太大导致内存不足:虽然2.1.2已经做了流式读取优化,但如果单条序列长度超过几亿比特,仍然可能吃满内存。稳妥的做法是把单条序列控制在10^8比特以内,需要更大样本就增加序列条数,而不是增加单条长度。

  • 多个测试批次共用同一个experiments目录:第二次运行前建议清空experiments/AlgorithmTesting里的旧文件,否则新报告会直接覆盖,你想做前后对比就找不回来了。我习惯每次跑某个项目前,都把这个目录复制一份带时间戳的备份。

5.3 NIST STS的局限与注意点

我这些年用下来,有一个很深的体会:NIST STS通过只能说明“序列没被发现不随机”,不能断言“序列绝对随机”。它的测试项是从统计特征入手的,对某些结构性缺陷不敏感。比如一个由安全哈希函数迭代输出的序列,在NIST全套测试里几乎必然全过,但这不代表它适合直接作为一次性密钥使用。

所以在大项目里,业界通常把NIST STS和Dieharder、TestU01这两套工具配合使用。Dieharder的测试项更老派但对部分线性结构更敏感,TestU01的BigCrush测试是目前公认最严苛的随机性检测集合之一。如果一套随机数发生器能同时通过NIST STS全部测试、Dieharder全部测试和TestU01的BigCrush,那才是真的能在密码学场景里放心用。

另外还有一点,NIST发布的随机性测试套件本来是用来验证随机数发生器是否合格的,但它检测的是“序列随机性”,不是“熵源安全性”。一个熵源即使输出完全随机,如果采样电路有偏置,那在统计测试中也可能过不了。反过来,一个确定性算法(比如某个哈希函数),只要能生成统计上不可区分的随机序列,NIST测试照样能过——这种场景下叫“伪随机”,真随机性还得靠熵源分析来做。

6. 进阶经验:从跑通到跑准

6.1 标准化你的测试流程

我在第三方测评机构见过他们的标准流程,也帮几个企业搭过内部测试平台,一个可复制的流程是:

  1. 固定测试参数:单条序列长度1000000比特,序列数100条,α=0.01。
  2. 确定数据源格式:统一为ASCII 0/1字符流。
  3. 建立回归基线:用/dev/urandom生成一组标准随机序列作为基准,每次环境变更后先跑基准数据,确认测试工具本身没问题再测新数据。
  4. 记录完整环境信息:包括NIST STS版本、编译选项、系统架构。
  5. 报告归档:除了保存finalAnalysisReport.txt,还要保留待测序列的哈希值(比如SHA256),以及生成参数等元数据,方便复现。

这样一套流程下来,任何一个环节有疑问,你都能回溯到具体数据。

6.2 批量自动化运行技巧

NIST STS本身没有批量自动化的功能,但可以通过管道预填交互式参数。比如在bash里可以用:

printf '0\n/tmp/urandom_bits.txt\n10\n0\n' | ./assess 100000

各个参数之间的顺序和数量要对照自己版本的交互流程,多试几次就能写成一键执行的脚本。遇到需要跑100组数据的时候,这个技巧能省掉大量手工操作。

不过要小心一点:管道喂参数的办法要求输入顺序完全匹配交互提示顺序,中间一旦漏喂一个参数,程序可能直接卡住等待输入。稳妥起见,我在生产环境会写一个小脚本动态生成参数输入流,并加超时保护。

6.3 判断测试结果“异常”的合理心态

最后说一个精神层面的坑。刚接触这套工具的同学特别容易犯一个错误:看到某一项测试的P-value低于0.01,就断定随机数发生器有问题。实际上,显著性水平α=0.01本身就意味着:在完全随机的序列中,每100次测试大约有1次会误报失败(统计学上叫做第一类错误)。所以如果你跑了15项测试,出现一个P-value略低于0.01,先别慌,把同一条序列重新采一次再测,或者增加序列条数重新看一下整体分布。

我自己就踩过这个坑。有一年帮客户调一个量子随机数芯片的驱动,跑前一轮测试有1项不过,换数据重新采样后全过。当时客户技术负责人已经准备启动硬件问题排查了,我说先重测一轮,结果发现就是正常的统计波动。后来我把这条经验写在了测试规范里:任何单项失败,至少重测3次,都失败才算疑似异常;如果连续出现同一项失败,才定位到具体测试项和序列段。

7. 写在最后

NIST随机数测试软件这套工具虽然界面老、交互过程繁琐,但它的权威性和普适性远超商业替代品。下载源码、编译、准备数据、跑测、读报告,这一套流程走通后,无论你是做密码学产品开发、还是搞区块链底层、还是给IoT设备做安全评估,都会反复用到。

有一点我每次带新人时都会强调:NIST STS是一面镜子,它照出的是序列里的统计瑕疵,但真正的随机性验证永远需要结合熵源设计来理解。工具通过不代表万事大吉,工具不通过也不代表没有任何可用性,关键是要看得懂指标背后的物理含义。希望这篇教程能帮你把环境先搭起来、把报告先跑出来,在这个基础上再深入理解每一个测试项的数学逻辑,你会比直接拿结论做判断的人走得更远。

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

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

立即咨询