本文档介绍了 Zircon 的加密安全伪随机数生成器 (CPRNG) 的设计,包括其算法、(再)种子过程和熵源。
简介
Zircon 的内置 CPRNG 以非阻塞方式提供加密安全的伪随机数据。用户空间程序可以通过 zx_cprng_draw()
系统调用访问它们。
Zircon 的 CPRNG 只信任可从内核内部访问的熵源,因为内核外部的任何内容(例如被视为用户空间程序的驱动程序)都是不可信的。为使 CPRNG 正确安全地运行,您至少需要提供其中一个来源。不过,用户空间程序可以通过 zx_cprng_add_entropy()
系统调用向 CPRNG 注入额外的熵。
算法
Zircon 的 CPRNG 是一个伪随机数生成器。其实现位于 zircon/kernel/lib/crypto
。它支持两种操作:Draw()
和 AddEntropy()
,它们对应于上述两个系统调用。内部状态由 256 位 key
和 128 位 nonce
组成。key
必须保密,因为知道 CPRNG 输出可以准确地预测 CPRNG 输出。首先,使用一些随机字节初始化 key
(请参阅下一部分),并将 nonce
初始化为 0。
调用 Draw()
方法时:
nonce
递增。使用 ChaCha20 算法以及
key
和nonce
对输出缓冲区进行加密。
在这里,每个 Draw()
请求都会递增 nonce
,以确保不同的结果。调用方提供一个缓冲区,以就地执行加密。系统会使用该缓冲区中的任何现有数据,因为它们不会影响安全属性。
如果存在 AddEntropy()
请求,则通过将额外的熵与旧键混合来更新 key
:
k<sub>new</sub> = H(e || k<sub>old</sub>)
其中,k<sub>old</sub>
和 k<sub>new</sub>
分别是旧的和新的 key
,e
表示输入字节,H
是 SHA256 哈希函数,||
表示串联。旧键包含在哈希值中,以确保调用方(例如调用 zx_cprng_add_entropy()
的用户空间程序)无法完全清除旧 key
并将其替换为这些程序控制的内容。
播种和补种
调用 AddEntropy()
方法会执行 Zircon CPRNG 的初始种子。虚拟内存 ASLR 需要初始种子,因此,对 AddEntropy()
方法的第一次调用发生在用户空间启动前的启动序列的早期。CPRNG 需要初始种子才能发挥作用,因为 Draw()
方法会阻塞,直到添加足够的熵。
初始种子设定后,系统会创建一个线程,以通过调用 AddEntropy()
方法每 30 秒重新植入 CPRNG。这样可以确保正向加密(保证 CPRNG 自上次重新种子以来的所有先前输出的信息保密,即使其内部状态被破解)。
熵源
Zircon 的 CPRNG 可以使用多种熵来源进行种子设定和重新种子:
来自内核 cmdline 选项
kernel.entropy-mixin
的熵,如 kernel_cmdline.md 中所述。硬件 RNG 中的熵,例如 x86 设备上的
RDSEED
指令和其他硬件专用 RNG 中的熵。
内核 cmdline 仅在初始种子下使用,因为它是在启动时传入的常量,仅供一次性使用。来自硬件的熵和抖动熵可用于初始种子和重新种子。为了确保 CPRNG 从选定的熵源充分(重新)种子,您可以使用内核 cmdline kernel.cprng-(re)seed-require.*
选项。如需了解详情,请参阅 kernel_cmdline.md。
可能还有其他可用的熵源,例如可信平台模块 (TPM),但我们目前还没有强大的框架让用户空间程序与内核中的 CPRNG 子系统进行安全通信。