Pseudo Random Number Generation Lab 偽隨機數 seed
Pseudo Random Number Generation Lab
生成隨機數是安全軟體中的一個相當常見的任務。 在許多情況下,使用者未提供加密金鑰,而是在軟體內生成。 他們的隨機性非常重要; 否則,攻擊者可以預測加密金鑰,從而擊敗加密的目的。 許多開發人員知道如何從他們的先前經驗中生成隨機數(例如,對於Monte Carlo模擬),因此它們使用類似的方法來為安全目的生成隨機數。 不幸的是,一系列隨機數對於蒙特卡羅模擬可能是良好的,但它們可能對加密金鑰不利。 開發人員需要知道如何生成安全的隨機數,或者他們會犯錯誤。 在一些知名的產品中,包括類似的錯誤,包括Netscape和Kerberos。
在這個實驗室中,我們將學習為什麼典型的隨機數生成方法不適用於生成祕密,例如加密金鑰。 他們將進一步學習標準的方法來生成適合安全目的的偽隨機數。 此實驗室涵蓋以下主題:
- 偽隨機數生成
- 隨機數生成中的錯誤
- 生成加密金鑰
-
/dev/random
和/dev/urandom
裝置檔案
程式碼倉庫:https://github.com/SKPrimin/HomeWork/tree/main/SEEDLabs/Crypto_Random_Number
任務1:以錯誤的方式生成加密金鑰
要生成良好的偽隨機數,我們需要從一個隨機的東西開始; 否則,結果將是非常可預測的。 以下程式使用當前時間作為偽隨機數發生器的種子。
#include <stdio.h> #include <stdlib.h> #include <time.h> #define KEYSIZE 16 void main() { int i; char key[KEYSIZE]; printf("%lld\n", (long long)time(NULL)); srand(time(NULL)); for (i = 0; i < KEYSIZE; i++) { key[i] = rand() % 256; printf("%.2x", (unsigned char)key[i]); } printf("\n"); }
庫函式時間()將時間返回以來秒以來的秒數,1970-01-01 00:00:00 +0000(UTC)。 執行上面的程式碼,並描述您的觀察。 然後,註釋退出行
srand (time(NULL));
,再次執行程式,並描述您的觀察。 在兩種情況下使用觀察來解釋程式碼中SRAND()和時間()函式的目的。
程式碼如下,首先註釋掉 srand (time(NULL));
gcc task1.c -o task1
./task1
在反覆執行多次之後發現,隨著時間改變,註釋掉 srand (time(NULL));
後編譯執行的結果只有秒數在變,但是生成的隨機數是不變的。 取消註釋後:
發現每次的隨機數和秒數都不一樣。 這是因為 srand(time(NULL))函式用於給隨機數生成函式 rand() 設定種子;time 是 C 語言獲取當前系統時間的函式,以秒作單位, 代表當前時間自 Unix 標準時間戳(1970 年 1 月 1 日 0 點 0 分 0 秒, GMT)經過了多少秒;每次執行的時間不一樣,故得到的隨機數也不 一樣。當註釋掉 srand (time(NULL))時,由於沒有設定種子,就會使 用預設是隨機數種子 0,因此每次執行程式,產生的隨機數都是相同的。
任務2:猜測金鑰
2018年4月17日,Alice Fi儲存了她的納稅申報表,她在磁碟上儲存了回報(PDF file)。為了保護file,她使用任務1中描述的程式生成的鍵加密了PDF檔案。她在筆記本中寫下了鑰匙,該膝上型電腦安全地儲存在保險箱中。幾個月後,鮑勃闖入了她的電腦並獲得了加密納稅申報表的副本。由於愛麗絲是一個大公司的執行長,這一檔案非常有價值。
Bob無法獲得加密金鑰,而是通過檢視Alice的計算機,他看到了關鍵代表程式,並懷疑程式可以生成Alice的加密金鑰。他還注意到了加密檔案的時間戳,即“2018-04-17 23:08:49”。他猜到了在建立檔案之前,可以在兩個小時的視窗中生成金鑰。
由於FI LE是PDF FI LE,其具有標題。標題的開始部分始終是版本號。圍繞建立檔案的時間,PDF-1.5是最常見的版本,即,標題以%PDF-1.5開始,這是8個位元組的資料。接下來的8位元組的資料也很容易預測。因此,Bob很容易獲得明文的第16個位元組。基於加密檔案的元資料,他知道使用AES-128-CBC加密檔案。由於AES是一個128位密碼,16位元組明文由一個明文組成,因此Bob知道一塊明文及其匹配的密文。此外,BOb還知道來自加密的檔案(IV永遠不會加密)的初始向量(IV)。這是Bob知道的:
Plaintext: 255044462d312e350a25d0d4c5d80a34 Ciphertext: d06bf9d0dab8e8ef880660d2af65aa82 IV: 09080706050403020100A2B2C2D2E2F2
您的工作是幫助Bob 查出 Alice的加密金鑰,因此您可以解密整個文件。 您應該編寫一個程式以嘗試所有可能的金鑰。 想要生成正確的金鑰幾乎是不可能的任務。但是,由於Alice使用time()來建立她的隨機數生成器,您應該能夠輕鬆地找到她的金鑰。 您可以使用date命令用來打印出特定時間之間的秒數,1970-01-01 00:00:00 +0000(UTC)。 請參閱以下示例。
$ date -d "2018-04-15 15:00:00" +%s 1523818800
將市區調至紐約
首先根據加密檔案時間戳計算兩小時內秒數範圍:
date -d "2018-04-17 21:08:49" +%s
date -d "2018-04-17 23:08:49" +%s
1524013729
1524020929
計算金鑰
編寫程式碼獲得所有可能的金鑰:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define KEYSIZE 16
#define START 1524013729
#define END 1524020929
void main()
{
int i;
time_t t;
char key[KEYSIZE];
FILE *f;
f = fopen("key.txt", "w");
for (t = START; t < END; t++) // 兩小時之內
{
/* 初始化隨機數發生器 */
srand((unsigned)t);
/* 輸出 0 到 256之間的16個隨機數 */
for (i = 0; i < KEYSIZE; i++)
{
key[i] = rand() % 256;
fprintf(f, "%.2x", (unsigned char)key[i]);
}
fprintf(f, "\n");
}
}
編譯執行
gcc task2.c -o task2
./task2
keys.txt檔案是根據開始結束時間建立的,在這段時間內可能生成的所有鍵都在keys.txt中
驗證金鑰
現在要做的就是對所有金鑰一個一個的嘗試,直到成功為止,於是編寫指令碼get_key.py:
# /* get_key.py */
from Crypto import Random
from Crypto.Cipher import AES
ciphertext = "d06bf9d0dab8e8ef880660d2af65aa82"
IV = "09080706050403020100A2B2C2D2E2F2".lower().decode("hex")
plaintext1 = "255044462d312e350a25d0d4c5d80a34".decode("hex")
with open('key.txt') as f:
keys = f.readlines()
for k in keys:
key = (k[:-1]).decode("hex")
cipher = AES.new(key, AES.MODE_CBC, IV)
encrypted = cipher.encrypt(plaintext1)
if ciphertext == encrypted.encode("hex")[0:32]:
print("Match found")
print("key: "+k[:-1])
print("Ciphertext: " + ciphertext)
print("Encrypted: " + encrypted.encode("hex"))
執行指令碼
python get_key.py
得到金鑰:95fa2030e73ed3f8da761b4eb805dfd7
時間作為seed值並不是一個真正的隨機數,用時間生成隨機數不可取。
任務3:測量核心的熵
在虛擬世界中,難以建立隨機性,即,單獨的軟體很難建立隨機數。 大多數系統訴諸物理世界獲得隨機性。 Linux從以下物理資源中獲得了隨機性:
void add_keyboard_randomness(unsigned char scancode); void add_mouse_randomness(__u32 mouse_data); void add_interrupt_randomness(int irq); void add_blkdev_randomness(int major);
前兩個非常容易理解:第一個使用按鍵之間的時間; 第二個使用滑鼠運動和中斷定時;第三個一個使用中斷定時收集隨機數。 當然,並非所有中斷都是良好的隨機性。 例如,定時器中斷不是一個不錯的選擇,因為它是可預測的。 但是,磁碟中斷是更好的度量。 最後一個測量塊裝置請求的檔案。
使用熵測量隨機性,這與資訊理論中的熵的含義不同。 在這裡,它只是意味著系統當前具有多少位隨機數。 您可以使用以下命令突出核心在當前時刻有多少熵。$ cat /proc/sys/kernel/random/entropy_avail
讓我們通過Watch執行上面的命令來監視熵的更改,該命令定期執行程式,顯示全屏中的輸出。 以下命令每0.1秒執行CAT程式。
$ watch -n .1 cat /proc/sys/kernel/random/entropy_avail
請執行上述命令。當它執行時,移動滑鼠,單擊滑鼠,鍵入一些內容,閱讀大檔案,訪問網站。哪些活動顯著增加了熵。請在報告中描述您的觀察結果。
執行以下命令:實時監測熵
watch -n .1 cat /proc/sys/kernel/random/entropy_avail
發現每次移動滑鼠、敲擊鍵盤等都會引起熵的變化
任務4:從/ dev /random獲取偽隨機數
Linux將從物理資源收集的隨機資料儲存到隨機池中,然後使用兩個裝置將隨機性轉換為偽隨機數。 這兩個裝置是/ dev /隨機和/ dev / urandom。 他們有不同的行為。 / dev /隨機裝置是阻塞裝置。
即,每次通過該裝置發出隨機數時,隨機池的熵將會減小。 當熵達到零時,/ dev /隨機將阻塞,直到它增益足夠的隨機性。
讓我們設計一個實驗以觀察/開/隨機裝置的行為。 我們將使用cat命令從/ dev /隨機讀取偽隨機數。 我們將輸出管向exdump管子進行漂亮的列印。
cat /dev/random | hexdump
watch -n .1 cat /proc/sys/kernel/random/entropy_avail
請執行上面的命令,同時使用Watch命令監視熵。
如果您沒有移動滑鼠或鍵入任何內容,會發生什麼。 然後,隨機移動滑鼠,看看你是否可以觀察任何差異。 請描述和解釋您的觀察。
問題:如果伺服器使用/開/隨機以使用客戶端生成隨機會話金鑰。 請描述如何在此類伺服器上啟動拒絕服務(DOS)攻擊。
不移動滑鼠時不會有任何反應,只有移動滑鼠到到一定程度時才會出現新的一行資料。
且entropy_avail值會從0~70不斷變化,與此前的資料相比大小減小
任務 5:從/dev/urandom獲取偽隨機數
Linux 提供了另一種通過 /dev/urandom 裝置訪問隨機池的方法,除了此裝置不會阻塞。/dev/random 和 /dev/urandom 都使用池中的隨機資料來生成偽隨機數。 當熵不夠時,/dev/random 將暫停,而 /dev/urandom 將繼續生成新數字。將池中的資料視為"seed",正如我們所知,我們可以使用seed來生成任意數量的偽隨機數。
讓我們看看 /dev/urandom 的行為。我們再次使用 cat 從此裝置獲取偽隨機數。請執行以下命令,並描述移動滑鼠是否對結果有任何影響。
$ cat /dev/random | hexdump
watch -n .1 cat /proc/sys/kernel/random/entropy_avail
cat /dev/urandom | hexdump
不管三七二十一,控制檯都會瘋狂列印資料
質量評測
讓我們測量隨機數的質量。我們可以使用名為 ent 的工具,該工具已安裝在虛擬機器中。根據其手冊,"ent將各種測試應用於儲存在檔案中的位元組序列,並報告這些測試的結果。該程式可用於評估偽隨機數生成器,用於加密和統計取樣應用程式,壓縮演算法以及感興趣的檔案資訊密度的其他應用程式"。讓我們首先從 /dev/urandom 生成 1 MB 的偽隨機數,並將它們儲存在一個檔案中。然後,我們對檔案執行 ent。請描述您的結果,並分析隨機數的質量是否良好。
$ head -c 1M /dev/urandom > output.bin $ ent output.bin
在本地發現ent指令無法在seed12上使用。這裡我們又請來了老朋友kali
評測結果顯示
Entropy = 7.999828 bits per byte.
熵=每一個位元組7.999828位。————表示該檔案的資訊非常密集——基本上是隨機的。
Optimum compression would reduce the size
of this 1048576 byte file by 0 percent.
最佳壓縮將將1048576位元組檔案的大小縮小為0%。————
Chi square distribution for 1048576 samples is 249.82, and randomly
would exceed this value 57.98 percent of the times.
1048576樣品的卡方分佈為249.82,隨機將超過此值的時間為57.98%。————卡方檢驗是最常用的資料隨機性檢驗
Arithmetic mean value of data bytes is 127.5494 (127.5 = random).
資料位元組的算術平均值為127.5494(127.5 =隨機)。————值高於127.5表示它是隨機的
Monte Carlo value for Pi is 3.140064774 (error 0.05 percent).
PI的Monte Carlo值為3.140064774(錯誤0.05%)。————如果是隨機的,Pi的蒙特卡羅值將接近於Pi的值,它只有0.02%,因此我們可以認為它是隨機的
Serial correlation coefficient is -0.001884 (totally uncorrelated = 0.0).
序列相關係數為-0.001884(完全不相關= 0.0)。————序列相關係數——這個量度量檔案中每個位元組對前一個位元組的依賴程度,對於隨機數,這個值將接近0
真正的隨機數
從理論上講,/dev/random裝置更安全,但在實踐中,沒有太大區別,因為/dev/urandom使用的"seed"是隨機且不可預測的(每當新的隨機資料可用時,/dev/urandom都會重新播種)。 /dev/random 的阻塞行為的一個大問題是,阻塞可能導致拒絕服務攻擊。因此,建議我們使用 /dev/urandom 來獲取隨機數。要在我們的程式中執行此操作,我們只需要直接從此裝置檔案中讀取即可。以下程式碼片段演示瞭如何操作。
#define LEN 16 // 128 bits unsigned char *key = (unsigned char *) malloc(sizeof(unsigned char)*LEN); FILE * random = fopen("/dev/urandom", "r"); fread(key, sizeof(unsigned char) *LEN, 1, random); fclose(random);
請修改上述程式碼片段以生成 256 位加密金鑰。
/* task5.c */
#include <stdio.h>
#include <stdlib.h>
#define LEN 32 // 256 bits
void main()
{
int i;
unsigned char *key = (unsigned char *)malloc(sizeof(unsigned char) * LEN);
FILE *random = fopen("/dev/urandom", "r");
for (i = 0; i < LEN; i++)
{
fread(key, sizeof(unsigned char) * LEN, 1, random);
printf("%.2x", *key);
}
printf("\n");
fclose(random);
}
編譯執行
gcc task5.c -o task5
./task5
這是真正的隨機數,因為它是從/dev/urandom
讀取的