做密码算法、安全模块或者硬件真随机数发生器TRNG的同行应该都体会过这样的场景手里的随机数看起来“挺随机”但评审或认证时对方只认测试报告。NIST STSNIST Statistical Test Suite就是最常用的随机数测试软件之一对应 NIST SP 800-22 标准用来从统计角度评估一个随机数生成器的输出到底靠不靠谱。很多测评报告、密码模块检测、芯片出厂测试里都能看到它的身影。这篇文章我会把目前最新的 NIST STS 软件从下载、安装到完整使用的过程捋一遍重点说清楚那些不看源码就很容易踩的坑数据文件到底该用什么格式、交互式参数怎么填、跑完之后的 finalAnalysisReport 该怎么读、什么条件下算“通过”。如果你正准备给手头的随机数生成器做统计检测或者刚接触 SP 800-22 正愁不知道怎么下手照着这套流程走基本能顺利出结果。1. 先搞清楚这个软件到底是干什么的1.1 随机数质量为什么值得做统计检验很多人对“随机数”的理解就是“看起来没有规律”。但严格来说一个输出序列是否随机靠人眼是看不出来的甚至靠简单的频数统计也不够。比如一个序列前 50 万个 bit 全是 0、后 50 万个 bit 全是 1整体上 0 和 1 的数量完全相等单看比例可能觉得“没问题”可一旦切到局部或者看游程分布问题立刻就暴露了。NIST STS 就是通过一组统计测试来检测序列中各类“非随机模式”的工具。它不只检查 0/1 比例还会检查连续相同位的游程是否异常、矩阵秩是否存在线性相关、频谱上有没有隐藏周期、序列的压缩潜力是否过大、线性反馈移位寄存器能否模拟出这个序列等等。这些检测覆盖了随机数生成器最常见的缺陷类型因此成为密码学和安全测评领域事实上的标准工具之一。1.2 NIST SP 800-22 的 15 项测试在测什么软件的核心是 SP 800-22 标准里定义的 15 项统计测试每一项都有明确的统计学原理。为了避免后面看到报告时一头雾水建议先对这 15 个名字混个脸熟。编号测试名称检测方向01Frequency (Monobit) Test全序列 0/1 比例是否接近 1/202Frequency Test within a Block每个块内 0/1 比例是否正常03Runs Test0/1 游程长度分布是否异常04Longest Run of Ones in a Block块内最长连续 1 游程是否异常05Binary Matrix Rank Test固定大小矩阵的秩检测线性依赖06Discrete Fourier Transform (Spectral) Test频域上是否存在周期性峰值07Non-overlapping Template Matching Test非重叠模板模式的匹配次数08Overlapping Template Matching Test重叠模板模式的匹配次数09Maurers Universal Statistical Test基于无损压缩思想的通用统计检验10Linear Complexity Test线性反馈移位寄存器长度是否异常11Serial Test相邻 m 位模式的频次分布12Approximate Entropy Test相邻块模式的“可预测性”13Cumulative Sums (Cusum) Test随机游走的最大偏移是否过大14Random Excursions Test随机游走访问特定状态的次数15Random Excursions Variant Test随机游走变体统计量这里面要注意第 07 项 Non-overlapping Template Matching 内部实际会遍历 148 种非周期模板所以最终报告里这项测试会出现很多行不要以为程序出 bug 了。真正跑完 15 项测试后报告里的测试统计行数会明显超过 15 行这是正常现象。2. 下载软件认准版本与渠道2.1 官方渠道和第三方仓库怎么选先回答很多人最关心的问题目前最新版本到底是哪个NIST 官方发布的 NIST STS 至今仍然是2.1.2对应 SP 800-22 Revision 1a。这套工具虽然发布得早但核心算法和判定标准到今天依然是各类测评报告的依据。下载时有几个渠道我按推荐程度排一下NIST CSRC 官网在 NIST 的 Computer Security Resource CenterCSRC网站里找到 Random Bit Generation 项目页里面提供 sts-2.1.2 的 zip 压缩包。这是最权威的来源如果做正式项目或者有第三方审计要求建议从这里下载。GitHub 上的镜像仓库一些开发者把官方源码同步到了 GitHub方便直接 clone。搜索 sts-2.1.2 或者 nist-rng 通常能找到。这类镜像的好处是能看到补丁和 issue 讨论但注意不要下载来路不明的“魔改版”或“优化版”测试标准这种东西越接近官方越好。某些技术博客提供的网盘链接不推荐你无法确认里面有没有被改动过而且版本经常不是最新的。我个人的建议是先从 NIST 官网拿压缩包如果网络环境拉取比较慢再去 GitHub 找知名仓库下载 release 压缩包。尽量不要用git clone拉整个仓库因为仓库里可能带有历史提交、示例数据等额外文件压缩包方式下载更省事。2.2 下载压缩包与校验方式拿到压缩包后如果你是从官网下载的建议顺手做一下校验。Linux 下用sha256sum命令sha256sum sts-2_1_2.zip官方页面会给出对应的 SHA-256 摘要值对比一下是否一致。这个步骤在正式项目里比较重要因为随机数测试工具本身如果被篡改那测出来的结论就完全没有意义了。如果你是从 GitHub 镜像下载的至少看一眼这个仓库的 star 数量、最近是否有 issue 反馈编译问题以及仓库描述里是否标注了 “based on NIST STS 2.1.2”。这种交叉确认能避免下载到测试参数被改过的 fork。3. 编译安装Linux 与 Windows 两条路线3.1 Linux 下用 make 编译踩过的坑这个软件本身没有复杂的依赖核心就是gcc和make。在 Ubuntu/Debian 系系统上先把基础编译工具装好sudo apt update sudo apt install build-essential然后解压进入源码目录unzip sts-2_1_2.zip cd sts-2.1.2 make -f Makefile编译完成后当前目录下会生成一个名为assess的可执行文件。运行一下./assess 1000000如果能看到交互菜单说明编译成功。这里有两个我实际踩过的坑单独说一下。第一个坑是“编译能过但运行时报段错误或结果明显异常”。主要原因通常不是编译器太老而是系统架构和 Makefile 里的优化选项匹配不上。很多镜像里的 Makefile 默认带有-marchnative或者类似针对特定 CPU 的优化项放到别的机器上虽然能编译但运行不稳定。遇到这种情况直接打开 Makefile把CFLAGS里的-march、-mtune这类参数删掉只保留-O3或者-O2即可。第二个坑是“找不到头文件”之类的编译错误。这个多半是老代码对比较新的 GCC 版本兼容性不好。我遇到的是exit函数未声明最简单的处理是把你缺失的头文件补进报错的.c文件里一般是加上#include stdlib.h。NIST 官方原版代码是 2010 年前后写的用新版本编译器时出现少量警告和个别报错都很正常不要慌逐个修复即可。3.2 Windows 下用 WSL/Cygwin 注意点很多人日常工作环境是 Windows但这个软件没有官方 Windows 原生版本。我试过几种方案最省心的是WSLWindows Subsystem for Linux。安装 WSL 后在 Ubuntu 子系统里按上面 Linux 的流程编译即可性能损失可以忽略而且生成的数据文件和报告可以直接用 Windows 文件系统访问。需要注意路径问题WSL 里访问 Windows 盘符是/mnt/c/...如果你把数据文件放在 Windows 桌面上输入文件名时就要写对应的 WSL 路径。Cygwin 也能跑但我个人不太推荐。Cygwin 环境下编译老代码时路径转换和权限问题会更明显而且最终产出的是 Windows 风格的可执行文件切换到 Linux 环境又得重新编译。如果你只是为了跑一次测试WSL 足够用了。Windows 原生开发环境如果想用还有一条路是把源码移植到 MSVC但这需要改的代码相当多收益不成正比不建议折腾。4. 准备测试数据文件决定结果是否可信的第一步4.1 数据文件格式约定ASCII 0/1这是整个测试过程中最容易翻车的地方。NIST STS 默认从文件读数据时按字节逐个读取每个字节必须是 ASCII 字符 0 或 1。也就是说一份 100 万 bit 的测试序列对应的数据文件就是 100 万个字符文件大小约 1 MB。很多第一次用的人会直接把随机数生成器输出的二进制字节流每个字节取值 0~255扔进工具里结果要么程序读取后全部按错误的方式解析要么报告的测试结果惨不忍睹其实数据格式一开始就不对。正确的做法是把二进制字节流展开成 0/1 字符流。每个字节展开成 8 个字符比如十六进制值0xA5展开成二进制是10100101那么文件里应该写这 8 个字符而不是一个值 0xA5 的字节。另外一个容易忽略的细节是数据文件里不要放换行符、空格、逗号等任何其他字符。程序读文件时按“位”数读取对应数量的字节如果文件里有换行换行符会被当成一个非 0/1 字符轻则影响统计结果重则导致某些测试计算出错。网上有些教程教你把数据每行存 100 个 bit 便于查看这在 NIST STS 里不可取。提示测试之前可以用wc -c检查一下文件字节数确认和你计划写入的总 bit 数完全一致。如果文件里有不可见字符cat -A可以直接显示出来。4.2 用 Python 与 OpenSSL 生成随机样本生成符合格式要求的数据文件最简单的方式是 Python。下面是基于secrets模块生成随机字节再展开为 ASCII 0/1 的脚本适合用来测试软件本身是否正常工作。import secrets total_bits 100_000_000 # 100 个序列每个 1,000,000 bit byte_len total_bits // 8 with open(data.bits, w) as f: for _ in range(byte_len): b secrets.randbelow(256) f.write(f{b:08b})这段脚本会生成一个 100 MB 左右的文本文件内容全部是连续的 0/1 字符。如果你测试的是自己实现的 PRNG也一样把每次输出的随机字节f{b:08b}直接写入文件即可。如果你不想写 Python用 OpenSSL 生成原始随机字节再转换也可以openssl rand -out random.bin 12500000 xxd -b random.bin | awk {for(i2;iNF;i) printf %s, $i} | tr -d \n data.bits注意这条命令会比较慢而且xxd的输出包含了每行的地址列awk 从第 2 个字段开始取就是为了跳过地址。实际项目中我更喜欢直接用 Python因为转换过程和格式控制更直观。4.3 序列长度与样本数量的经验参数NIST STS 的交互式参数里会问你两个关键数字文件中包含多少 bit和要测试多少条序列。我见到的大多数测试方案用的是每条序列 1,000,000 bit一共 100 条序列。也就是总数据量 100,000,000 bit约 12.5 MB 原始二进制字节展开成 ASCII 后约 100 MB。这个参数组合的作用有两个第一单条序列 100 万 bit 能满足绝大多数测试对样本长度的要求第二100 条序列做比例统计时通过率下限的计算会比较直观。在 NIST 官方文档里也给出了类似“至少 55 条序列、每条至少 100 万 bit”的指导值。样本数太少时比例估计的置信区间会很宽就算生成器有明显问题也可能“侥幸通过”。所以如果条件允许我通常建议做到 100 条。如果硬盘和内存吃紧最低也不要低于 55 条。5. 交互式执行测试的完整流程5.1 从 generator 选择到参数输入进入解压后的目录运行cd sts-2.1.2 ./assess 1000000这里的1000000就是每条序列的 bit 数。程序启动后会先出现一个数据源选择菜单。常见的几个选项是[0] Input from file从文件读取数据这是绝大多数场景下用的选项。其他数字对应不同的内置伪随机数生成器比如 LCG、xorshift 等用来做演示或者快速验证实际测试基本不用。输入0回车。接着会出现测试项选择菜单S T A T I S T I C A L T E S T S __________________________________ [00] Frequency [01] Block Frequency ... [14] Random Excursions Variant [15] Serial输入0表示选择全部 15 项测试也可以输入数字加空格来选择部分测试比如1 2 3。建议第一次跑全量测试直接输入0。选完测试项后程序会针对每项测试询问参数。大多数情况下直接回车即可使用默认值。比如 Frequency Test within a Block 会问块长度默认是128Non-overlapping Template Matching 会问模板长度和块长度默认分别是9和2048。这些默认值在 SP 800-22 文档中被验证过除非你有明确理由否则不要随意更改。最后程序会提示你输入文件名Provide an input file name: data/data.bits如果你把数据文件放在 sts-2.1.2 目录下的data子目录里就填这个相对路径如果放在其他位置填完整路径即可。然后还会问两个数字文件中总共有多少 bit、要测试多少条序列。分别输入100000000和100和上一步生成的文件保持一致。之后程序会开始跑测试终端显示Statistical Testing In Progress.........等待时间取决于数据量和机器性能。100 万 bit × 100 条序列全量测试在普通 PC 上通常需要几分钟到十几分钟期间不要 CtrlC 中断。5.2 15 项测试跑完后的报告位置测试完成后结果会写入experiments/AlgorithmTesting/目录下的finalAnalysisReport.txt。每次运行前建议手动清理一下旧的experiments/AlgorithmTesting目录避免上一次的中间文件残留干扰rm -rf experiments/AlgorithmTesting报告文件是纯文本直接打开看即可cat experiments/AlgorithmTesting/finalAnalysisReport.txt如果你是在服务端跑的也可以把报告传到本地再打开。这个文件就是整个测试的最终结论里面每个测试会有一段统计表格。6. 读懂 finalAnalysisReportP-value 与通过率6.1 报告表格每一列的含义报告里每个测试的输出大致长这样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 -------------------------------------------------------------------------------- 12 9 10 9 7 10 12 10 9 12 0.834308 100/100 Frequency --------------------------------------------------------------------------------各列含义如下C1 到 C10100 条被测序列分别计算出的 P-value落在 0 到 0.1、0.1 到 0.2……0.9 到 1.0 这 10 个区间内的序列条数。理想情况下每个区间大约 10 条偏离太远说明 P-value 分布不均匀。P-VALUE对 C1 到 C10 这 10 个计数做卡方拟合优度检验得到的 P-value表示这组 P-value 是否均匀分布。PROPORTIONP-value 大于显著性水平 α默认 0.01的序列比例一般显示为“通过条数/总条数”。比如 100/100 就是全部通过。STATISTICAL TEST对应的测试名称。6.2 怎么判断一批随机数是否合格NIST SP 800-22 给出了两个判定标准两个都要满足第一个是比例标准Proportion。对于显著性水平 α0.01 和 m 条被测序列通过比例的下限是(1 - α) - 3 * sqrt(α * (1 - α) / m)当 m100 时下限约等于 0.96015也就是 100 条序列中通过数至少 96 条。如果某一项测试的 PROPORTION 低于这个值说明该项测试没有通过。第二个是均匀性标准P-Value of P-values。也就是报告中那列P-VALUE应该不小于 0.0001。这个值反映的是 100 个 P-value 是否在 [0,1] 上分布均匀。如果某一项测试的 P-VALUE 太小说明虽然通过条数够但 P-value 明显聚集在某些区间依然提示生成器有问题。实际看报告时我会按这个顺序排查先快速扫一遍所有测试行看有没有 PROPORTION 标红或者异常低的再重点看每个测试的 P-VALUE 是否小于 0.0001。如果只有个别测试出现临界值偏低可以多测几轮或者增大数据量再确认不要急着下结论。7. 实际使用中的高频问题与规避技巧7.1 二进制文件与 ASCII 文件的坑前面提过数据文件格式的问题这里再强调一次因为这个坑我见过太多次了。NIST STS 读数据时是按字节逐个处理的它期望文件里是 ASCII 字符0和1。你把真正的二进制字节流喂进去程序不会自动帮你展开因为二进制字节流里的 0x00 到 0xFF 绝大部分不是字符0或1解析出来基本是一堆乱码所有测试结果都会离谱。判断文件格式是否正确的快速方法用head -c 10 data.bits看输出。如果显示的是1010010111这样的可见字符流说明格式对如果显示一堆看不懂的符号或者空白说明你给的文件是二进制格式先做转换再测试。一个小技巧当你不确定自己生成的二进制文件是否正确时先拿一个已知良好的随机源比如openssl rand生成的字节流展开跑一遍看报告各项是否通过。如果已知良好的样本都测出大量失败那问题基本在你的数据转换流程而不是被测对象。7.2 用脚本实现批量自动测试交互式测试适合手工跑一两轮但当你需要压测多个随机数生成器或者做回归测试时手工输入参数太累了。这里分享一个用管道喂应答的脚本方案。假设数据文件已经生成在data/data.bits总共 100,000,000 bit分成 100 条序列每次要跑全量测试可以这样写#!/bin/bash cd sts-2.1.2 printf 0\n0\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n100000000\n100\ndata/data.bits\n | ./assess 1000000这里的换行序列需要根据测试项数量调整。第一次跑交互模式时建议先手动跑一遍观察它会询问多少次参数然后把对应的回车次数写进 printf 里。更稳妥的方案是用 Python 的subprocess配合 pexpect 控制交互但简单场景下 printf 管道就够了。如果不想处理交互参数还有一个思路直接调用./assess seq_len -a进入自动化模式但这个模式对数据文件位置和参数有固定要求灵活性低一些。我实际项目中用得更多的还是管道应答方式因为参数完全可控。7.3 误判与边界情况最后聊几个容易被误判的情况。第一不要把参数调得过于极端。比如块频率测试的块长度设得太小比如 8 bit那么每块只可能统计 0/1 个数几乎测不出异常设得太大又会导致块数量太少。默认值 128 是经过验证的除非你对某个测试做过预分析否则保持默认。第二P-value 只是统计意义上的结论不是绝对判据。即使一个生成器是完全随机的也有约 1% 的概率在某项测试中产生小于 0.01 的 P-value。所以一批测试里有 1 项刚好踩线失败并不一定代表生成器有问题可以通过增加序列数量重新测试来判断。但如果多项测试系统性失败那生成器大概率确实有结构缺陷。第三Random Excursions 和 Random Excursions Variant 两个测试有前置条件。只有当序列中的随机游走确实“回到零点”足够多次时这两个测试才有意义。如果被测序列随机性太差程序可能直接显示测试不适用或者结果异常。遇到这种情况先检查被测生成器的基本频率和游程测试通常能定位到问题。第四不同版本工具的结果不能直接对比。网上有一些 fork 版本修正了原版在特定场景下的数值精度问题但它们输出的报告格式和某些统计量的计算方式可能有细微差异。正式场合统一使用官方 2.1.2 版本不要混用。我自己的习惯是拿到一个待评估的随机数生成器第一轮先跑全量 15 项数据量用 100 万 bit × 100 条如果发现某几项失败再针对失败项单独跑 1000 万 bit × 100 条确认是否稳定失败。这样既节省时间又能把偶然失败的干扰降到最低。随机数检测这种事宁可多跑几轮也别急着给结论。
阅读完成 · 觉得有帮助?