抖动库由 Stephan Mueller 编写,位于 https://github.com/smuellerDD/jitterentropy-library;如需查看相关说明,请访问 http://www.chronox.de/jent.html.在 Zircon 中,它被用作简单的熵 为系统 CPRNG 播种。
本文档介绍并分析了 Cloud Storage 的两个(独立的)配置选项, 抖动:
- 是否在噪声中使用可变的伪随机迭代次数 生成函数。
- 是否使用抖动的内部抖动对原始噪声样本进行后处理 处理例程。
之所以考虑这些基本配置选项,是因为 使用抖动方法的过程。我把它们与可调参数对比 (例如,如果未选择循环计数,则使用精确值 或内部使用的暂存内存大小 抖动),因为可调参数不会对 即抖动收集熵的大小 。
我在本文档的最后就是了完整的结论,但总的来说,我认为 我们应避免在选择伪随机迭代编号和使用 抖动后处理数据。
抖动的简要说明
该作者的文档以 HTML 形式提供,网址为: http://www.chronox.de/jent/doc/CPU-Jitter-NPTRNG.html,或 PDF 格式: http://www.chronox.de/jent/doc/CPU-Jitter-NPTRNG.pdf.简而言之,该库 从 CPU 指令时间变化中收集随机位。
抖动会维持随机状态,即一个 64 位的数字, 受许多抖动函数的影响,最终可用作 输出随机性。
有两个噪声源,它们都是运行速度相对较慢的区块 用于测量精确运行时的代码(使用系统时钟,大约需要 纳秒分辨率)。完成这些代码块的确切时间将 变化。我们会对这些时间进行测试,以确保其不可预测;而不能 测试结果(包括 令人鼓舞。但请注意,本文档的目的并非 证明我们估计的抖动样本中的最小熵是合理的,不过, 讨论上面列出的两种配置选项
第一个用作噪声源的代码块是 CPU 密集型 LFSR
循环,在
jent_lfsr_time
函数。
LFSR 逻辑的重复次数由
kernel.jitterentropy.ll
cmdline(“ll
”代表“LFSR 循环”)。如果为 ll = 0
,
使用伪随机计数,否则使用 ll
的值。
查看源代码,发现外部循环根据 ll
参数。内部循环将 LFSR 前进 64 步,每次执行 XOR 运算
最近的时间样本中的一个位。将时间样本传递至
通过这种方式,LFSR 起到一个处理步骤,通常
随机时间步。如
熵质量测试文档中,请务必注意
测试 CPU 时间的熵内容时,跳过此处理
变体。同样的情况是,启用处理功能会增加
有时可能会出现可疑量的熵估算(请参阅
“处理原始样本的影响”部分)。
第二个噪声源是内存访问循环,
jent_memaccess
函数。
根据 kernel.jitterentropy.ml
重复执行内存访问循环
cmdline(“ml
”表示“内存循环”),其中值 0 同样会激活
伪随机循环计数,并且任何非零值会覆盖伪随机值
计数。实际内存访问循环的每次迭代都会读取和写入
相对较大的内存块,分为 kernel.jitterentropy.bc
对很多
每个块的大小为 kernel.jitterentropy.bs
个字节。在我加载对象时
为 bc = 1024
和 bs = 64
;最新的默认值
均应记录在
cmdline 文档。为便于比较,
抖动源代码为 bc = 64
和 bs = 32
,
此处定义。
根据 jent_memaccess
函数上方的注释,总内存大小
应大于目标计算机的 L1 缓存大小。令人困惑
bc = 64
和 bs = 32
产生的内存大小为 2048 字节,
甚至比大多数 L1 缓存要小(我找不到任何具有超过 0 个字节的 CPU)
但小于 4KB)。使用 bs = 64
和 bc = 1024
会生成 64KB
该内存通常足以溢出 L1 数据缓存。
选项 1:伪随机循环计数
抖动最初设计为两个噪声生成函数
运行伪随机数的次数。具体而言,
jent_loop_shuffle
函数
混用 (1) 从高分辨率时钟读取的时间和 (2)
抖动的内部随机状态,以此来决定运行次数
噪声来源。
我们添加了覆盖这些伪随机循环计数的功能,并测试了 抖动性能。结果如下: 我们将在本单元后面的部分 “伪随机循环计数的影响”部分, 但总而言之:统计测试表明,伪随机循环 计数增加的熵远远超出预期。这让我不信任 这些较高的熵计数,因此建议使用较低的估计值, 优先考虑确定性循环计数而非伪随机。
抖动的随机数据处理
如上所述,抖动可以处理其随机数据, 数据看起来“更加随机”。具体而言,处理应该减少(并且 最好去掉)随机数据与均匀分布的偏差, 并减少(理想情况下,移除)随机字节之间的任何互相关关系。
生成已处理样本的主要函数是
jent_gen_entropy
、
该方法被
jent_read_entropy
来生成任意大数量的随机字节。
实质上,jent_gen_entropy
会在循环中调用 64 次噪声函数。
jent_lfsr_time
的 64 次调用每次都会混合噪声时间测量结果
转换为抖动随机状态。
经过这 64 次迭代后,随机状态会变为“搅拌”在
jent_stir_pool
通过使用“混合器”进行 XOR 运算值而变化,这取决于
状态。如源代码中所述,此操作无法增加或减少
池中的熵(因为 XOR 是双射的),但有可能改进
随机状态的统计外观。
原则上,调用噪声源函数 64 次应产生 64 次
倍的熵,最多为随机状态可以存储的最大 64 位。
这假设 jent_lfsr_time
中的混合操作是以加密方式进行的
声音。我不是密码分析方面的专家,但 LFSR 本身并不是
具有加密安全性的 RNG,因为 64 个连续位会显示整个状态,
64 位 LFSR 之后,所有过去和未来的值都可以
计算。我不确定抖动方案 — 对时间进行 XOR 运算
衡量结果与 LFSR 的比例进行比较,
安全。未经仔细的加密检查,
我知道可能存在这种现象,但我没有在抖动图中看到它
文档),我倾向于使用未处理的样本,
以已知良好的方式(例如我们现在使用的 SHA-2)导入系统熵池中。
尽管如此,我确实对已处理的数据样本运行了 NIST 测试套件。我的 结果在 “处理原始样本的影响”部分) 。
测试流程
如需了解运行熵源质量测试的过程,请参阅 熵质量测试文档。
这些初步结果是基于 Raspberry Pi 上的 Zircon 调试 build 收集的
3,根据现已作废的项目中的 18358de5e90a012cb1e042efae83f5ea264d1502 条款构建而成:https://fuchsia.googlesource.com/zircon/+/a1a80a6a7d
“[virtio][entropy] 基本 virtio-rng 驱动程序”。在
我的 local.mk
文件:
ENABLE_ENTROPY_COLLECTOR_TEST=1
ENTROPY_COLLECTOR_TEST_MAXLEN=1048576
我使用
以下内核 cmdline,更改 $ML
、$LL
和 $RAW
的值:
kernel.entropy-test.src=jitterentropy
kernel.jitterentropy.bs=64
kernel.jitterentropy.bc=1024
kernel.jitterentropy.ml=$ML
kernel.jitterentropy.ll=$LL
kernel.jitterentropy.raw=$RAW
测试结果和分析
伪随机循环计数的影响
原始数据
按照抖动源代码中的逻辑(搜索
MAX_FOLD_LOOP_BIT
和
MAX_ACC_LOOP_BIT
)
伪随机循环计数会在以下范围内变化:
ml: 1 .. 128 (inclusive)
ll: 1 .. 16 (inclusive)
在本课中,我添加了来自 NIST 套件的整体最小熵估算值, 和两个影响估计值:压缩估计值和 马尔可夫估计值。NIST 最小熵估计值是 10 包括这两个指标。压缩估算通常是指 具有确定性循环计数的抖动原始样本的最小值,以及 对于其他噪声图像, 配置。
ml |
ll |
最小熵(位 / 字节) | 预计压缩率 | 马尔可夫估计值 |
---|---|---|---|---|
随机 (1 .. 128) | 随机 (1 .. 16) | 5.77 | 6.84 | 5.77 |
128 | 16 | 1.62 | 1.62 | 3.60 |
1 | 1 | 0.20 | 0.20 | 0.84 |
换句话说,改变循环计数会伪随机地增加最小熵 原始样本的估计值(与确定性样本相比) 始终使用伪随机范围中的最大值的版本。
分析
伪随机循环计数值通过额外增加一次时间来确定 每个噪声函数的样本。首先,这些时间样本与 噪声函数时间测量值,因为循环计数时间之间的差距 样本与噪声函数时间测量值可预测地对应。作为 那么就很不容易想到 输出数据的最小熵。第二,认为 循环计数时间样本在某种程度上约为噪声函数的 250%。 因为两者均依赖于相同的噪声来源,只不过 随机抽取, 启动系统以运行测试所需的时长。
因此,我怀疑所发生的情况是伪随机循环 足以“迷惑”一组特定的统计测试和 基于预测器的测试,但使用 关于如何推算抖动伪随机循环计数的具体知识 实际上都可以更准确地预测输出结果。我认为“真正的” 伪随机循环计数测试中的最小熵,以应对 具体针对我们的代码,在两个确定性 也就是大约 0.20 到 1.62 位/字节。
使用伪随机计数方法会迫使我们做出额外的决定:
保守地估计每字节 0.20 位的实际熵内容(好像
伪随机计数函数始终选择 ml = 1
和 ll = 1
)?还是我们
选择平均熵内容(可能出现更智能的平均值,
而不是计算 (1.62 + 0.20) / 2 = 0.91 位 / 字节,但
可能会造成伪随机循环计数
是否偶尔导致我们的平均熵值低于该平均水平?如果我们
过于保守,我们将花费更多的时间收集熵;如果
我们过于乐观,认为可能存在安全漏洞。归根结底,
会在安全性(优先考虑保守熵)之间进行权衡
和效率(倾向于乐观熵估算)。
处理原始样本的影响
原始数据
我重复了上面报告的三项测试,
处理功能(使用 kernel.jitterentropy.raw = false
,而不是
默认值为 true
)。为方便起见,下表同时列出了
前三行中的示例结果(从上面复制),并将处理
结果(新添加)。
ml |
ll |
原始 | 最小熵(位 / 字节) | 预计压缩率 | 马尔可夫估计值 |
---|---|---|---|---|---|
随机 (1 .. 128) | 随机 (1 .. 16) | true | 5.77 | 6.84 | 5.77 |
128 | 16 | true | 1.62 | 1.62 | 3.60 |
1 | 1 | true | 0.20 | 0.20 | 0.84 |
ml |
ll |
原始 | 最小熵(位 / 字节) | 预计压缩率 | 马尔可夫估计值 |
---|---|---|---|---|---|
随机 (1 .. 128) | 随机 (1 .. 16) | false | 5.79 | 6.59 | 5.79 |
128 | 16 | false | 5.78 | 6.97 | 5.78 |
1 | 1 | false | 5.77 | 6.71 | 5.77 |
分析
后期处理后的最小熵估算值基本上相等(高达 很容易由随机性解释的细微变化),并且也等于 具有伪随机循环计数的原始样本的最小熵估算。
回想一下,抖动的已处理熵由 64 个独立的随机数字组成,
数据样本,在 64 位内部状态缓冲区中混合在一起。每个原始
个样本对应于 raw = true
表中的一个样本。特别是
将 64 个样本与 ml = 1
和 ll = 1
组合起来会是很荒谬的事情,
每处理 8 个字节,就会产生 (5.77 * 8) = 46.2 位熵
因为这意味着 (46.2 / 64) = 0.72 位熵,
与 0.20 位的测量值相反。
此参数适用于 ml = 1
、ll = 1
、raw = false
测量值,
但不适用于 ml = 128
、ll = 16
、raw = false
。具体来说,
原则上,将 64 个原始样本与 ml = 128
和 ll = 16
组合使用可以
collection (1.64 * 64 / 8) = 13.1 位的熵每个处理的字节,但
当然,每个字节都有 8 位硬性限制。
有趣的是,最小熵估算器从 传递给马尔可夫估算器。我的理论是 就足以“欺骗”压缩估计值。如果有 抖动处理例程中存在加密漏洞, 可以编写一个类似的估算器, 最小熵。如果我们使用通用测试来确定原始样本数量, 才能获得 256 的最小熵,而对手则使用目标 那么(相对于这种定向攻击)我们的系统的熵可能会降低 的概率高于我们的预期。这是一个安全漏洞。
如果抖动处理例程中有一个非常糟糕的弱点,
减少“true”内部池中的熵。通过
关于 ml = 1
和 ll = 1
的算术参数显示我们不信任
使用 NIST 测试套件准确测量
所以处理过程实际上有可能减少
而我们的工具无法检测到损失。这会加剧
如上一段落中所述)。
总结
最佳情况下,抖动的伪随机循环计数是有问题的, 一旦使用,就会迫使我们在安全性/效率方面做出取舍。除非我们能 有说服力的证据,证明伪随机时间确实 除了使 NIST 测试套件失效,我们还将提高熵估算值, 应使用确定的循环次数,最好是针对 调整预算
抖动的处理也存在问题,因为(据我所知) 尚未经过充分的加密分析和测试 信任。此外,我们无法直接测量 通过 NIST 测试套件对数据进行后处理, 所以我们无法轻易检测到它我认为我们应该 并保留 Zircon CPRNG 中的熵混合码(基于 SHA-2), 已停用抖动处理。
TODO
- 针对不同版本的 Zircon 重复上述测试, 以确保熵估算值保持一致。
- 在不同的平台和目标上重复这些测试(注意:x86 目标不会 目前可以在前期启动期间访问系统时钟,因此前期启动 熵测试和前期启动 CPRNG 种子尚不支持抖动 x86)。
自动执行此实验中运行测试和生成报告的过程 文档。具体而言,这两种测试应该比较以下两项:
- 伪随机循环计数与各种确定性循环计数值
- 原始样本与处理后的数据