1. 程式人生 > 其它 >Pseudo Random Number Generation Lab 偽隨機數 seed

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讀取的