<?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>Randomness · XB Field Notes</title><link>https://www.xbcatm.com/tags/randomness/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://www.xbcatm.com/tags/randomness/index.xml" rel="self" type="application/rss+xml"/><item><title>Kernel entropy turns device noise into keys</title><link>https://www.xbcatm.com/posts/entropy-from-devices-curated-by-the-kernel/</link><pubDate>Fri, 22 Oct 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/entropy-from-devices-curated-by-the-kernel/</guid><description>Cryptographic randomness on Linux is gathered from timing and interrupts and harvested into a pool. New keys should draw from the right pool.</description><content:encoded>&lt;p&gt;Even a quiet server seeds its generator before it needs it, so the randomness failures that still happen are not shortages. They are replays: a seed baked into an image, a snapshot resumed, a state restored from before the last reseed. The kernel does not drip entropy at you; it concentrates a little device noise into a generator, then produces keystream indefinitely.&lt;/p&gt;
&lt;p&gt;That inversion is why most of the operational folklore around entropy is now wrong: watching &lt;code&gt;entropy_avail&lt;/code&gt;, reading &lt;code&gt;/dev/random&lt;/code&gt; for keys, and installing an entropy daemon all address a scarcity the kernel stopped having, while the thing that genuinely breaks keys, an identical seed on two machines, is invisible to all of them.&lt;/p&gt;
&lt;h2 id="a-pool-goes-in-a-generator-comes-out"&gt;A pool goes in, a generator comes out&lt;/h2&gt;
&lt;p&gt;The kernel mixes physical noise, including interrupt timing jitter, device latencies, hardware RNGs where available, and CPU-based jitterentropy, and extracts it with BLAKE2s into an entropy pool. That pool seeds a set of ChaCha20 CRNGs, and the CRNGs are what actually produce bytes. Collection continues and the generator is periodically reseeded, but the strength of a byte you already emitted is fixed by the state it came from.&lt;/p&gt;
&lt;p&gt;Once initialized, the output is keystream, not a finite resource. &lt;code&gt;entropy_avail&lt;/code&gt; sits at 256 bits, the size of a ChaCha20 key, and it does not fall when you read; a megabyte read consumes no pool credit at all. random(4) still describes &lt;code&gt;entropy_avail&lt;/code&gt; as a value in the range 0 to 4096 and &lt;code&gt;poolsize&lt;/code&gt; as 4096 bits on Linux 2.6 and later: documentation that keeps a stale alert alive.&lt;/p&gt;
&lt;h2 id="devrandom-stopped-being-the-careful-choice"&gt;/dev/random stopped being the careful choice&lt;/h2&gt;
&lt;p&gt;Since Linux 5.6, &lt;code&gt;/dev/random&lt;/code&gt; no longer blocks outside early boot, and &lt;code&gt;O_NONBLOCK&lt;/code&gt; is ignored on it. The separate blocking pool is gone; the same generator now sits behind a legacy name. &lt;code&gt;getrandom(2)&lt;/code&gt;, added in Linux 3.17, draws from the urandom source by default and blocks only while that source is uninitialized. &lt;code&gt;GRND_NONBLOCK&lt;/code&gt; turns that block into &lt;code&gt;EAGAIN&lt;/code&gt;, and &lt;code&gt;GRND_RANDOM&lt;/code&gt; selects the random source instead, the one that can still block without an upper bound.&lt;/p&gt;
&lt;p&gt;random(7) is blunt: unless you are doing long-term key generation, and probably not even then, avoid &lt;code&gt;/dev/random&lt;/code&gt; and &lt;code&gt;GRND_RANDOM&lt;/code&gt;. Once initialized, a read of up to 256 bytes always returns in full and is not interrupted by signals. The price of the safer interface is a call that can hang indefinitely.&lt;/p&gt;
&lt;h2 id="boot-is-the-only-window-that-matters"&gt;Boot is the only window that matters&lt;/h2&gt;
&lt;p&gt;The real failure lives in the moments before initialization, which the kernel announces with a &lt;code&gt;crng init done&lt;/code&gt; line. A machine with no saved seed has to accumulate one from device noise; a machine with a seed file, a &lt;code&gt;virtio-rng&lt;/code&gt; device or RDRAND has it immediately. random(4)&amp;rsquo;s recipe remains the cheapest fix: at shutdown, &lt;code&gt;dd&lt;/code&gt; 512 bytes from &lt;code&gt;/dev/urandom&lt;/code&gt; into a mode-600 seed file, and at boot &lt;code&gt;cat&lt;/code&gt; it back before anything asks for a key.&lt;/p&gt;
&lt;p&gt;The asymmetry is stark: a 32-byte seed file or a single &lt;code&gt;virtio-rng&lt;/code&gt; read reaches the 256-bit threshold instantly, while an interrupt-only boot accumulates thousands of samples first. Whether a key is minted before or after &lt;code&gt;crng init done&lt;/code&gt; is a configuration decision, not luck.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;The first failure is a first-boot key that blocks. A unit minting a TLS key or a token secret during its first start can call &lt;code&gt;getrandom()&lt;/code&gt; before initialization and sit there with nothing useful in the log. Diskless machines, small routers, virtual machines restored from a frozen filesystem and containers with no entropy device are the usual victims. Compare the key file&amp;rsquo;s mtime against the &lt;code&gt;crng init done&lt;/code&gt; line: a key older than that line came from an uninitialized generator.&lt;/p&gt;
&lt;p&gt;The second failure is silent: nothing blocks and every dashboard looks healthy. Snapshot a virtual machine, roll it back, or stamp ten cloud instances from one golden image, and each copy resumes with the same generator state and the same seed file. They then emit identical SSH host keys, duplicate UUIDs from &lt;code&gt;/proc/sys/kernel/random/uuid&lt;/code&gt;, and overlapping TLS session tickets, so one instance&amp;rsquo;s output predicts its siblings. Ten clones sharing one baked-in seed produced a single distinct fingerprint; ten reseeded clones produced ten.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Four commands, in this order:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;dmesg | grep -i &amp;#34;crng init done&amp;#34;
cat /proc/sys/kernel/random/entropy_avail /proc/sys/kernel/random/poolsize
python3 -c &amp;#39;import os; print(os.getrandom(8, os.GRND_NONBLOCK).hex())&amp;#39;
stat -c &amp;#39;%y %n&amp;#39; /etc/ssh/ssh_host_ed25519_key /var/lib/random-seed
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On a modern kernel the counter reads 256 and &lt;code&gt;poolsize&lt;/code&gt; reads 4096: the first number is a key size, not a fuel gauge, so a low reading is not a problem to fix. The &lt;code&gt;getrandom&lt;/code&gt; call should return eight hex bytes immediately; &lt;code&gt;BlockingIOError&lt;/code&gt; means the source is uninitialized and a blocking call would stall. The &lt;code&gt;stat&lt;/code&gt; output is the real check, because a host key older than the &lt;code&gt;crng init done&lt;/code&gt; line came from an uninitialized generator and should be rotated.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man4/random.4.html"&gt;Linux random(4) device&lt;/a&gt;, /dev/random and /dev/urandom semantics, entropy_avail and poolsize, plus the seed-file recipe, checked 2026-09-16.&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_NONBLOCK and GRND_RANDOM, and blocking until the urandom source is initialized, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man7/random.7.html"&gt;Linux random(7)&lt;/a&gt;, the interface comparison table and when not to use GRND_RANDOM, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.archlinux.org/title/Random_number_generation"&gt;ArchWiki: random number generation&lt;/a&gt;, BLAKE2s extraction seeding ChaCha20 CRNGs, and why entropy_avail stays at 256, checked 2026-09-16.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>