安全加密的伪随机数生成器

本文档介绍了 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() 方法时:

  1. nonce 递增。

  2. 使用 ChaCha20 算法以及 keynonce 对输出缓冲区进行加密。

在这里,每个 Draw() 请求都会递增 nonce,以确保不同的结果。调用方提供一个缓冲区,以就地执行加密。系统会使用该缓冲区中的任何现有数据,因为它们不会影响安全属性。

如果存在 AddEntropy() 请求,则通过将额外的熵与旧键混合来更新 key

k<sub>new</sub> = H(e || k<sub>old</sub>)

其中,k<sub>old</sub>k<sub>new</sub> 分别是旧的和新的 keye 表示输入字节,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 子系统进行安全通信。