GM/T 0005-2012 随机性检测实战:15 项统计测试全流程解析
在国密算法的实际应用中,随机数的质量直接决定了系统的安全性。SM2 签名需要随机数 k,SM4 加密需要 IV 和初始向量,密钥生成过程同样依赖高质量的随机数。如果随机数存在偏差或规律,攻击者可能通过统计分析推断出密钥。
GM/T 0005-2012《随机性检测规范》正是解决这一问题的核心标准。它规定了密码应用中随机数质量的检测方法,包含 14 项统计测试,涵盖了从频率检测到游程分析、从矩阵秩检测到扑克测试等多个维度。
本文将逐一解读这 15 项测试,并提供完整的 Python 实现代码,帮助开发者在实际项目中验证随机数质量。
1. 标准概述
GM/T 0005-2012 由国家密码管理局发布,是密码应用中随机数检测的强制性标准。标准规定了随机数序列的质量评估方法,适用于基于 SM2/SM3/SM4 等国密算法的系统。
1.1 标准适用范围
- 密钥生成:SM2 签名密钥对生成中的随机数 k
- 初始化向量:SM4 加密模式中的 IV
- 盐值生成:PBKDF2 中的 salt
- nonce 生成:TLS 握手中的 nonce
1.2 检测流程概述
标准规定的检测流程分为三步:
- 采样:从待测随机数源中抽取长度为 n 的二进制序列
- 测试:依次执行 14 项统计测试
- 判定:根据 P 值和阈值判断随机数序列是否合格
2. 14 项统计测试详解
2.1 频率测试(Monobit Frequency Test)
目的:检测序列中 0 和 1 的比例是否接近 50%。
原理:计算序列中 1 的个数 N₁,若序列长度 n 足够大,则 N₁ 应近似服从二项分布 B(n, 0.5)。
公式:
S_n = Σ(2·b_i - 1) (将 0/1 映射为 -1/+1)
S_n / √n < 1.96(置信度 95%)判定:P 值 ≥ 0.01 为合格。
代码实现:
import math
def monobit_test(bits):
"""
频率测试(单比特频率测试)
bits: 二进制序列(列表,元素为 0 或 1)
返回: (passed: bool, p_value: float)
"""
n = len(bits)
s_n = sum(2 * b - 1 for b in bits)
# 计算检验统计量
test_stat = abs(s_n) / math.sqrt(n)
# 计算 P 值(正态分布累积分布函数的补函数)
p_value = math.erfc(test_stat / math.sqrt(2))
# 判定阈值:P 值 >= 0.01 为合格
passed = p_value >= 0.01
return passed, p_value2.2 游程测试(Runs Test)
目的:检测序列中游程(连续相同比特)的分布是否符合随机性。
原理:计算游程总数 V_obs,若序列随机,则游程数应近似服从正态分布。
公式:
V_obs = 序列中游程总数
μ = 1 + 2·N₁·N₀ / n
σ² = 2·N₁·N₀·(2·N₁·N₀ - n) / (n²·(n-1))
P = erfc(|V_obs - μ| / (σ·√2))代码实现:
def runs_test(bits):
"""
游程测试
bits: 二进制序列
返回: (passed: bool, p_value: float)
"""
n = len(bits)
N_1 = sum(bits)
N_0 = n - N_1
# 计算游程数
runs = 1
for i in range(1, n):
if bits[i] != bits[i-1]:
runs += 1
# 期望值和方差
mu = 1 + 2 * N_1 * N_0 / n
sigma_sq = 2 * N_1 * N_0 * (2 * N_1 * N_0 - n) / (n**2 * (n - 1))
sigma = math.sqrt(sigma_sq) if sigma_sq > 0 else 1
# 计算 P 值
z = abs(runs - mu) / sigma
p_value = math.erfc(z / math.sqrt(2))
return p_value >= 0.01, p_value2.3 扑克测试(Poker Test)
目的:检测固定长度块中比特组合的分布均匀性。
原理:将序列分成 m 位一组,统计各组合出现次数,用卡方检验判断分布均匀性。
公式:
χ² = Σ(O_i - E_i)² / E_i
其中 E_i = n / 2^m代码实现:
def poker_test(bits, m=4):
"""
扑克测试(固定长度块频率测试)
bits: 二进制序列
m: 每块位数,默认 4 位(检测 16 种组合)
返回: (passed: bool, p_value: float)
"""
n = len(bits)
block_size = m
# 分割成块
num_blocks = n // block_size
blocks = bits[:num_blocks * block_size]
# 统计各组合出现次数
counts = [0] * (2 ** block_size)
for i in range(num_blocks):
block_bits = blocks[i * block_size:(i + 1) * block_size]
value = int(''.join(str(b) for b in block_bits), 2)
counts[value] += 1
# 卡方检验
expected = num_blocks / (2 ** block_size)
chi2 = sum((c - expected) ** 2 / expected for c in counts)
# 计算 P 值(卡方分布的补函数)
# 自由度 = 2^m - 1
df = 2 ** block_size - 1
p_value = _chi2_sf(chi2, df)
return p_value >= 0.01, p_value
def _chi2_sf(x, df):
"""卡方分布的补累积分布函数(sf = 1 - cdf)"""
# 使用正则化不完全伽马函数
return 1 - _regularized_gamma_p(df / 2, x / 2)
def _regularized_gamma_p(a, x):
"""正则化下不完全伽马函数 P(a, x)"""
if x < 0:
return 0
if x == 0:
return 0
# 级数展开
if x < a + 1:
return _gamma_series(a, x)
else:
return 1 - _gamma_cf(a, x)
def _gamma_series(a, x, max_iter=100):
"""级数展开计算 P(a, x)"""
ap = a
s = 1.0 / a
delta = s
for _ in range(max_iter):
ap += 1
delta *= x / ap
s += delta
if abs(delta) < abs(s) * 1e-10:
break
return s * math.exp(-x + a * math.log(x) - math.lgamma(a))
def _gamma_cf(a, x, max_iter=100):
"""连分数展开计算 Q(a, x) = 1 - P(a, x)"""
# 简化实现,实际应使用更精确的连分数
# 此处仅用于演示
return 1 - _gamma_series(a, x)2.4 游程最大宽度测试(Longest Run of Ones in a Block Test)
目的:检测序列中连续 1 的最大游程长度分布是否合理。
原理:将序列分成若干块,每块中统计最长连续 1 的长度,用卡方检验判断分布是否符合理论值。
代码实现:
def longest_run_test(bits, block_size=128):
"""
最长游程测试(块内最长连续 1 的游程)
bits: 二进制序列
block_size: 每块长度,默认 128
返回: (passed: bool, p_value: float)
"""
n = len(bits)
num_blocks = n // block_size
# 统计每块的最长游程
runs = []
for i in range(num_blocks):
block = bits[i * block_size:(i + 1) * block_size]
max_run = 0
current_run = 0
for b in block:
if b == 1:
current_run += 1
max_run = max(max_run, current_run)
else:
current_run = 0
runs.append(max_run)
# 计算经验分布
thresholds = [4, 5, 6]
observed = [0, 0, 0]
for r in runs:
if r <= 4:
observed[0] += 1
elif r <= 5:
observed[1] += 1
else:
observed[2] += 1
# 理论概率(来自标准附录)
expected_probs = [0.2082, 0.3670, 0.4248]
expected = [p * num_blocks for p in expected_probs]
# 卡方检验
chi2 = sum((o - e) ** 2 / e for o, e in zip(observed, expected))
# 自由度 = 2
p_value = _chi2_sf(chi2, 2)
return p_value >= 0.01, p_value2.5 串行测试(Serial Test)
目的:检测序列中 m 位子序列的出现频率是否均匀。
原理:统计所有可能的 m 位组合出现次数,用卡方检验判断均匀性。
代码实现:
def serial_test(bits, m=8):
"""
串行测试
bits: 二进制序列
m: 子序列长度,默认 8 位
返回: (passed: bool, p_value: float)
"""
n = len(bits)
num_patterns = 2 ** m
# 滑窗统计各 m 位组合出现次数
counts = [0] * num_patterns
for i in range(n - m + 1):
value = 0
for j in range(m):
value = (value << 1) | bits[i + j]
counts[value] += 1
# 卡方检验
total = n - m + 1
expected = total / num_patterns
chi2 = sum((c - expected) ** 2 / expected for c in counts)
p_value = _chi2_sf(chi2, num_patterns - 1)
return p_value >= 0.01, p_value2.6 游程测试(扩展版)
检测不同块大小下的游程分布,增强对周期性模式的敏感性。
代码实现:
def runs_test_extended(bits, block_sizes=None):
"""
扩展游程测试
bits: 二进制序列
block_sizes: 可选的块大小列表,默认 [128, 256]
返回: (passed: bool, p_values: dict)
"""
if block_sizes is None:
block_sizes = [128, 256]
p_values = {}
all_passed = True
for bs in block_sizes:
if len(bits) < bs:
continue
num_blocks = len(bits) // bs
runs_list = []
for i in range(num_blocks):
block = bits[i * bs:(i + 1) * bs]
# 计算块内游程数
runs = 1
for j in range(1, bs):
if block[j] != block[j - 1]:
runs += 1
runs_list.append(runs)
# 对游程数分布做卡方检验
ones = sum(bits[:num_blocks * bs])
zeros = num_blocks * bs - ones
mu = 1 + 2 * ones * zeros / (num_blocks * bs)
sigma_sq = 4 * ones * zeros * (2 * ones * zeros - num_blocks * bs) / (num_blocks * bs) ** 3
if sigma_sq <= 0:
sigma_sq = 1
sigma = math.sqrt(sigma_sq)
z = abs(sum(runs_list) / num_blocks - mu) / sigma
p_values[f"runs_block_{bs}"] = math.erfc(z / math.sqrt(2))
if p_values[f"runs_block_{bs}"] < 0.01:
all_passed = False
return all_passed, p_values2.7 矩阵秩测试(Matrix Rank Test)
目的:检测序列构成的矩阵是否满秩。
原理:将比特序列排成矩阵,计算矩阵的秩。若随机数质量良好,矩阵应为满秩的概率约为 0.35。
代码实现:
def matrix_rank_test(bits):
"""
矩阵秩测试
bits: 二进制序列
返回: (passed: bool, p_value: float)
"""
n = len(bits)
# 选择矩阵维度(取最接近的方阵)
side = int(math.sqrt(n))
if side < 29:
side = 29
actual_n = side * side
bits = bits[:actual_n]
# 构造矩阵
matrix = []
for i in range(side):
row = bits[i * side:(i + 1) * side]
matrix.append(row[:])
# 高斯消元求秩
rank = 0
for col in range(side):
pivot = -1
for row in range(rank, side):
if matrix[row][col] == 1:
pivot = row
break
if pivot == -1:
continue
rank += 1
# 交换行
matrix[rank-1], matrix[pivot] = matrix[pivot], matrix[rank-1]
# 消元
for row in range(rank, side):
if matrix[row][col] == 1:
for j in range(col, side):
matrix[row][j] ^= matrix[rank-1][j]
# 判定:满秩概率约 0.35,秩为 side-1 或 side-2 时也可接受
passed = rank >= side - 2
# 近似P值(基于偏离满秩的程度)
deficit = side - rank
p_value = math.exp(-deficit * 2) if deficit > 0 else 1.0
return passed, p_value2.8 通用序列测试(Universal Statistical Test)
目的:用线性复杂度检测序列的随机性。
原理:序列的线性复杂度应近似为 n/2。用 Berlekamp-Massey 算法计算线性复杂度。
代码实现:
def linear_complexity_test(bits):
"""
线性复杂度测试
bits: 二进制序列
返回: (passed: bool, p_value: float)
"""
n = len(bits)
# Berlekamp-Massey 算法
c = [0] * (n // 2 + 1)
b = [0] * (n // 2 + 1)
c[0] = 1
b[0] = 1
L = 0
m = 1
for i in range(n):
d = bits[i]
for j in range(1, L + 1):
d ^= c[j] & bits[i - j]
if d == 0:
m += 1
else:
t = c[:]
if 2 * L <= i:
L = i + 1 - L
c = [0] * (n // 2 + 1)
b = t[:]
m = 1
for k in range(m, m + len(b)):
if k < len(c):
c[k] ^= b[k - m]
m += 1
# 判定:线性复杂度应在 [n/2 - n/10, n/2 + n/10] 范围内
deviation = abs(L - n / 2)
threshold = n / 10
passed = deviation <= threshold
# 计算P值(正态近似)
z = deviation / (math.sqrt(n) / 6)
p_value = math.erfc(z / math.sqrt(2))
return passed, p_value2.9 离散傅里叶变换测试(Discrete Fourier Transform Test)
目的:检测序列的频谱特性,识别周期性成分。
原理:计算序列的 DFT 幅度谱,若随机,则各频率分量幅度应近似指数分布。
代码实现:
def dft_test(bits):
"""
离散傅里叶变换测试
bits: 二进制序列
返回: (passed: bool, p_value: float)
"""
import cmath
n = len(bits)
# 将 0/1 映射为 -1/+1
sequence = [2 * b - 1 for b in bits]
# 计算 DFT
X = [cmath.rect(1, 0)] * n
for k in range(1, n):
for j in range(n):
X[k] += sequence[j] * cmath.exp(-2j * cmath.pi * k * j / n)
# 计算幅度谱
magnitudes = [abs(X[k]) / n for k in range(1, n // 2)]
# 计算峰值高度
peak_height = max(magnitudes)
# 理论分布:指数分布
# P = exp(-pi * peak_height^2 / 4)
p_value = math.exp(-math.pi * peak_height**2 / 4)
return p_value >= 0.01, p_value2.10 非重叠模板匹配测试(Non-overlapping Template Matching Test)
目的:检测特定模式的出现频率是否符合预期。
原理:统计指定模板在序列中出现的次数,用泊松分布判断是否符合。
代码实现:
def non_overlapping_template_test(bits, template="11111111"):
"""
非重叠模板匹配测试
bits: 二进制序列
template: 检测模板,默认 8 个 1
返回: (passed: bool, p_value: float)
"""
n = len(bits)
m = len(template)
# 统计模板出现次数(非重叠)
count = 0
i = 0
while i <= n - m:
if ''.join(str(b) for b in bits[i:i+m]) == template:
count += 1
i += m
else:
i += 1
# 期望次数
lambda_val = (n - m + 1) / (2 ** m)
# 计算 P 值(泊松分布)
p_value = _poisson_sf(count, lambda_val)
return p_value >= 0.01, p_value
def _poisson_sf(k, lam):
"""泊松分布的补累积分布函数"""
# 使用正则化上不完全伽马函数
return _regularized_gamma_q(k + 1, lam)
def _regularized_gamma_q(a, x):
"""正则化上不完全伽马函数 Q(a, x) = 1 - P(a, x)"""
return 1 - _regularized_gamma_p(a, x)2.11 重叠模板匹配测试(Overlapping Template Matching Test)
与 2.10 类似,但允许模板重叠匹配。
代码实现:
def overlapping_template_test(bits, template="11111111"):
"""
重叠模板匹配测试
bits: 二进制序列
template: 检测模板,默认 8 个 1
返回: (passed: bool, p_value: float)
"""
n = len(bits)
m = len(template)
# 统计模板出现次数(允许重叠)
count = 0
for i in range(n - m + 1):
match = True
for j in range(m):
if bits[i + j] != int(template[j]):
match = False
break
if match:
count += 1
# 期望次数
lambda_val = (n - m + 1) / (2 ** m)
# 计算P值(泊松分布)
p_value = _poisson_sf(count, lambda_val)
return p_value >= 0.01, p_value代码实现:
def overlapping_template_test(bits, template="11111111"):
"""
重叠模板匹配测试
bits: 二进制序列
template: 检测模板,默认 8 个 1
返回: (passed: bool, p_value: float)
"""
n = len(bits)
m = len(template)
# 统计模板出现次数(允许重叠)
count = 0
for i in range(n - m + 1):
match = True
for j in range(m):
if bits[i + j] != int(template[j]):
match = False
break
if match:
count += 1
# 期望次数
lambda_val = (n - m + 1) / (2 ** m)
# 计算P值(泊松分布)
p_value = _poisson_sf(count, lambda_val)
return p_value >= 0.01, p_value2.12 游程测试(三进制游程)
检测连续相同比特的分布,特别关注长游程。
2.13 压缩测试(Compression Test)
目的:利用压缩算法检测序列的熵。
原理:若序列可压缩,则存在冗余,非随机。
代码实现:
def compression_test(bits):
"""
压缩测试
bits: 二进制序列
返回: (passed: bool, ratio: float)
"""
import zlib
n = len(bits)
# 转换为字节
byte_data = bytes(int(''.join(str(b) for b in bits[i:i+8]), 2)
for i in range(0, n, 8))
# 压缩
compressed = zlib.compress(byte_data)
ratio = len(compressed) / len(byte_data)
# 判定:压缩率应接近 1(随机序列不可压缩)
passed = ratio > 0.98
return passed, ratio2.14 伪随机数测试(Linear Complexity Over Subsequences)
检测子序列的线性复杂度分布。
代码实现:
def linear_complexity_subsequence_test(bits, subseq_len=500):
"""
伪随机数测试(子序列线性复杂度)
bits: 二进制序列
subseq_len: 子序列长度
返回: (passed: bool, p_value: float)
"""
n = len(bits)
num_subseq = n // subseq_len
if num_subseq < 10:
return True, 1.0 # 样本太少
# 计算每个子序列的线性复杂度
complexities = []
for i in range(num_subseq):
subseq = bits[i * subseq_len:(i + 1) * subseq_len]
L = _berlekamp_massey(subseq)
complexities.append(L)
# 计算均值和方差
mean_L = sum(complexities) / num_subseq
var_L = sum((c - mean_L) ** 2 for c in complexities) / num_subseq
# 判定:均值应接近 subseq_len/2
deviation = abs(mean_L - subseq_len / 2)
threshold = subseq_len / 10
passed = deviation <= threshold
# P值近似
z = deviation / (math.sqrt(var_L / num_subseq) if var_L > 0 else 1)
p_value = math.erfc(z / math.sqrt(2))
return passed, p_value
def _berlekamp_massey(bits):
"""Berlekamp-Massey 算法计算线性复杂度"""
n = len(bits)
c = [0] * (n // 2 + 1)
b = [0] * (n // 2 + 1)
c[0] = 1
b[0] = 1
L = 0
m = 1
for i in range(n):
d = bits[i]
for j in range(1, L + 1):
d ^= c[j] & bits[i - j]
if d == 0:
m += 1
else:
t = c[:]
if 2 * L <= i:
L = i + 1 - L
c = [0] * (n // 2 + 1)
b = t[:]
m = 1
for k in range(m, m + len(b)):
if k < len(c):
c[k] ^= b[k - m]
m += 1
return L代码实现:
def linear_complexity_subsequence_test(bits, subseq_len=500):
"""
伪随机数测试(子序列线性复杂度)
bits: 二进制序列
subseq_len: 子序列长度
返回: (passed: bool, p_value: float)
"""
n = len(bits)
num_subseq = n // subseq_len
if num_subseq < 10:
return True, 1.0 # 样本太少
# 计算每个子序列的线性复杂度
complexities = []
for i in range(num_subseq):
subseq = bits[i * subseq_len:(i + 1) * subseq_len]
L = _berlekamp_massey(subseq)
complexities.append(L)
# 计算均值和方差
mean_L = sum(complexities) / num_subseq
var_L = sum((c - mean_L) ** 2 for c in complexities) / num_subseq
# 判定:均值应接近 subseq_len/2
deviation = abs(mean_L - subseq_len / 2)
threshold = subseq_len / 10
passed = deviation <= threshold
# P值近似
z = deviation / (math.sqrt(var_L / num_subseq) if var_L > 0 else 1)
p_value = math.erfc(z / math.sqrt(2))
return passed, p_value
def _berlekamp_massey(bits):
"""Berlekamp-Massey 算法计算线性复杂度"""
n = len(bits)
c = [0] * (n // 2 + 1)
b = [0] * (n // 2 + 1)
c[0] = 1
b[0] = 1
L = 0
m = 1
for i in range(n):
d = bits[i]
for j in range(1, L + 1):
d ^= c[j] & bits[i - j]
if d == 0:
m += 1
else:
t = c[:]
if 2 * L <= i:
L = i + 1 - L
c = [0] * (n // 2 + 1)
b = t[:]
m = 1
for k in range(m, m + len(b)):
if k < len(c):
c[k] ^= b[k - m]
m += 1
return L2.15 平均游走测试(Cumulative Sums Test)
目的:检测随机游走的路径是否偏离原点。
原理:将序列映射为 ±1,计算累积和,若偏离过大则非随机。
代码实现:
def cumulative_sums_test(bits):
"""
平均游走测试(前向)
bits: 二进制序列
返回: (passed: bool, p_value: float)
"""
n = len(bits)
# 映射为 -1/+1
s = [2 * b - 1 for b in bits]
# 计算累积和
cumsum = [0] * (n + 1)
for i in range(n):
cumsum[i + 1] = cumsum[i] + s[i]
# 计算最大绝对累积和
max_abs = max(abs(c) for c in cumsum)
# 判定
passed = max_abs < math.sqrt(n) * 1.5
# P值计算(基于随机游走理论)
z = max_abs / math.sqrt(n)
p_value = 2 * math.erfc(z / math.sqrt(2))
return passed, min(p_value, 1.0)3. 完整检测框架
将上述测试整合为统一的检测框架:
import math
import random
import sys
class RandomnessTester:
"""GM/T 0005-2012 随机性检测器"""
def __init__(self, bits):
self.bits = bits
self.n = len(bits)
self.results = {}
def run_all_tests(self):
"""运行所有 15 项测试"""
tests = [
("频率测试", self.monobit_test),
("游程测试", self.runs_test),
("扑克测试", self.poker_test),
("最长游程测试", self.longest_run_test),
("串行测试", self.serial_test),
("矩阵秩测试", self.matrix_rank_test),
("线性复杂度测试", self.linear_complexity_test),
("DFT 测试", self.dft_test),
("非重叠模板测试", self.non_overlapping_template_test),
("压缩测试", self.compression_test),
("平均游走测试", self.cumulative_sums_test),
("游程扩展测试", self.runs_test_extended),
("重叠模板测试", self.overlapping_template_test),
("伪随机测试", self.linear_complexity_subsequence_test),
]
print(f"\n{'='*60}")
print(f"GM/T 0005-2012 随机性检测结果")
print(f"序列长度: {self.n} bits")
print(f"{'='*60}\n")
passed_count = 0
failed_tests = []
for name, test_func in tests:
try:
passed, p_value = test_func(self.bits)
self.results[name] = {"passed": passed, "p_value": p_value}
status = "✓ 通过" if passed else "✗ 失败"
print(f"{name:20s}: {status} (P={p_value:.6f})")
if passed:
passed_count += 1
else:
failed_tests.append(name)
except Exception as e:
print(f"{name:20s}: 错误 - {e}")
failed_tests.append(name)
print(f"\n{'='*60}")
print(f"汇总: {passed_count}/{len(tests)} 项测试通过")
if failed_tests:
print(f"失败的测试: {', '.join(failed_tests)}")
print("\n结论: 随机数序列不符合 GM/T 0005-2012 要求")
else:
print("\n结论: 随机数序列通过 GM/T 0005-2012 全部检测")
print(f"{'='*60}\n")
return len(failed_tests) == 0
def monobit_test(self, bits=None):
if bits is None:
bits = self.bits
return monobit_test(bits)
def runs_test(self, bits=None):
if bits is None:
bits = self.bits
return runs_test(bits)
def poker_test(self, bits=None, m=4):
if bits is None:
bits = self.bits
return poker_test(bits, m)
def longest_run_test(self, bits=None, block_size=128):
if bits is None:
bits = self.bits
return longest_run_test(bits, block_size)
def serial_test(self, bits=None, m=8):
if bits is None:
bits = self.bits
return serial_test(bits, m)
def matrix_rank_test(self, bits=None):
if bits is None:
bits = self.bits
return matrix_rank_test(bits)
def linear_complexity_test(self, bits=None):
if bits is None:
bits = self.bits
return linear_complexity_test(bits)
def dft_test(self, bits=None):
if bits is None:
bits = self.bits
return dft_test(bits)
def non_overlapping_template_test(self, bits=None, template="11111111"):
if bits is None:
bits = self.bits
return non_overlapping_template_test(bits, template)
def compression_test(self, bits=None):
if bits is None:
bits = self.bits
return compression_test(bits)
def cumulative_sums_test(self, bits=None):
if bits is None:
bits = self.bits
return cumulative_sums_test(bits)
def runs_test_extended(self, bits=None, block_sizes=None):
if bits is None:
bits = self.bits
return runs_test_extended(bits, block_sizes)
def overlapping_template_test(self, bits=None, template="11111111"):
if bits is None:
bits = self.bits
return overlapping_template_test(bits, template)
def linear_complexity_subsequence_test(self, bits=None, subseq_len=500):
if bits is None:
bits = self.bits
return linear_complexity_subsequence_test(bits, subseq_len)
def runs_test_extended(self, bits=None, block_sizes=None):
if bits is None:
bits = self.bits
return runs_test_extended(bits, block_sizes)
def overlapping_template_test(self, bits=None, template="11111111"):
if bits is None:
bits = self.bits
return overlapping_template_test(bits, template)
def linear_complexity_subsequence_test(self, bits=None, subseq_len=500):
if bits is None:
bits = self.bits
return linear_complexity_subsequence_test(bits, subseq_len)
def generate_test_bits(n=10000, bias=None):
"""生成测试用的随机比特序列"""
if bias is None:
return [random.randint(0, 1) for _ in range(n)]
else:
return [1 if random.random() < bias else 0 for _ in range(n)]
def verify_with_gmssl():
"""使用 gmssl 库验证随机数质量"""
from gmssl import func
# 生成 256 位随机数
rand_bytes = func.rand_bytes(32)
bits = [int(b) for b in format(int.from_bytes(rand_bytes, 'big'), '0256b')]
tester = RandomnessTester(bits)
return tester.run_all_tests()
if __name__ == "__main__":
# 测试随机生成的比特序列
print("=== 测试 1: 纯随机序列 ===")
random_bits = generate_test_bits(10000)
tester = RandomnessTester(random_bits)
tester.run_all_tests()
# 测试有偏的比特序列
print("\n=== 测试 2: 有偏序列 (bias=0.7) ===")
biased_bits = generate_test_bits(10000, bias=0.7)
tester2 = RandomnessTester(biased_bits)
tester2.run_all_tests()
# 使用 gmssl 验证
print("\n=== 测试 3: gmssl 随机数 ===")
verify_with_gmssl()4. 实际应用指南
4.1 密评场景下的随机数检测
在密码应用安全性评估中,GM/T 0005 的检测要求如下:
| 检测对象 | 要求 |
|---|---|
| SM2 签名随机数 k | 必须通过全部 14 项测试 |
| SM4 加密 IV | 至少通过频率测试和游程测试 |
| 密钥生成随机数 | 全部通过 |
| Salt 生成 | 通过频率测试即可 |
4.2 常见问题排查
问题 1:频率测试失败
原因:随机数源存在偏差,如硬件 RNG 未充分校准。
解决:
- 检查硬件 RNG 的熵源是否稳定
- 增加后处理(如 HMAC-DRBG)
- 更换 RNG 设备
原因:序列中存在周期性或自相关。
解决:
- 使用更高质量的熵源
- 增加 Whiteninɡ 处理
原因:序列中存在可预测的模式。
解决:
- 检查 RNG 的实现是否合规
- 使用 NIST SP 800-90A 标准算法
4.3 性能建议
在实际部署中,建议采用以下策略:
- 在线检测:仅运行频率测试和游程测试(快速筛查)
- 离线检测:运行全部 14 项测试(深度验证)
- 定期检测:每季度或更换 RNG 时执行完整检测
5. 标准引用与参考
- GM/T 0005-2012《随机性检测规范》,国家密码管理局
- NIST SP 800-22《A Statistical Test Suite for Random and Pseudorandom Number Generators for Cryptographic Applications》
- ISO/IEC 18031-1:2018《Random bit generation》
6. 总结
GM/T 0005-2012 为密码应用中的随机数质量提供了系统化的检测框架。通过 14 项统计测试,可以全面评估随机数的质量,发现潜在的偏差和模式。
在实际工程中,建议将随机数检测作为密钥生成、初始化向量配置等环节的必要步骤,确保密码系统的底层安全性。
相关实践文章: