☰
Portmaster 随机数发生器(rng)熵质量测试指南:tickFeeder 与 Fortuna 的 dieharder 验证实践
2026/10/7 23:53:35 网站建设 项目流程
  • 网络安全

【免费下载链接】portmaster

🏔 Love Freedom - ❌ Block Mass Surveillance

项目地址:https://gitcode.com/gh_mirrors/po/portmaster
点击查看免费下载

本篇指南围绕 Portmaster 项目base/rng随机数包的熵质量测试方案展开,完整说明其熵源架构(OS 熵源 + tickFeeder 调度器抖动熵源 + 完整馈送器)如何汇聚到 Fortuna CSPRNG,以及如何通过rng/test测试程序生成随机数据、使用 dieharder 套件检验其统计随机性。读完本文,你将掌握熵质量测试的标准操作流程、测试程序的运行参数、输出文件解读方法,以及 Portmaster 随机数包在重播种(reseed)策略与熵池管理上的实现细节。

为什么 Portmaster 需要自建熵测试

Portmaster 是一款以“阻止大规模监控”为核心理念的网络安全应用(详见仓库根目录 README.md),其核心功能(SPN 匿名网络、TLS 指纹混淆、流量伪装等)对加密密钥与随机数的质量有极高要求。为此,项目在 base/rng/doc.go 中明确声明:rng包提供的是一个可馈送(feedable)的 CSPRNG,底层使用 Fortuna 生成器(github.com/seehuhn/fortuna),默认由两条熵源驱动:

  • 以crypto/rand的种子启动,并定期从操作系统熵源重新播种;
  • 一个极其简单的tickFeeder,通过 goroutine 从 Go 内部调度器中提取熵,设计意图是在程序处于负载状态下工作得更好。

base/rng/test/README.md 正是这套熵架构的“质检报告与操作手册”——它说明了如何证明系统生成的随机数据“足够随机”,并给出了真实环境的 dieharder 测试结果。

架构速览:从熵源到 Fortuna 的完整链路

从源码结构看,base/rng 包的熵生成链路由以下组件协作完成:

组件文件职责
Fortuna 生成器rng.go底层 CSPRNG,通过newCipher支持 AES 或 Serpent 两种分组密码
熵池 Feederentropy.go统一熵收集接口,聚齐 256 位(minFeedEntropy)熵后才向上游输出
OS 熵源osfeeder.go周期性调用crypto/rand读取系统熵
调度器抖动熵源tickfeeder.go采集UnixNano最低位比特,模拟运行负载
完整馈送器fullfeed.go定期清空rngFeeder管道中的积压熵并Reseed
对外接口get.go提供Read/Bytes/Number等读取入口,并负责按字节数/时间触发重播种

初始化流程见 rng.go 的Start():先以 OS 熵异步完成首次Reseed,随后并行启动三个后台 worker——osFeeder、tickFeeder与fullFeeder,共同向 Fortuna 喂入熵。

tickFeeder:为什么每个字节只折算 1 位熵

tickFeeder的实现(tickfeeder.go)每次“滴答”时执行:

value = (value << 1) | (time.Now().UnixNano() % 2)

即取当前纳秒时间戳的最低有效位推入一个 64 位移位寄存器;凑满 64 次推送后组装成 8 字节,通过Feeder送入熵池,并在entropyData中只声明 8 位熵(每字节 1 位)。

README 对这一保守估值的解释是:tickFeeder 的输出永远不会被直接使用,而是作为熵喂给 Fortuna;为了保证最终熵质量足够高,期望每生成 1 字节只折算 1 位熵,即采集量为所需量的 8 倍(“we gather 8 times the amount we need”)。与之对照,OS 熵源在 osfeeder.go 中同样每次取minFeedEntropy/8字节,但按满额entropyBytes * 8位申报——因为crypto/rand的输出被视为高置信度随机。

tickFeeder 的滴答间隔由getTickFeederTickDuration()(tickfeeder.go)动态计算:目标是在重播种周期(默认 600 秒)的 1/10 时间内攒满minFeedEntropy(256 位)熵,因此每个 tick 只贡献 0.125 位,需要256 * 8 = 2048次滴答,算出约 29ms 间隔;若计算结果低于 10ms,则强制取 10ms 下限。值得注意的是,tickFeeder官方注释强调:程序负载越高,质量越好——因为 Go 调度器无法立即唤醒就绪的 goroutine,使时间戳最低位抖动更剧烈。

运行熵测试:生成随机数据样本

第一步:构建测试程序

测试程序位于 base/rng/test/main.go,用法为:

./test {fortuna|tickfeeder} <file> [output size in MB]

在 base/rng/test 目录下先构建:

go build

第二步:选择测试模式并生成样本

README 给出的两种模式:

./test tickfeeder tickfeeder.out 1 # 仅输出附加熵源(tickFeeder)的原始数据 # OR ./test fortuna fortuna.out 10 # 输出带全部熵源的真实 CSPRNG 数据

参数含义(由 main.go 的prep()解析):

  • 第一个参数是模式:fortuna或tickfeeder,其余值直接报 usage 错误;
  • 第二个参数是输出文件路径,以os.O_CREATE|os.O_WRONLY、权限0644打开;
  • 第三个可选参数是输出大小(MB,内部按n * 1000000换算成字节),默认outputSize为1000000字节即 1MB。

