Linux 系統(tǒng)中隨機(jī)數(shù)在 KVM 中的應(yīng)用
隨機(jī)數(shù)在計(jì)算機(jī)系統(tǒng)中處于非常重要的地位,如果沒(méi)有隨機(jī)數(shù),可能很多應(yīng)用都將陷入麻煩,隨機(jī)數(shù)在密碼學(xué)和安全領(lǐng)域也是至關(guān)重要。本文主要介紹隨機(jī)數(shù)的概念和重要性,Linux 系統(tǒng)中隨機(jī)數(shù)是如何產(chǎn)生的,最后介紹在 KVM 虛擬機(jī)中如何添加和使用硬件隨機(jī)數(shù)產(chǎn)生器來(lái)產(chǎn)生隨機(jī)數(shù)。
什么是隨機(jī)數(shù)
很多軟件和應(yīng)用都需要隨機(jī)數(shù),從紙牌游戲中紙牌的分發(fā)到 SSL 安全協(xié)議中密鑰的產(chǎn)生,到處都有隨機(jī)數(shù)的身影。隨機(jī)數(shù)至少具備兩個(gè)條件:
- 數(shù)字序列在統(tǒng)計(jì)上是隨機(jī)的
- 不能通過(guò)已知序列推算后面的序列
自從計(jì)算機(jī)誕生起,尋求用計(jì)算機(jī)產(chǎn)生高質(zhì)量的隨機(jī)數(shù)序列的研究就一直是研究者長(zhǎng)期關(guān)注的課題。一般情況下,使用計(jì)算機(jī)程序產(chǎn)生一個(gè)真正的隨機(jī)數(shù)是很難的,因?yàn)槌绦虻男袨槭强深A(yù)測(cè)的,計(jì)算機(jī)利用設(shè)計(jì)好的算法結(jié)合用戶提供的種子產(chǎn)生的隨機(jī)數(shù)序列通常是“偽隨機(jī)數(shù)”(pseudo-random number),偽隨機(jī)數(shù)就是我們平時(shí)經(jīng)常使用的“隨機(jī)數(shù)”。偽隨機(jī)數(shù)可以滿足一般應(yīng)用的需求,但是在對(duì)于安全要求比較高的環(huán)境和領(lǐng)域中存在明顯的缺點(diǎn):
- 偽隨機(jī)數(shù)是周期性的,當(dāng)它們足夠多時(shí),會(huì)重復(fù)數(shù)字序列
- 如果提供相同的算法和相同的種子值,將會(huì)得出完全一樣的隨機(jī)數(shù)序列
- 可以使用逆向工程,猜測(cè)算法與種子值,以便推算后面所有的隨機(jī)數(shù)列
只有實(shí)際物理過(guò)程才是真正的隨機(jī),只有借助物理世界中事物的隨機(jī)性才能產(chǎn)生真正的隨機(jī)數(shù),比如真空內(nèi)亞原子粒子量子漲落產(chǎn)生的噪音、超亮發(fā)光二極管在噪聲的量子不確定性和放射性衰變等。
隨機(jī)數(shù)為什么如此重要
生成隨機(jī)數(shù)是密碼學(xué)中的一項(xiàng)基本任務(wù),是生成加密密鑰、加密算法和加密協(xié)議所必不可少的,隨機(jī)數(shù)的質(zhì)量對(duì)安全性至關(guān)重要。最近報(bào)道有人利用隨機(jī)數(shù)缺點(diǎn)成功攻擊了某網(wǎng)站,獲得了管理員的權(quán)限。美國(guó)和法國(guó)的安全研究人員最近也評(píng)估了兩個(gè) Linux 內(nèi)核 PRNG——/dev/random 和/dev/urandom 的安全性,認(rèn)為 Linux 的偽隨機(jī)數(shù)生成器不滿足魯棒性的安全概念,沒(méi)有正確積累熵??梢?jiàn)隨機(jī)數(shù)在安全系統(tǒng)中占據(jù)著非常重要的地位。
Linux 中隨機(jī)數(shù)如何產(chǎn)生
PRNG(Pseudo-Random Number Generator)
1994 年,美國(guó)軟件工程師 Theodore Y. Ts’o 第一次在 Linux 內(nèi)核中實(shí)現(xiàn)了隨機(jī)數(shù)發(fā)生器,使用 SHA-1 散列算法而非密碼,提高了密碼強(qiáng)度。
Linux 內(nèi)核采用熵來(lái)描述數(shù)據(jù)的隨機(jī)性,熵(entropy)是描述系統(tǒng)混亂無(wú)序程度的物理量,一個(gè)系統(tǒng)的熵越大則說(shuō)明該系統(tǒng)的有序性越差,即不確定性越大。內(nèi)核維護(hù)了一個(gè)熵池用來(lái)收集來(lái)自設(shè)備驅(qū)動(dòng)程序和其它來(lái)源的環(huán)境噪音。理論上,熵池中的數(shù)據(jù)是完全隨機(jī)的,可以實(shí)現(xiàn)產(chǎn)生真隨機(jī)數(shù)序列。為跟蹤熵池中數(shù)據(jù)的隨機(jī)性,內(nèi)核在將數(shù)據(jù)加入池的時(shí)候?qū)⒐浪銛?shù)據(jù)的隨機(jī)性,這個(gè)過(guò)程稱作熵估算。熵估算值描述池中包含的隨機(jī)數(shù)位數(shù),其值越大表示池中數(shù)據(jù)的隨機(jī)性越好。 內(nèi)核中隨機(jī)數(shù)發(fā)生器 PRNG 為一個(gè)字符設(shè)備 random,代碼實(shí)現(xiàn)在 drivers/char/random.c,該設(shè)備實(shí)現(xiàn)了一系列接口函數(shù)用于獲取系統(tǒng)環(huán)境的噪聲數(shù)據(jù),并加入熵池。系統(tǒng)環(huán)境的噪聲數(shù)據(jù)包括設(shè)備兩次中斷間的間隔,輸入設(shè)備的操作時(shí)間間隔,連續(xù)磁盤(pán)操作的時(shí)間間隔等。 對(duì)應(yīng)的接口包括:
- 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);
內(nèi)核提供了 1 個(gè)的接口來(lái)供其他內(nèi)核模塊使用。
- void get_random_bytes(void *buf, int nbytes);
該接口會(huì)返回指定字節(jié)數(shù)的隨機(jī)數(shù)。random 設(shè)備了提供了 2 個(gè)字符設(shè)備供用戶態(tài)進(jìn)程使用——/dev/random 和/dev/urandom:
- /dev/random 適用于對(duì)隨機(jī)數(shù)質(zhì)量要求比較高的請(qǐng)求,在熵池中數(shù)據(jù)不足時(shí), 讀取 dev/random 設(shè)備時(shí)會(huì)返回小于熵池噪聲總數(shù)的隨機(jī)字節(jié)。/dev/random 可生成高隨機(jī)性的公鑰或一次性密碼本。若熵池空了,對(duì)/dev/random 的讀操作將會(huì)被阻塞,直到收集到了足夠的環(huán)境噪聲為止。這樣的設(shè)計(jì)使得/dev/random 是真正的隨機(jī)數(shù)發(fā)生器,提供了最大可能的隨機(jī)數(shù)據(jù)熵。
- /dev/urandom,非阻塞的隨機(jī)數(shù)發(fā)生器,它會(huì)重復(fù)使用熵池中的數(shù)據(jù)以產(chǎn)生偽隨機(jī)數(shù)據(jù)。這表示對(duì)/dev/urandom 的讀取操作不會(huì)產(chǎn)生阻塞,但其輸出的熵可能小于/dev/random 的。它可以作為生成較低強(qiáng)度密碼的偽隨機(jī)數(shù)生成器,對(duì)大多數(shù)應(yīng)用來(lái)說(shuō),隨機(jī)性是可以接受的。
/dev/random 也允許寫(xiě)入,任何用戶都可以向熵池中加入隨機(jī)數(shù)據(jù)。即使寫(xiě)入非隨機(jī)數(shù)據(jù)亦是無(wú)害的,因?yàn)橹挥泄芾韱T可以調(diào)用 ioctl 以增加熵池大小。Linux 內(nèi)核中當(dāng)前熵的值和大小可以通過(guò)訪問(wèn) /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 內(nèi)核中隨機(jī)發(fā)生器中存在幾個(gè)弱點(diǎn),在嵌入式系統(tǒng)(缺少鼠標(biāo)鍵盤(pán)),Live CD 系統(tǒng)(缺少磁盤(pán)),路由器,無(wú)盤(pán)工作站和一些服務(wù)器系統(tǒng)中,環(huán)境熵的來(lái)源較為受限,隨機(jī)數(shù)質(zhì)量會(huì)有所下降。對(duì)于有 NVRAM 的系統(tǒng),建議在關(guān)機(jī)時(shí)保存一部分隨機(jī)數(shù)發(fā)生器的狀態(tài),使得在下次開(kāi)機(jī)時(shí)可以恢復(fù)這些狀態(tài)。對(duì)于路由器而言,可以考慮把網(wǎng)絡(luò)數(shù)據(jù)可以作為熵的主要來(lái)源。
EGD
EGD(熵收集守護(hù)進(jìn)程,entropy gathering daemon)通常可以在不支持/dev/random 設(shè)備的 Unix 系統(tǒng)中提供類似的功能。這是一個(gè)運(yùn)行于用戶態(tài)的守護(hù)進(jìn)程,提供了高質(zhì)量的密碼用隨機(jī)數(shù)據(jù)。一些加密軟件,比如 OpenSSL,GNU Privacy Guard 和 Apache HTTP 服務(wù)器支持在/dev/random 不可用的時(shí)候使用 EGD。
EGD,或者類似的軟件 prngd,可以從多種來(lái)源收集偽隨機(jī)的熵,并對(duì)這些數(shù)據(jù)進(jìn)行處理以去除偏置,并改善密碼學(xué)質(zhì)量,然后允許其它程序通過(guò) Unix 域套接口(通常使用/dev/egd-pool),或 TCP 套接口訪問(wèn)其輸出。該程序通常使用建立子進(jìn)程的以查詢系統(tǒng)狀態(tài)的方式來(lái)收集熵。它查詢的狀態(tài)通常是易變和不可預(yù)測(cè)的,例如 CPU,I/O,網(wǎng)絡(luò)的使用率,也可能是一些日志文件和臨時(shí)目錄中的內(nèi)容。
EGD 通過(guò)一個(gè)簡(jiǎn)單的協(xié)議與那些需要隨機(jī)數(shù)的客戶端進(jìn)行通信,客戶端通過(guò)連接 EGD socket 發(fā)送命令(從前八位來(lái)識(shí)別命令):
- command 0: 查詢當(dāng)前可用熵
- command 1: 非阻塞地獲取隨機(jī)字節(jié)數(shù)
- command 2: 阻塞地獲取隨機(jī)字節(jié)數(shù)
- command 3: 更新熵
硬件隨機(jī)數(shù)產(chǎn)生器
當(dāng)前有很多硬件隨機(jī)數(shù)產(chǎn)生器(hwrng)用于產(chǎn)生可靠的隨機(jī)數(shù),但都是商用的,價(jià)格比較昂貴,最常使用的是 ComScire QNG,截止筆者寫(xiě)這篇文章,ComScire PQ4000KU 的官方價(jià)格接近 900 美元。
Intel’s Ivy Bridge family 有一個(gè)功能叫”Secure Key”, 處理器包含了一個(gè)內(nèi)部硬件 DRNG(Digital Random Number Generator)用于產(chǎn)生隨機(jī)數(shù),使用匯編指令 RDRAND 即可獲得高強(qiáng)度的隨機(jī)數(shù),Linux Kernel 會(huì)使用異或操作把 RDRAND 產(chǎn)生的隨機(jī)數(shù)混合進(jìn)熵池, 代碼實(shí)現(xiàn)在 drivers/char/random.c 的 extract_entropy()函數(shù)里。
- for (i = 0; i < LONGS(EXTRACT_SIZE); i++) {
- unsigned long v;
- if (!arch_get_random_long(&v))
- break;
- hash.l[i] ^= v;
- }
還有一些第三方的硬件隨機(jī)數(shù)生成器,通常是 USB 或者 PCI 設(shè)備,主要是在服務(wù)器上使用。Linux Kernel 的 hwrng(hardware random number generator)抽象層(/dev/hwrng 設(shè)備)可以選擇監(jiān)控 RNG 設(shè)備,并且在熵池?cái)?shù)據(jù)不足的時(shí)候要求設(shè)備提供隨機(jī)數(shù)據(jù)到 kernel 的熵池,rngd 守護(hù)進(jìn)程可以讀取 hwrng 的數(shù)據(jù)然后補(bǔ)給到 kernel 的熵池中。
在 KVM 虛擬機(jī)中如何應(yīng)用
虛擬機(jī)環(huán)境下和服務(wù)器情況類似,輸入設(shè)備操作很少,相對(duì)于 Host 而言,Disk I/O 也相對(duì)較少,因此依賴 Guest 自身 PRNG 產(chǎn)生的隨機(jī)數(shù)質(zhì)量不高,因此虛擬機(jī)通常從 Host(宿主機(jī))獲取部分隨機(jī)數(shù)據(jù)。對(duì)于 KVM 虛擬機(jī)來(lái)說(shuō),存在一個(gè)半虛擬化設(shè)備 virtio-rng 作為硬件隨機(jī)數(shù)產(chǎn)生器。Linux Kernel 從 2.6.26 開(kāi)始支持 virtio-rng, QEMU 在 1.3 版本加入了對(duì) virtio-rng 的支持。 virtio-rng 設(shè)備會(huì)讀取 Host 的隨機(jī)數(shù)源并且填充到 Guest(客戶機(jī))的熵池中。通常情況下使用/dev/random 作為輸入源。當(dāng)然,數(shù)據(jù)源可以更改,當(dāng) Host 系統(tǒng)中存在 hwrng 的情況下你可以使用/dev/hwrng 來(lái)作為 virtio-rng 的輸入源。 也可以把 hwrng 設(shè)備 pass-through(透?jìng)鳎┑娇蛻魴C(jī)中,但是并不實(shí)用,比如在虛擬機(jī) Live Migration(實(shí)時(shí)遷移)時(shí)會(huì)存在問(wèn)題。在 Guest 中添加 virtio-rng 設(shè)備具體操作,使用/dev/random 作為輸入源,兩種方法:
- 使用 libvirt 編輯虛擬機(jī)的 XML
- 在虛擬機(jī) 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
虛擬機(jī)啟動(dòng)后,在 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
會(huì)看到當(dāng)前 QEMU 進(jìn)程正在使用/dev/random 設(shè)備。
- 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 已經(jīng)識(shí)別到硬件隨機(jī)數(shù)產(chǎn)生器。
- $ dd if=/dev/hwrng of=/home/random-data bs=1
添加 bs 選項(xiàng)并且最好設(shè)置的值比較小,因?yàn)?Host 上隨機(jī)數(shù)資源可能會(huì)比較少,如果 bs 設(shè)置值太大,短時(shí)間內(nèi)可能無(wú)法獲得足夠的數(shù)據(jù)寫(xiě)入文件,同時(shí)在 Host 端多做一些鼠標(biāo)鍵盤(pán)或者磁盤(pán)的操作,會(huì)更快地產(chǎn)生隨機(jī)數(shù)。
- $ hexdump /home/random-data
- 00000000 9501 e702 ....
- 00000010 .... .... ....
使用 EGD 協(xié)議來(lái)作為輸入源:
- 使用 libvirt 編輯虛擬機(jī)的 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
總結(jié)
隨機(jī)數(shù)在計(jì)算機(jī)系統(tǒng)中有著非常重要的作用,本文闡述了隨機(jī)數(shù)的概念和重要性,介紹了在 Linux 中產(chǎn)生隨機(jī)數(shù)的方法,以及在 KVM 環(huán)境下虛擬機(jī)如何使用 virtio-rng 來(lái)獲取隨機(jī)數(shù)據(jù)。