Linux 系統中隨機數在 KVM 中的應用
隨機數在計算機系統中處于非常重要的地位,如果沒有隨機數,可能很多應用都將陷入麻煩,隨機數在密碼學和安全領域也是至關重要。本文主要介紹隨機數的概念和重要性,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,該設備實現了一系列接口函數用于獲取系統環境的噪聲數據,并加入熵池。系統環境的噪聲數據包括設備兩次中斷間的間隔,輸入設備的操作時間間隔,連續磁盤操作的時間間隔等。 對應的接口包括:
- 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 個的接口來供其他內核模塊使用。
- 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/得到,比如:
- # 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()函數里。
- 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
- 在虛擬機 XML 定義中,在<devices>段中添加:
- <rng model='virtio'>
- <backend model='<strong>random</strong>'>/dev/random</backend>
- </rng>
- 使用 QEMU command Line 直接添加:
- -object <strong>rng-random</strong>,filename=/dev/random,id=rng0 \
- -device virtio-rng-pci,rng=rng0
虛擬機啟動后,在 Host 端:
- $ 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 設備。
- 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 已經識別到硬件隨機數產生器。
- $ dd if=/dev/hwrng of=/home/random-data bs=1
添加 bs 選項并且最好設置的值比較小,因為 Host 上隨機數資源可能會比較少,如果 bs 設置值太大,短時間內可能無法獲得足夠的數據寫入文件,同時在 Host 端多做一些鼠標鍵盤或者磁盤的操作,會更快地產生隨機數。
- $ hexdump /home/random-data
- 00000000 9501 e702 ....
- 00000010 .... .... ....
使用 EGD 協議來作為輸入源:
- 使用 libvirt 編輯虛擬機的 XML:
- <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 直接添加:
- -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 來獲取隨機數據。