在fortuna模式下,程序调用rng.Bytes(64)循环取数并写入文件,每写满 1024 字节向 stderr 输出一个.进度点,每 65536 字节打印累计字节数(见 main.go)。

在tickfeeder模式下(main.go),测试程序会直接复刻 tickFeeder 的采样逻辑——每 10 纳秒睡眠后采集UnixNano() % 2并入位,攒满 64 位写 8 字节;同时并行运行一个noise()goroutine(main.go),用 AES-CTR 不断做流加密运算制造 CPU 负载,模拟真实运行环境下调度器抖动。

第三步:用 dieharder 检验随机性

对生成的样本文件运行 dieharder(一款经典随机数统计测试套件):

dieharder -a -f output.bin

-a表示运行全部可用测试,-f指定输入二进制文件。dieharder 会输出每个测试的名称(如diehard_birthdays、sts_serial、rgb_lagged_sum)、ntup/tsamples/psamples 等参数、p-value 以及PASSED/WEAK的评估结论。

实测结果解读:如何判断“通过”

README 提供了两组真实测试输出作为参照基准,结论要点如下:

  1. 少量测试失败是正常的,甚至是期望的:README 明确指出“around 5 tests ofdiehardernormally fail. This is expected and even desired.”——真正的 CSPRNG 不应在每次跑分中让全部测试恰好通过,出现零星 WEAK/失败恰恰是统计自然波动的体现;
  2. rng 当前在输出 1MB 或经过 10 分钟后重新播种(对应 get.go 中reseedAfterBytes = 1048576(1MB)与reseedAfterSeconds = 600(10 分钟)两个常量);
  3. Fortuna 全链路样本(10MB,go1.14.2 linux/amd64,2020-04-21):100 余项测试几乎全部PASSED,仅少量如diehard_squeeze、sts_serial、rgb_permutations、rgb_lagged_sum等在单一样本上出现WEAK,但第二样本(2nd sample)均恢复PASSED;
  4. 纯 tickFeeder 原始样本(22KB 上下文切换输出,go1.10.3 linux/amd64,2018-08-23):同样绝大多数测试PASSED,个别WEAK(如diehard_opso、diehard_sums、sts_serial、dab_monotobit2),但整体分布良好——这验证了即使只取 tickFeeder 的原始比特流,其统计特性也足以支撑熵池。

需要强调的是:tickFeeder 原始输出的WEAK是预期内的,因为它只是熵源而非最终随机数出口;真正的安全随机数一律来自经过 Fortuna 混合后的输出,而 Fortuna 样本的测试结果显著更稳。

源码级解读:重播种(Reseed)机制如何支撑熵测试

dieharder 测试“随机性”的背后,是 get.go 中checkEntropy()实现的按需重播种策略:

if rngBytesRead > reseedAfterBytes || int(time.Since(rngLastFeed).Seconds()) > reseedAfterSeconds { select { case r := <-rngFeeder: rng.Reseed(r) rngBytesRead = 0 rngLastFeed = time.Now() case <-time.After(1 * time.Second): return errors.New("failed to get new entropy") } }
  • 每次Read/Bytes调用都会检查是否已累计读取超过 1MB,或距上次馈送超过 600 秒;
  • 若需要重播种,会从rngFeeder管道取一批熵数据执行Reseed;若 1 秒内拿不到新熵则返回错误——这是熵池饥饿的显式防护;
  • fullFeeder(fullfeed.go)则按reseedAfterSeconds * 5(至少 600 秒)的周期清空rngFeeder中所有积压数据,避免熵数据滞留过久。

熵池侧,Feeder.run()(entropy.go)只有在累计声明的熵达到minFeedEntropy(256 位)后才把缓冲区整体送入rngFeeder管道。此外 entropy.go 还提供了SupplyEntropy/SupplyEntropyIfNeeded/SupplyEntropyAsInt等对外接口,供其他模块(如 SPN 的加密握手)向 RNG 注入额外熵源——这正是doc.go中“can also be easily fed with additional sources”的设计兑现。

单元测试中的自检路径

除 dieharder 这类外部统计工具外,包内还内置了 rng_test.go 作为持续集成自检:它启动完整模块后,依次验证 AES 与 Serpent 两种密码模式的newCipher创建、Read、Reader.Read、Bytes(32)与Number(100)等全部公开接口是否可用。这保证了每次构建时随机数链路的基本可用性,而 dieharder 则负责对统计质量做更严格的抽样验证——两者形成“功能性 + 统计性”的双层测试矩阵。

小结

Portmaster 的熵质量测试方案可以概括为三条可操作结论:

  1. 测试入口:cd base/rng/test && go build,随后用./test fortuna fortuna.out 10或./test tickfeeder tickfeeder.out 1生成样本;
  2. 检验工具:dieharder -a -f <输出文件>,重点观察绝大多数测试PASSED、仅零星WEAK即可视为通过;
  3. 设计意图:tickFeeder 每字节只按 1 位熵保守申报(8 倍采集),Fortuna 负责混合浓缩,1MB/10 分钟的重播种策略保证前向安全——整套机制确保了 Portmaster 加密功能所依赖的随机数在统计与安全两个维度上都站得住脚。
  • 网络安全

【免费下载链接】portmaster

🏔 Love Freedom - ❌ Block Mass Surveillance

项目地址:https://gitcode.com/gh_mirrors/po/portmaster
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询