隨機數在計算機系統中處於非常重要的地位,如果沒有隨機數,可能很多應用都將陷入麻煩,隨機數在密碼學和安全領域也是至關重要。本文主要介紹隨機數的概念和重要性,Linux 系統中隨機數是如何產生的,最後介紹在 KVM 虛擬機器中如何新增和使用硬體隨機數產生器來產生隨機數。
什麼是隨機數
很多軟體和應用都需要隨機數,從紙牌遊戲中紙牌的分發到 SSL 安全協議中金鑰的產生,到處都有隨機數的身影。隨機數至少具備兩個條件:
- 數字序列在統計上是隨機的
- 不能通過已知序列推算後面的序列
自從計算機誕生起,尋求用計算機產生高質量的隨機數序列的研究就一直是研究者長期關注的課題。一般情況下,使用計算機程式產生一個真正的隨機數是很難的,因為程式的行為是可預測的,計算機利用設計好的演算法結合使用者提供的種子產生的隨機數序列通常是“偽隨機數”(pseudo-random number),偽隨機數就是我們平時經常使用的“隨機數”。偽隨機數可以滿足一般應用的需求,但是在對於安全要求比較高的環境和領域中存在明顯的缺點:
- 偽隨機數是週期性的,當它們足夠多時,會重複數字序列
- 如果提供相同的演算法和相同的種子值,將會得出完全一樣的隨機數序列
- 可以使用逆向工程,猜測演算法與種子值,以便推算後面所有的隨機數列
只有實際物理過程才是真正的隨機,只有藉助物理世界中事物的隨機性才能產生真正的隨機數,比如真空內亞原子粒子量子漲落產生的噪音、超亮發光二極體在噪聲的量子不確定性和放射性衰變等。
隨機數為什麼如此重要
生成隨機數是密碼學中的一項基本任務,是生成加密金鑰、加密演算法和加密協議所必不可少的,隨機數的質量對安全性至關重要。最近報導有人利用隨機數缺點成功攻擊了某網站,獲得了管理員的許可權。美國和法國的安全研究人員最近也評估了兩個 Linux 核心 PRNG——/dev/random 和/dev/urandom 的安全性,認為 Linux 的偽隨機數生成器不滿足魯棒性的安全概念,沒有正確積累熵。可見隨機數在安全系統中佔據著非常重要的地位。
Linux 中隨機數如何產生
PRNG(Pseudo-Random Number Generator)
1994 年,美國軟體工程師 Theodore Y. Ts’o 第一次在 Linux 核心中實現了隨機數發生器,使用 SHA-1 雜湊演算法而非密碼,提高了密碼強度。
Linux 核心採用熵來描述資料的隨機性,熵(entropy)是描述系統混亂無序程度的物理量,一個系統的熵越大則說明該系統的有序性越差,即不確定性越大。核心維護了一個熵池用來收集來自裝置驅動程式和其它來源的環境噪音。理論上,熵池中的資料是完全隨機的,可以實現產生真隨機數序列。為跟蹤熵池中資料的隨機性,核心在將資料加入池的時候將估算資料的隨機性,這個過程稱作熵估算。熵估算值描述池中包含的隨機數位數,其值越大表示池中資料的隨機性越好。 核心中隨機數發生器 PRNG 為一個字元裝置 random,程式碼實現在 drivers/char/random.c,該裝置實現了一系列介面函式用於獲取系統環境的噪聲資料,並加入熵池。系統環境的噪聲資料包括裝置兩次中斷間的間隔,輸入裝置的操作時間間隔,連續磁碟操作的時間間隔等。 對應的介面包括:
1 2 3 4 5 |
void add_device_randomness(const void *buf, unsigned int size); void add_input_randomness(unsigned int type, unsigned int code, unsigned int value); void add_interrupt_randomness(int irq, int irq_flags); void add_disk_randomness(struct gendisk *disk); |
核心提供了 1 個的介面來供其他核心模組使用。
1 |
void get_random_bytes(void *buf, int nbytes); |
該介面會返回指定位元組數的隨機數。random 裝置了提供了 2 個字元裝置供使用者態程式使用——/dev/random 和/dev/urandom:
- /dev/random 適用於對隨機數質量要求比較高的請求,在熵池中資料不足時, 讀取 dev/random 裝置時會返回小於熵池噪聲總數的隨機位元組。/dev/random 可生成高隨機性的公鑰或一次性密碼本。若熵池空了,對/dev/random 的讀操作將會被阻塞,直到收集到了足夠的環境噪聲為止。這樣的設計使得/dev/random 是真正的隨機數發生器,提供了最大可能的隨機資料熵。
- /dev/urandom,非阻塞的隨機數發生器,它會重複使用熵池中的資料以產生偽隨機資料。這表示對/dev/urandom 的讀取操作不會產生阻塞,但其輸出的熵可能小於/dev/random 的。它可以作為生成較低強度密碼的偽隨機數生成器,對大多數應用來說,隨機性是可以接受的。
/dev/random 也允許寫入,任何使用者都可以向熵池中加入隨機資料。即使寫入非隨機資料亦是無害的,因為只有管理員可以呼叫 ioctl 以增加熵池大小。Linux 核心中當前熵的值和大小可以通過訪問 /proc/sys/kernel/random/得到,比如:
1 2 3 4 5 6 |
# cat /proc/sys/kernel/random/poolsize 4096 # cat /proc/sys/kernel/random/entropy_avail 298 # cat /proc/sys/kernel/random/uuid 4f0683ae-6141-41e1-b5b9-57f4bd299219 |
但是 Linux 核心中隨機發生器中存在幾個弱點,在嵌入式系統(缺少滑鼠鍵盤),Live CD 系統(缺少磁碟),路由器,無盤工作站和一些伺服器系統中,環境熵的來源較為受限,隨機數質量會有所下降。對於有 NVRAM 的系統,建議在關機時儲存一部分隨機數發生器的狀態,使得在下次開機時可以恢復這些狀態。對於路由器而言,可以考慮把網路資料可以作為熵的主要來源。
EGD
EGD(熵收集守護程式,entropy gathering daemon)通常可以在不支援/dev/random 裝置的 Unix 系統中提供類似的功能。這是一個執行於使用者態的守護程式,提供了高質量的密碼用隨機資料。一些加密軟體,比如 OpenSSL,GNU Privacy Guard 和 Apache HTTP 伺服器支援在/dev/random 不可用的時候使用 EGD。
EGD,或者類似的軟體 prngd,可以從多種來源收集偽隨機的熵,並對這些資料進行處理以去除偏置,並改善密碼學質量,然後允許其它程式通過 Unix 域套介面(通常使用/dev/egd-pool),或 TCP 套介面訪問其輸出。該程式通常使用建立子程式的以查詢系統狀態的方式來收集熵。它查詢的狀態通常是易變和不可預測的,例如 CPU,I/O,網路的使用率,也可能是一些日誌檔案和臨時目錄中的內容。
EGD 通過一個簡單的協議與那些需要隨機數的客戶端進行通訊,客戶端通過連線 EGD socket 傳送命令(從前八位來識別命令):
- command 0: 查詢當前可用熵
- command 1: 非阻塞地獲取隨機位元組數
- command 2: 阻塞地獲取隨機位元組數
- command 3: 更新熵
硬體隨機數產生器
當前有很多硬體隨機數產生器(hwrng)用於產生可靠的隨機數,但都是商用的,價格比較昂貴,最常使用的是 ComScire QNG,截止筆者寫這篇文章,ComScire PQ4000KU 的官方價格接近 900 美元。
Intel’s Ivy Bridge family 有一個功能叫”Secure Key”, 處理器包含了一個內部硬體 DRNG(Digital Random Number Generator)用於產生隨機數,使用匯編指令 RDRAND 即可獲得高強度的隨機數,Linux Kernel 會使用異或操作把 RDRAND 產生的隨機數混合進熵池, 程式碼實現在 drivers/char/random.c 的 extract_entropy()函式裡。
1 2 3 4 5 6 |
for (i = 0; i < LONGS(EXTRACT_SIZE); i++) { unsigned long v; if (!arch_get_random_long(&v)) break; hash.l[i] ^= v; } |
還有一些第三方的硬體隨機數生成器,通常是 USB 或者 PCI 裝置,主要是在伺服器上使用。Linux Kernel 的 hwrng(hardware random number generator)抽象層(/dev/hwrng 裝置)可以選擇監控 RNG 裝置,並且在熵池資料不足的時候要求裝置提供隨機資料到 kernel 的熵池,rngd 守護程式可以讀取 hwrng 的資料然後補給到 kernel 的熵池中。
在 KVM 虛擬機器中如何應用
虛擬機器環境下和伺服器情況類似,輸入裝置操作很少,相對於 Host 而言,Disk I/O 也相對較少,因此依賴 Guest 自身 PRNG 產生的隨機數質量不高,因此虛擬機器通常從 Host(宿主機)獲取部分隨機資料。對於 KVM 虛擬機器來說,存在一個半虛擬化裝置 virtio-rng 作為硬體隨機數產生器。Linux Kernel 從 2.6.26 開始支援 virtio-rng, QEMU 在 1.3 版本加入了對 virtio-rng 的支援。 virtio-rng 裝置會讀取 Host 的隨機數源並且填充到 Guest(客戶機)的熵池中。通常情況下使用/dev/random 作為輸入源。當然,資料來源可以更改,當 Host 系統中存在 hwrng 的情況下你可以使用/dev/hwrng 來作為 virtio-rng 的輸入源。 也可以把 hwrng 裝置 pass-through(透傳)到客戶機中,但是並不實用,比如在虛擬機器 Live Migration(實時遷移)時會存在問題。在 Guest 中新增 virtio-rng 裝置具體操作,使用/dev/random 作為輸入源,兩種方法:
- 使用 libvirt 編輯虛擬機器的 XML
12345在虛擬機器 XML 定義中,在<devices>段中新增:<rng model='virtio'><backend model='<strong>random</strong>'>/dev/random</backend></rng>
- 使用 QEMU command Line 直接新增:
12-object <strong>rng-random</strong>,filename=/dev/random,id=rng0 \-device virtio-rng-pci,rng=rng0
虛擬機器啟動後,在 Host 端:
1 2 3 |
$ lsof /dev/random COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME qemu-syst 23590 mars 11r CHR 1,8 0t0 1032 /dev/random |
會看到當前 QEMU 程式正在使用/dev/random 裝置。
1 2 3 4 5 6 7 8 |
Guest 端: $ cat /sys/devices/virtual/misc/hw_random/rng_available virtio $ cat /sys/devices/virtual/misc/hw_random/rng_current virtio $ lsmod | grep virtio_rng virtio_rng 12790 0 .... |
可以看到 Guest 已經識別到硬體隨機數產生器。
1 |
$ dd if=/dev/hwrng of=/home/random-data bs=1 |
新增 bs 選項並且最好設定的值比較小,因為 Host 上隨機數資源可能會比較少,如果 bs 設定值太大,短時間內可能無法獲得足夠的資料寫入檔案,同時在 Host 端多做一些滑鼠鍵盤或者磁碟的操作,會更快地產生隨機數。
1 2 3 |
$ hexdump /home/random-data 00000000 9501 e702 .... 00000010 .... .... .... |
使用 EGD 協議來作為輸入源
- 使用 libvirt 編輯虛擬機器的 XML:
12345<rng model='virtio'><backend model='<strong>egd</strong>' type='tcp'><source mode='connect' host='127.0.0.1' service='8000'/></backend></rng>
- 使用 QEMU command Line 直接新增:
123-chardev socket,host=localhost,port=1024,id=chr0 \-object <strong>rng-egd</strong>,chardev=chr0,id=rng0 \-device virtio-rng- pci,rng=rng0
總結
隨機數在計算機系統中有著非常重要的作用,本文闡述了隨機數的概念和重要性,介紹了在 Linux 中產生隨機數的方法,以及在 KVM 環境下虛擬機器如何使用 virtio-rng 來獲取隨機資料。
參考資料
學習
- 《Linux RNG May Be Insecure After All》: 關於 Linux RNG 安全性的一篇報導。
- 《Make your software behave: Beating the bias》: 瞭解更多如何通過硬體獲取隨機數的資訊。
- 《What is Intel Secure Key Technology?》: 瞭解關於 Intel Secure Key 科技的相關資訊。
- RDRAND 的維基百科:瞭解更多 RDRAND 指令的資訊。
- QEMU 中對 virtio RNG 特性的實現:瞭解 QEMU 如何使用 virtio RNG 裝置。
- 在 developerWorks Linux 專區尋找為 Linux 開發人員(包括 Linux 新手入門)準備的更多參考資料。