- 网络安全
【免费下载链接】portmaster
🏔 Love Freedom - ❌ Block Mass Surveillance
本篇指南围绕 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 两种分组密码 |
| 熵池 Feeder | entropy.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 提供了两组真实测试输出作为参照基准,结论要点如下:
- 少量测试失败是正常的,甚至是期望的:README 明确指出“around 5 tests of
diehardernormally fail. This is expected and even desired.”——真正的 CSPRNG 不应在每次跑分中让全部测试恰好通过,出现零星 WEAK/失败恰恰是统计自然波动的体现; - rng 当前在输出 1MB 或经过 10 分钟后重新播种(对应 get.go 中
reseedAfterBytes = 1048576(1MB)与reseedAfterSeconds = 600(10 分钟)两个常量); - 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; - 纯 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 的熵质量测试方案可以概括为三条可操作结论:
- 测试入口:
cd base/rng/test && go build,随后用./test fortuna fortuna.out 10或./test tickfeeder tickfeeder.out 1生成样本; - 检验工具:
dieharder -a -f <输出文件>,重点观察绝大多数测试PASSED、仅零星WEAK即可视为通过; - 设计意图:tickFeeder 每字节只按 1 位熵保守申报(8 倍采集),Fortuna 负责混合浓缩,1MB/10 分钟的重播种策略保证前向安全——整套机制确保了 Portmaster 加密功能所依赖的随机数在统计与安全两个维度上都站得住脚。
- 网络安全
【免费下载链接】portmaster
🏔 Love Freedom - ❌ Block Mass Surveillance
相关推荐
终极指南:mbedtls随机数质量评估与嵌入式熵源测试
终极指南:mbedtls随机数质量评估与嵌入式熵源测试 mbedtls作为一款开源的TLS库和PSA加密API参考实现,在嵌入式环境中提供了可靠的随机数生成功能
网络安全密码学通信嵌入式ESP-IDF 随机数生成(RNG)完全指南:从硬件熵源到 esp_random / getrandom / getentropy API 实战
ESP IDF 随机数生成(RNG)完全指南:从硬件熵源到 esp_random / getrandom / getentropy API 实战 ESP IDF
物联网嵌入式Tachyon随机测试生成器:rng.h与边界条件验证实践
Tachyon随机测试生成器:rng.h与边界条件验证实践 在零知识证明(Zero Knowledge, ZK)系统开发中,随机数生成器(Random Numb
密码学区块链高性能计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考