<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>随机性 · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/%E9%9A%8F%E6%9C%BA%E6%80%A7/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/%E9%9A%8F%E6%9C%BA%E6%80%A7/index.xml" rel="self" type="application/rss+xml"/><item><title>内核把设备噪声转成密钥</title><link>https://www.xbcatm.com/zh/posts/entropy-from-devices-curated-by-the-kernel/</link><pubDate>Fri, 22 Oct 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/entropy-from-devices-curated-by-the-kernel/</guid><description>核对初始化状态，不把熵计数、种子文件或密钥时间戳当作安全证明。</description><content:encoded>&lt;p&gt;Linux 应用通常从内核的密码学安全生成器取得随机字节，不是每输出一位，就必须重新采集一位物理噪声。不过，初始化和状态复用仍然重要。机器繁忙不自动意味着安全，机器安静也不自动意味着故障。判断之前，应确认所用接口、内核版本，以及启动或克隆流程，不能只凭一个计数器就宣布密钥已经不安全。&lt;/p&gt;
&lt;h2 id="区分采集与输出"&gt;区分采集与输出&lt;/h2&gt;
&lt;p&gt;Linux 6.12 的实现把输入混入基于 BLAKE2s 的池，并用 ChaCha20 生成输出。这是该版本的实现细节，不是所有未来版本永远不变的接口契约。硬件来源、中断时序和引导程序输入，按具体平台的信任规则参与。加入字节与把这些字节计入初始化熵估算，是两件不同的事；内核不能假设用户任意提供的一串字节都包含新鲜、不可预测的信息。&lt;/p&gt;
&lt;p&gt;在这个实现中，poolsize 与初始化完成后的熵计数都达到二百五十六位，读取不会像消耗燃料一样扣减计数。因此，旧手册里四千零九十六位的描述，不应直接变成所有机器的告警阈值。应记录运行内核并检查对应行为，不能声称所有 Linux 版本的计数都一样，也不能仅因数字较小就判定随机输出薄弱。&lt;/p&gt;
&lt;h2 id="选择会等待初始化的接口"&gt;选择会等待初始化的接口&lt;/h2&gt;
&lt;p&gt;默认 getrandom 会等到初始化完成。带 GRND_NONBLOCK 时，尚未初始化会报告 EAGAIN，使应用能够说明状态而不无限等待。初始化以后，默认源的一次请求若不超过二百五十六字节，具有文档规定的完整读取与信号保证；调用方仍应检查错误和返回长度。更大请求要正常处理部分返回和中断，不能把小请求的保证扩大到所有缓冲区。&lt;/p&gt;
&lt;p&gt;random 设备从 Linux 5.6 起改变了行为：初始化后不再因为所谓熵耗尽而阻塞。在核对的 Linux 6.12 源码中，GRND_RANDOM 也不选择一个独立、会被耗尽的输出池。有些手册仍保留旧行为描述，不应把历史限制与当前内核结论混合。直接读取 urandom 可能发生在初始化之前；早期启动生成密钥，应使用支持初始化等待的接口。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;早期启动仍可能缺少足够的可信计入输入。容器中缺少设备节点则是另一类接口问题：getrandom 不需要设备路径，普通容器共享宿主内核。操作等待时，应有明确的启动失败或等待策略，而不是改用时间戳、可预测种子或不安全的备用输出。让服务尽快启动，不是降低密钥生成要求的理由。&lt;/p&gt;
&lt;p&gt;保存的种子可以帮助下一次启动，但把文件写回 urandom 并不会自动增加熵计数。一个三十二字节文件，或者某种硬件设备的存在，都不能单独保证立即就绪。应使用发行版提供的种子管理机制，了解它何时、依据什么信任条件计入熵。保护种子文件，避免在克隆镜像里分发相同的保存种子，并让引导程序专用的种子处理与操作系统流程一致。&lt;/p&gt;
&lt;p&gt;复制磁盘镜像和恢复运行中的虚拟机，是两种不同风险。前者可能复制种子文件或已经生成的主机密钥，后者可能复制正在使用的生成器内存状态。新输入及虚拟机生成标识处理，可以让后续状态分离。未来输出完全相同是需要调查的风险，不是每次克隆必然出现的结果；两台机器使用同一份种子，也不足以证明它们全部生成器状态完全一致。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;在 Linux 上运行下面的 Python 3 脚本。它读取计数器，并请求一次非阻塞的三十二字节输出；不会打印随机内容，不读取私钥，不更改种子文件，也不需要管理员权限。这样可以观察接口是否就绪，而不把诊断过程变成密钥材料收集或系统配置变更。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;platform&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;counters&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;entropy_avail&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;poolsize&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/proc/sys/kernel/random&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;OSError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counters&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getrandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GRND_NONBLOCK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;BlockingIOError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;kernel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ready&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;counters&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;kernel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ready&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;bytes_returned&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;before&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;after&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;counters&lt;/span&gt;&lt;span class="p"&gt;()}))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;本轮在 Linux 6.18.44、Python 3.12.14 上观察到 ready=true、bytes_returned=32，前后两个计数器都为二百五十六。这只是已初始化主机上的结果，不是早期启动、硬件质量或恢复虚拟机的测试。若源未初始化，程序会报告 ready=false；本轮没有通过修改宿主随机状态去制造该分支，也不能把未执行的分支标记成实测通过。&lt;/p&gt;
&lt;p&gt;成功调用说明当前接口在这一时刻提供了要求的字节，不是随机性统计检验，更不是密码学不可预测性的证明；比较两次样本也无法给出这种证明。如果 proc 访问受限，计数器可能显示错误类型，而系统调用仍然成功。应分别解释这些情况，不能把诊断信息不可读直接当作熵故障，也不能把成功返回当作所有启动和克隆风险均已排除。&lt;/p&gt;
&lt;h2 id="用直接证据调查身份复用"&gt;用直接证据调查身份复用&lt;/h2&gt;
&lt;p&gt;密钥文件的修改时间不能证明生成时使用了哪个随机源。文件会跨重启保存，复制可以保留时间戳，系统时钟也可能调整；看不到初始化日志同样不能证明初始化失败。应检查镜像准备流程、密钥生成代码、启动策略和重复的公钥指纹。确认身份重复或泄露时再制定轮换措施，不应仅凭时间戳对比就宣布一批密钥已经受损。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/torvalds/linux/v6.12/drivers/char/random.c"&gt;Linux 6.12 随机数实现&lt;/a&gt;，初始化计数、getrandom、sysctl 与虚拟机分叉重播种，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man2/getrandom.2.html"&gt;Linux getrandom(2)&lt;/a&gt;，初始化等待、小请求与错误；历史 GRND_RANDOM 描述需结合版本，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man4/random.4.html"&gt;Linux random(4)&lt;/a&gt;，写入不计熵及 Linux 5.6 阻塞变化；历史池大小不代表当前内核，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/systemd/systemd/v256/man/systemd-random-seed.service.xml"&gt;systemd 256 随机种子服务&lt;/a&gt;，种子生命周期、熵信任策略与镜像准备，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>