# Documentation and Analysis of the Linux Random Number Generator [Documentation and Analysis of the Linux Random Number Generator](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Studies/LinuxRNG/LinuxRNG_EN_V4_5.pdf?__blob=publicationFile&v=9) ### Big picture of Linux Random Number Generator Linux‑RNG 在 `drivers/char/random.c` 中實作,透過裝置檔案系統 `/dev/random` 和 `/dev/urandom` 以及 `getrandom` sys.call 為使用者空間應用程式提供對其隨機數產生器的存取。此外提供API `get_random_bytes` ,允許其他 Linux 核心元件獲取隨機數。 ![image](https://hackmd.io/_uploads/ryk3wXKI0.png) ### NDRNG Noise Source: 1. 在實體設備上:智慧卡、特殊電路、硬體安全模組 (HSM) 等。 2. 常規硬體的事件行為:人機互動(e.g.滑鼠移動或鍵盤打字)、硬碟或中斷。 3. 利用 CPU 功能:time-based 噪音源、使用 RDRAND 等硬體噪音源的 CPU 指令 ![image](https://hackmd.io/_uploads/r1u6FmYUA.png =30%x) ## Linux‑RNG ### Linux‑RNG 架構 * 發生硬體事件後,Linux‑RNG 會進行熵估計 * 將事件時間&值混合到 4096 bits 的熵池 (input_pool) * 熵池 (input_pool) * 指的是保存隨機資料的儲存區域 * 資料透過確定性輸入和 LFSR-based 的狀態轉換函數進行處理,再以 Blake2s-based 雜湊函數輸出 * 由熵池維護的資料處理是完全確定性的 ![image](https://hackmd.io/_uploads/HJ2xnXFUC.png) * input_pool 是收集並壓縮來自硬體事件的熵的熵池。此熵池的大小為 4,096 位元。 * ChaCha20 DRNG 的內部狀態為 512 位元。然而,只有 256 位元(ChaCha20 狀態的關鍵部分)充滿了隨機資料,其從 input_pool 取得其種子數據,並可透過以下方式存取: * 透過`/dev/urandom`、`/dev/random` 或 `getrandom` 系統呼叫從使用者空間獲取 * 透過 `get_random_bytes`函數從核心空間取得。 ### input_pool ##### structure ```cpp // the pointer to the static data block that holds the actual static u32 input_pool_data[POOL_WORDS] __latent_entropy; random pool. static struct { ... u16 add_ptr; // the index to the current pool word that was accessed u16 input_rotate; // the input data rotation value int entropy_count; // the entropy estimator value } ``` #### Entropy Pool State Transition Function LFSR 用於將新值插入熵池,並選擇適當的多項式 "stir" 熵池內隨機數後 "twist",可以看到在 twist 中一次扭轉三位以降低計算成本。 ```cpp enum poolinfo { POOL_WORDS = 128, POOL_WORDMASK = POOL_WORDS - 1, POOL_BYTES = POOL_WORDS * sizeof(u32), POOL_BITS = POOL_BYTES * 8, POOL_BITSHIFT = ilog2(POOL_BITS), ... /* x^128 + x^104 + x^76 + x^51 +x^25 + x + 1 */ POOL_TAP1 = 104, POOL_TAP2 = 76, POOL_TAP3 = 51, POOL_TAP4 = 25, POOL_TAP5 = 1, ... } static u32 const twist_table[8] = { 0x00000000, 0x3b6e20c8, 0x76dc4190, 0x4db26158, 0xedb88320, 0xd6d6a3e8, 0x9b64c2b0, 0xa00ae278 }; ``` 1. 選擇指向將要更新的熵池字的索引。 2. 將步驟 1 中的 4 個位元組與以下內容進行 XOR 運算: 1. 步驟 1 所取得的索引值所指向的目前熵池字 2. 由此索引加上 LFSR 多項式的第一個 tap 所指向的目前熵池字 3. 透過將輸入與步驟 2 中多項式的抽頭指向的 5 個不同熵池字進行 XOR 運算計算得到的 u32 值,再透過與 twist_table 的一個值進行 XOR 運算進一步攪拌。這項操作使用雙射操作來排列字的前三位。這個想法是透過「扭曲」將這三位混合。 4. 步驟 3 計算所得的值替換步驟 1 中索引指向的熵池字的先前值,成為新的值。 5. 重複步驟 1,直到所有輸入位元組都混合到熵池值中。 ```cpp static void _mix_pool_bytes(struct entropy_store *r, const void *in, int nbytes) { ... input_rotate = input_pool.input_rotate; i = input_pool.add_ptr; /* mix one byte at a time to simplify size handling and churn faster */ while (nbytes--) { w = rol32(*bytes++, input_rotate); i = (i - 1) & POOL_WORDMASK; /* XOR in the various taps */ w ^= input_pool_data[i]; w ^= input_pool_data[(i + POOL_TAP1) & POOL_WORDMASK]; w ^= input_pool_data[(i + POOL_TAP2) & POOL_WORDMASK]; w ^= input_pool_data[(i + POOL_TAP3) & POOL_WORDMASK]; w ^= input_pool_data[(i + POOL_TAP4) & POOL_WORDMASK]; w ^= input_pool_data[(i + POOL_TAP5) & POOL_WORDMASK]; /* Mix the result back in with a twist */ input_pool_data[i] = (w >> 3) ^ twist_table[w & 7]; /* * Normally, we add 7 bits of rotation to the pool. * At the beginning of the pool, add an extra 7 bits * rotation, so that successive passes spread the * input bits across the pool evenly. */ input_rotate = (input_rotate + (i ? 7 : 14)) & 31; } input_pool.input_rotate = input_rotate; input_pool.add_ptr = i; } ``` ![image](https://hackmd.io/_uploads/ByIUBEFUA.png =50%x) #### Entropy Estimator 熵估計器的值永遠不會**大於對應的熵池的大小**,因為熵池最多可以容納與其大小一樣多的熵,也不能全部小於零。另外**必須使用與其比較或處理的值相同的維度進行處理**。 ![image](https://hackmd.io/_uploads/BJECsrKLA.png =75%x) * 當對具有分數位的整數值進行操作時,整數值將保持不變。 * 取得目前位元總數中的熵量: right-shift 3 bits * 以位元組為單位處理熵池的熵內容: right-shift 6 bits (8bits=1byte) ##### Entropy Pool Output Function ```cpp #define ENTROPY_SHIFT 3 static size_t account(size_t nbytes, int min) { ... entropy_count = orig = READ_ONCE(r->entropy_count); /* never pull more than available */ ibytes = min_t(size_t, nbytes, entropy_count >> (POOL_ENTROPY_SHIFT + 3)); if (ibytes < min) ibytes = 0; nfrac = ibytes << (ENTROPY_SHIFT + 3); if ((size_t) entropy_count > nfrac) entropy_count -= nfrac; else entropy_count = 0; if (cmpxchg(&r->entropy_count, orig, entropy_count) != orig) ... return ibytes; } ``` ### ChaCha20 DRNG ##### structure ```cpp struct crng_state { u32 state[16]; unsigned long init_time; ... }; ``` ![image](https://hackmd.io/_uploads/rk2tprYUA.png) ## Interfaces to Linux-RNG ##### structure ```cpp static const struct memdev { const char *name; mode_t mode; const struct file_operations *fops; struct backing_dev_info *dev_info; } devlist[] = { ... [8] = { "random", 0666, &random_fops, NULL }, [9] = { "urandom", 0666, &urandom_fops, NULL }, ... }; ``` ##### The callback functions for `/dev/random` ```cpp const struct file_operations random_fops = { .read = random_read, .write = random_write, .poll = random_poll, .unlocked_ioctl = random_ioctl, ... .fasync = random_fasync, .llseek = noop_llseek, }; ``` #### random_poll * read: 當 ChaCha20 DRNG 已經 fully seeded 表明有足夠的熵可用時,內核會喚醒輪詢進程,以允許它們通過單獨的調用獲取數據。 * 允許用戶空間 asynchronously poll /dev/random 以避免在讀取 /dev/random 時出現阻塞行為 * random_poll 會保持成功返回直到下次重新啟動 * write: 當entropy estimator低於給定閾值表明可用熵不足時,核心會喚醒等待寫輪詢的進程。 #### Read and Write Operation dev/random:使用該裝置檔案存取隨機數產生器時,會呼叫`random_read`的讀取函數。該函數會阻止呼叫者,直到 ChaCha20 DRNG fully seeded。一旦發生這種情況,就會產生與下面概述的 `/dev/urandom` 相同的隨機數。 ```cpp static int write_pool(const char __user *buffer, size_t count) { ... __u32 buf[16]; ... while (count > 0) { bytes = min(count, sizeof(buf)); ... count -= bytes; ... mix_pool_bytes(buf, bytes); ... } } ``` ## Entropy Collection ### add_input_randomness 將目前事件的事件值與前一事件的事件值進行比較。如果兩個事件值相同(例如,滑鼠向一個方向移動兩步或按同一鍵兩次),則該事件將被丟棄。否則,事件將會被加入到 input_pool 中。 ```cpp void add_input_randomness(unsigned int type, unsigned int code, unsigned int value) { static unsigned char last_value; /* ignore autorepeat and the like */ if (value == last_value) return; ... last_value = value; add_timer_randomness(&input_timer_state, (type << 4) ^ code ^ (code >> 4) ^ value); ... } ``` ### add_interrupt_randomness ```cpp void add_interrupt_randomness(int irq) { ... struct fast_pool *fast_pool = this_cpu_ptr(&irq_randomness); struct pt_regs *regs = get_irq_regs(); unsigned long now = jiffies; cycles_t cycles = random_get_entropy(); ... c_high = (sizeof(cycles) > 4) ? cycles >> 32 : 0; j_high = (sizeof(now) > 4) ? now >> 32 : 0; fast_pool->pool[0] ^= cycles ^ j_high ^ irq; fast_pool->pool[1] ^= now ^ c_high; ip = regs ? instruction_pointer(regs) : _RET_IP_; fast_pool->pool[2] ^= ip; fast_pool->pool[3] ^= (sizeof(ip) > 4) ? ip >> 32 : get_reg(fast_pool, regs); fast_mix(fast_pool); ... if ((fast_pool->count < 64) && !time_after(now, fast_pool->last + HZ)) return; ... fast_pool->last = now; __mix_pool_bytes(&fast_pool->pool, sizeof(fast_pool->pool)); ... fast_pool->count = 0; /* award one bit for the contents of the fast pool */ credit_entropy_bits(1); } ``` 1. 必須滿足中斷次數和過期時間這兩個條件。 2. ast_pool 的最後一次讀取的時間戳設定為目前時間。 3. ast_pool 的內容會混合到 input_pool 中。 4. 將接收的中斷數重設為零,以進行步驟 1 中的初始條件檢查。 5. 將 input_pool 的熵估計器增加 1 – i CPU 硬體隨機數產生器存在時,熵估計量增加 2。 ### add_disk_randomness ```cpp #define QUEUE_FLAG_DEFAULT ((1 << QUEUE_FLAG_IO_STAT) | \ (1 << QUEUE_FLAG_STACKABLE) | \ (1 << QUEUE_FLAG_SAME_COMP) | \ (1 << QUEUE_FLAG_ADD_RANDOM)) void add_disk_randomness(struct gendisk *disk) { if (!disk || !disk->random) return; ... add_timer_randomness(disk->random, 0x100 + disk_devt(disk)); } ``` ### add_device_randomness `add_device_randomness` 的目標是在裝置驅動程式初始化期間向 input_pool 提供資料,在啟動期間,ChaCha20 DRNG 被視為first seeded 之前, `add_device_randomness`接收到的所有資料都將注入 ChaCha20 DRNG 而不是 input_pool。這將保證在 boot 期間源自 `/dev/urandom` 的資料流能夠從輸入資料中獲益。 ### add_hwgenerator_randomness 用於核心硬體 RNG 裝置驅動程式,其介面框架將獲得的熵透過`mix_pool_bytes`直接混合到 input_pool 中。 ![image](https://hackmd.io/_uploads/B1mHcuYL0.png) 硬體 RNG 驅動程式框架實作了一個核心線程,該線程不斷從註冊的硬體 RNG 設備讀取數據,並使用從硬體設備獲取的數據呼叫`add_hwgenerator_randomness`介面函數,如果 input_pool 的熵估計器中的值全部低於變數`random_write_wakeup_bits`設定的閾值,則允許呼叫者將資料送入 Linux‑ RNG。