資源下載地址:https://download.csdn.net/download/sheziqiong/85628255
資源下載地址:https://download.csdn.net/download/sheziqiong/85628255文章來源地址http://www.zghlxwxcb.cn/news/detail-469685.html
第一階段:Diffie-Hellman 協(xié)議的實現(xiàn)
- 客戶端與服務(wù)器之間通過 TCP Socket 通信;
- 客戶端與服務(wù)器之間通過 Diffie-Hellman 協(xié)議協(xié)商出對稱密鑰;
- 客戶端使用協(xié)商出的對稱密鑰對傳輸內(nèi)容做加密,并發(fā)送給服務(wù)端;
- 服務(wù)端接受客戶端發(fā)送過來的內(nèi)容,進行解密;
- 對稱加密算法采用 AES256-GCM;
第二階段:Diffie-Hellman 中間人攻擊方法研究與實現(xiàn)
- 研究 Diffie-Hellman 協(xié)議,研究中間人攻擊方法并完成相關(guān)代碼。當通信雙方進行通信時,中間人攻擊程序可以解密出傳輸內(nèi)容;
第三階段:Diffie-Hellman 協(xié)議改進
概要設(shè)計
抽象數(shù)據(jù)類型定義
Socket 結(jié)構(gòu)
數(shù)據(jù)對象 :客戶端與服務(wù)器進行通信所需要的所有函數(shù)等元素。
數(shù)據(jù)關(guān)系 :根據(jù) IP 地址和端口號,服務(wù)器綁定端口號并監(jiān)聽,等待客戶端連接;客戶端嘗試連接指定 IP 的指定端口號,成功連接則可以相互通信。
基本操作 :
- socket (): 創(chuàng)建一個 socket 句柄,用于客戶端與服務(wù)器通信。
- bind (): 綁定指定的端口號,成功則繼續(xù),失敗報錯并結(jié)束。
- listen (): 監(jiān)聽指定端口號,等待客戶端連接。
- accept (): 當有客戶端嘗試連接時,接收連接,并返回句柄。
- connect (): 客戶端根據(jù)指定的 IP 地址和端口號,連接服務(wù)器,成功則返回連接句柄,失敗則報錯,并終止程序。
Diffie Hellman 結(jié)構(gòu)
數(shù)據(jù)對象 :進行 Diffie Hellman 密鑰交換協(xié)議所需要的所有數(shù)據(jù)對象,包括結(jié)構(gòu)體、函數(shù)等。
數(shù)據(jù)關(guān)系 :通過 Socket 進行客戶端與服務(wù)器的通信,協(xié)商出用于 AES256-GCM 加密的密鑰。
基本操作 :
- struct DH_key: 用于 Diffie Hellman 密鑰交換協(xié)議的結(jié)構(gòu)體,包含了素數(shù)、原根、公鑰、私鑰、協(xié)商出來的密鑰等數(shù)據(jù)。
- get_random_int (): 輸入一個 mpz_t 類型的數(shù)據(jù)指針,生成一個隨機整數(shù),并輸出到輸入的指針中,這個數(shù)會非常大。
- check_prime (): 輸入一個 mpz_t 類型的數(shù)據(jù)指針,檢測對應(yīng)的數(shù)據(jù)是否為素數(shù),一定是素數(shù)則返回 2,可能是素數(shù)則返回 1,不是素數(shù)則返回 0.
- generate_p (): 輸入一個 mpz_t 類型的數(shù)據(jù)指針,輸出一個大素數(shù)。
- generate_pri_key (): 輸入一個 mp_t 類型的數(shù)據(jù)指針,輸出一個大素數(shù),用作客戶端或者服務(wù)器進行 Diffie Hellman 協(xié)議的私鑰。
- psk (): 預(yù)共享密鑰函數(shù),主要用于防止中間人攻擊。
AES 加密結(jié)構(gòu)
數(shù)據(jù)對象 :用于 AES256-GCM 加密的所有數(shù)據(jù)類型、函數(shù)等。
數(shù)據(jù)關(guān)系 :使用 Diffie Hellman 結(jié)構(gòu)中的 DH_key 數(shù)組中保存的密鑰作為 AES 加密的密鑰,對需要發(fā)送到明文進行加密,然后通過 Socket 結(jié)構(gòu)發(fā)送;對通過 Socket 接收到的密文進行解密,輸出明文。
基本操作 :
- sbox: 256 位數(shù)組,AES 加密需要的 S-Box。
- contrary_sbox: 256 位數(shù)組,AES 解密需要的逆向 S-Box。
- ScheduleKey (): 編排密鑰(密鑰擴展),輸入用于 AES256-GCM 加密的密鑰,輸出每一輪加密的輪密鑰。
- SubBytes (): 查 S-Box,對輸入的明文分組進行字節(jié)替換。
- Contrary_SubBytes (): 查逆向 S-Box,對輸入的密文分組進行字節(jié)替換。
- ShiftRows (): 行移位,用于加密。
- Contrary_ShiftRows (): 逆向行移位,用于解密。
- Mix_Column (): 列混合,用于加密。
- Contrary_Mix_Column (): 逆向列混合,用于解密。
- AesEncrypt (): AES 加密函數(shù),輸入明文,輸出密文。
- Contrary_AesEncrypt (): AES 解密函數(shù),輸入密文,輸出明文。
中間人數(shù)據(jù)包捕獲結(jié)構(gòu)
數(shù)據(jù)對象 :用于中間人程序,捕獲經(jīng)過網(wǎng)卡的數(shù)據(jù)包,對滿足特定要求的數(shù)據(jù)包進行數(shù)據(jù)提取和保存。
數(shù)據(jù)關(guān)系 :監(jiān)聽網(wǎng)卡,調(diào)用 Diffie Hellman 結(jié)構(gòu)中的函數(shù)用于和客戶端、服務(wù)器分別建立信任關(guān)系;調(diào)用 AES 結(jié)構(gòu)對提取到的密文進行解密和加密。
基本操作 :
- pcap_lookupdev (): 包含在 libpcap 中,輸入要存放錯誤信息的數(shù)組,輸出本機第一個網(wǎng)絡(luò)設(shè)備名稱。
- pcap_open_live (): 包含在 libpcap 中,輸入網(wǎng)絡(luò)設(shè)備名字指針,打開對應(yīng)的網(wǎng)絡(luò)設(shè)備,輸出網(wǎng)絡(luò)設(shè)備的句柄。
- pcap_compile (): 包含在 libpcap 中,編譯 BPF 過濾規(guī)則。
- pcap_setfilter (): 包含在 libpcap 中,應(yīng)用 BPF 過濾規(guī)則。
- pcap_loop (): 包含在 libpcap 中,輸入設(shè)備句柄,開始捕獲數(shù)據(jù)包,每捕獲到一個數(shù)據(jù)報,就調(diào)用一次回調(diào)函數(shù)處理數(shù)據(jù)包。
- pcap_sendpacket (): 包含在 libpcap 中,輸入設(shè)備句柄和數(shù)據(jù)包數(shù)據(jù)與長度,將數(shù)據(jù)包發(fā)往目的地。
- process_pkt (): 是 pcap_loop () 的回調(diào)函數(shù),輸入網(wǎng)絡(luò)地址信息和設(shè)備句柄,對抓到的符合 BPF 過濾規(guī)則的包進行處理。
- calc_checksum (): 輸入數(shù)據(jù)包數(shù)據(jù)和長度,輸出 TCP 校驗和。
- set_psd_header (): 設(shè)置 TCP 數(shù)據(jù)包的頭部,輸入頭部指針和需要寫入頭部的數(shù)據(jù)指針。
主程序流程
客戶端主程序流程
- 進行 Socket 連接,成功連接上服務(wù)器后進行下一步,否則退出;
- 生成大素數(shù) p 和原根 g,并發(fā)送給服務(wù)器
- 生成客戶端私鑰,并結(jié)合 p 和 g 計算出客戶端公鑰;
- 接收服務(wù)器的公鑰并保存,發(fā)送客戶端公鑰給服務(wù)器;
- 根據(jù)服務(wù)器公鑰、素數(shù) p 和客戶端私鑰計算出最終用于 AES 加解密的密鑰;
- 發(fā)送數(shù)據(jù)給客戶端,數(shù)據(jù)內(nèi)容為用戶輸入明文加密后的密文;
- 接收服務(wù)器發(fā)送的密文,并解密輸出;
- 重復(fù) 6 和 7,直到連接中斷。
服務(wù)器主程序流程
- 創(chuàng)建 Socket 連接,當有客戶端連接時,接收素數(shù) p 和原根 g;
- 生成服務(wù)器私鑰,并計算出公鑰,發(fā)送給客戶端;
- 接收客戶端的公鑰,并計算出用于 AES 加解密的密鑰;
- 等待客戶端發(fā)送的密文,并進行解密和輸出;
- 對服務(wù)器輸入的明文加密,將密文發(fā)送給客戶端;
- 重復(fù) 4 和 5,直到連接中斷。
中間人主程序流程
- 使用 arpspoof 進行 ARP 欺騙;
- 打開網(wǎng)絡(luò)設(shè)備,設(shè)置 BPF 過濾規(guī)則,開始捕獲數(shù)據(jù)包;
- 每捕獲到一個符合要求的數(shù)據(jù)包,查看是從客戶端發(fā)送的還是從服務(wù)器發(fā)送的,前者執(zhí)行 4) 和 5),后者執(zhí)行 6) 和 7);
- 若是客戶端公鑰,則自己保存后替換為自己的公鑰,重新設(shè)置校驗和和頭部后發(fā)送給服務(wù)器,返回 3);
- 若是客戶端發(fā)送的加密信息,則使用對客戶端的密鑰解密信息并寫入文件,然后將明文使用對服務(wù)器的密鑰加密,重新設(shè)置校驗和和頭部后再發(fā)送給服務(wù)器,返回 3);
- 若是服務(wù)器公鑰,則自己保存后生成自己的私鑰和公鑰,重新設(shè)置校驗和和頭部后,將公鑰發(fā)送給客戶端,返回 3);
- 若是服務(wù)器發(fā)送的加密信息,則使用對服務(wù)器的密鑰解密信息并寫入文件,然后將明文使用對客戶端的密鑰加密后,重新設(shè)置校驗和和頭部后,再發(fā)送給客戶端,返回 3)。
各程序調(diào)用關(guān)系
不管是客戶端還是服務(wù)器,都需要先使用 Socket 結(jié)構(gòu)的相關(guān)函數(shù)來建立連接。建立連接之后,調(diào)用 Diffie Hellman 協(xié)議相關(guān)的函數(shù)協(xié)商出密鑰,并將中間生成的素數(shù)、原根、私鑰、公鑰等數(shù)據(jù)保存在一個結(jié)構(gòu)體當中。接下來,調(diào)用 AES256-GCM 加密模塊,對要發(fā)送的數(shù)據(jù)加密、接收到的數(shù)據(jù)解密,直到程序結(jié)束。
對于中間人程序,不需要使用 Socket,取而代之的是數(shù)據(jù)包捕獲模塊。抓到服務(wù)器和客戶端交換公鑰的數(shù)據(jù)包后,會調(diào)用 Diffie Hellman 模塊中的函數(shù)生成自己的私鑰和公鑰;抓到加密的信息后,會調(diào)用 AES256-GCM 加解密模塊來對信息進行解密和加密。完成后再次回到數(shù)據(jù)包捕獲狀態(tài)。
技術(shù)開發(fā)思路
Socket 通信的實現(xiàn)
該內(nèi)容較簡單,如圖所示。
Diffie Hellman 協(xié)議的實現(xiàn)
Diffie Hellman 協(xié)議的原理如下圖所示。協(xié)議的實現(xiàn)需要大數(shù)處理,為了方便,可以使用 libgmp 來進行操作,它可以生成大數(shù)、判斷一個大數(shù)是否為素數(shù)、進行大數(shù)之間的基本運算等??梢允褂迷搸焐勺畛醯拇笏財?shù) p,并計算原根 g,然后隨機生成兩端的私鑰 a 和 b,并進行模運算計算出公鑰 A 和 B,交換公鑰后,就可以利用模運算計算出最終的密鑰 K 了,兩端的 K 應(yīng)該是一樣的。
AES256-GCM 加解密的實現(xiàn)
(實際上我的搭檔交給我的只是 AES,是不是 256 都尚且存疑,但是我懶得去修改了,有興趣的可以去參考 openssl 庫。)GCM 是認證加密模式中的一種,它結(jié)合了 CTR 模式和 GMAC 模式的特點,能同時確保數(shù)據(jù)的保密性、完整性及真實性,另外,它還可以提供附加消息的完整性校驗。具體原理如下圖所示。
需要的密鑰 K 由 Diffie Hellman 協(xié)議得到,然后根據(jù)矩陣運算寫出行移位、列混合的函數(shù),并引入官方的 S-Box 和逆向 S-Box 進行字節(jié)替換。不過在此之前,首先要進行密鑰擴展,生成 AES 加密的輪密鑰。iv 為初始向量,在這里主要用來進行完整性校驗。
中間人捕獲數(shù)據(jù)包的實現(xiàn)
這里主要用到了 libpcap,該庫可以捕獲數(shù)據(jù)包,并對捕獲到的每一個數(shù)據(jù)包進行處理,提取出需要的內(nèi)容,放入想要放進去的數(shù)據(jù),最后還要計算一下校驗和,否則接收方收到后沒有通過校驗和檢查,會將其視作損壞包而丟棄。
為了讓數(shù)據(jù)包能夠發(fā)送到中間人機器上來,還需要進行 ARP 欺騙,告訴服務(wù)器自己是客戶端,告訴客戶端自己是服務(wù)器。這里不需要寫程序,可以直接使用工具 arpspoof 來實現(xiàn)雙向欺騙。
防止中間人攻擊的實現(xiàn)
根據(jù)要求使用預(yù)共享密鑰的方法。首先要將一個密鑰封裝到代碼內(nèi)部,客戶端和服務(wù)器都有且相同。在 Diffie Hellman 協(xié)商密鑰后、AES 加密前,服務(wù)器先隨機生成一段字符串,以公鑰的形式發(fā)送給客戶端,客戶端收到后使用預(yù)共享密鑰進行加密,將密文返回給服務(wù)器,服務(wù)器使用預(yù)共享密鑰進行解密,如果解密后的字符串與發(fā)送的字符串相同,則允許互相通信,否則拒絕。
因為是以公鑰形式發(fā)送的數(shù)據(jù)包,中間人在截獲后會將其視作服務(wù)器發(fā)送的公鑰而處理,重新生成私鑰和公鑰,并將公鑰寫入數(shù)據(jù)包發(fā)送給客戶端,客戶端收到的自然也就不是服務(wù)器發(fā)送的字符串了。
git 版本管理
根據(jù)課設(shè)要求,需要使用 git 進行版本管理,這一步要從項目一開始就進行。由于是在 Linux 機器上進行開發(fā),安裝 git 很簡單,之后只需要掌握 git 相關(guān)的命令,如 git add,git commit,git status,git log 等,就可以輕松使用了。如果使用了 VS Code 或者其他 IDE,可以很方便的利用它們提供的圖形界面進行版本控制。
使用 makefile 將多個源文件結(jié)合
由于是兩個人一起進行設(shè)計,使用了多個源文件,因此需要使用 makefile,在寫好代碼后將源代碼匯聚到一起,根據(jù)其中的邏輯編寫 makefile,在修改源代碼后只需要輸入 make 就可以編譯了。
詳細設(shè)計
客戶端程序流程圖
客戶端程序的流程圖如圖所示,具體細節(jié)后面解釋。
服務(wù)器程序流程圖
服務(wù)器端程序的流程圖如下圖所示,具體細節(jié)后面解釋。
中間人程序流程圖
中間人程序的流程圖如下圖所示,具體細節(jié)后面解釋。
這里用到了 libgmp。GNU MP (gmp) 是一個用 C 語言編寫的可移植庫,用于對整數(shù)、有理數(shù)和浮點數(shù)進行任意精度的算術(shù)運算,它旨在為所有需要比 C 語言直接支持運算的精度更高的應(yīng)用程序提供盡可能快的算法。根據(jù)操作數(shù)的大小來選擇使用的算法,并將開銷控制在最低,這就是 gmp 被設(shè)計的目的。
對于 Diffie Hellman 協(xié)議,需要用到一個結(jié)構(gòu)體來保存中間過程的數(shù)據(jù)。p
表示生成的大素數(shù),g
為 p
的原根,這里方便起見,一般取 2 或者 5,pri_key
為隨機生成的一個私鑰,pub_key
為計算得到的公鑰,k
為最終協(xié)商得到的密鑰。mpz_t
為 libgmp 中定義的數(shù)據(jù)類型,為大整型數(shù)據(jù)。在中間人程序中,公鑰變量和密鑰變量需要有兩個,分別為對服務(wù)器的和對客戶端的。
typedef struct{
mpz_t p;
mpz_t g;
mpz_t pri_key;
mpz_t pub_key;
mpz_t k;}DH_key;
}
要進行 Diffie Hellman 協(xié)議,不可避免要生成大隨機數(shù),并且還要生成一個大素數(shù),這里可以使用 libgmp 提供的一些函數(shù)和數(shù)據(jù)類型。要生成大隨機數(shù),可以結(jié)合使用兩個隨機數(shù)生成函數(shù):mpz_rrandomb()
和 mpz_urandomb()
,將這兩個數(shù)相乘就是最終的隨機數(shù),至于為什么這樣做在 3.1 節(jié)中有詳細解釋。這兩個函數(shù)都需要一個 gmp_randstate
類型的數(shù)據(jù)變量作為傳入?yún)?shù),該變量可以由函數(shù) gmp_randinit_default()
來初始化,中間使用了 libgmp 默認的初始化算法。然后,獲取當前時間作為種子傳入 gmp_randstate
變量當中,這樣就可以將它作為狀態(tài)傳入隨機數(shù)生成函數(shù)當中了。具體代碼如下:
void get_random_int(mpz_t z, mp_bitcnt_t n){
mpz_t temp; // 臨時mpz_t變量,用于生成隨機數(shù),用完即廢棄
gmp_randstate_t grt; // gmp狀態(tài),用于生成隨機數(shù)
gmp_randinit_default(grt); // 使用默認算法初始化狀態(tài)
gmp_randseed_ui(grt, (mp_bitcnt_t)clock()); //將時間作為種子傳入狀態(tài)grt中
mpz_rrandomb(z, grt, n); // 生成2^(n-1)到2^n-1之間一個隨機數(shù)
mpz_init(temp);
gmp_randinit_default(grt);
gmp_randseed_ui(grt, (mp_bitcnt_t)clock());
do
{
mpz_urandomb(temp, grt, n); // 生成一0~2^(n-1)之間的隨機數(shù),可能為0
}while (mpz_cmp_ui(temp, (unsigned long int)0) \<= 0);
mpz_mul(z, z, temp); // 兩個隨機數(shù)相乘
mpz_clear(temp);
}
上面生成的知識一個較大的隨機數(shù),可是 Diffie Hellman 協(xié)議需要一個大素數(shù)。大素數(shù)的生成也可以使用 libgmp 提供的一些函數(shù)。首先使用上面的函數(shù)生成一個大隨機數(shù),然后對其進行素性檢測,這里用到了 mpz_probab_prime_p()
函數(shù),它可以檢測一個數(shù)是否為素數(shù),函數(shù)會執(zhí)行一個 Baillie-PSW 概率素性檢測,然后執(zhí)行指定次數(shù)的 Miller-Robin 素性檢測,根據(jù)需要可以設(shè)置 15 到 50 次。如果一定是素數(shù),函數(shù)返回 2;可能是素數(shù),返回 1;肯定不是素數(shù),返回 0。這里只需要返回非零值即可。mpz_nextprime()
函數(shù)可以得到比傳入?yún)?shù)大、且離得最近的一個素數(shù),如果隨機生成的數(shù)不是素數(shù),就使用該函數(shù)得到一個素數(shù)。
int check_prime(mpz_t prime)
{
return mpz_probab_prime_p(prime, 30);
}
void generate_p(mpz_t prime){
get_random_int(prime, (mp_bitcnt_t)128);
while (!check_prime(prime)){
mpz_nextprime(prime, prime);
}
}
要生成私鑰,和隨機生成一個大數(shù)沒什么不同,而且沒有了素數(shù)的限制,只需要調(diào)用 get_random_int()
函數(shù)即可,不再贅述。
不管是生成公鑰還是計算得到最終的密鑰,都需要進行指數(shù)運算和模運算,這里 libgmp 也提供了相關(guān)的運算函數(shù):mpz_powm(rlt, a, b, c)
。它可以計算 abmod c,并將結(jié)果傳遞給 rlt。這樣一來公鑰和密鑰的計算也迎刃而解。
以客戶端為例(服務(wù)器類似),最終進行 Diffie Hellman 協(xié)議的相關(guān)代碼如下:
void exchange_dh_key(int sockfd, mpz_t s){
DH_key client_dh_key; // 客戶端生成的密鑰
mpz_t server_pub_key; // 服務(wù)器公鑰
// 初始化mpz_t類型的變量
mpz_inits(client_dh_key.p, client_dh_key.g, client_dh_key.pri_key, client_dh_key.pub_key, client_dh_key.s, server_pub_key, NULL);
generate_p(client_dh_key.p);
mpz_set_ui(client_dh_key.g, (unsigned long int)5); // base g = 5
/* 將p發(fā)送給服務(wù)器,代碼略 */
generate_pri_key(client_dh_key.pri_key); // 計算客戶端的公鑰A
mpz_powm(client_dh_key.pub_key, client_dh_key.g,client_dh_key.pri_key, client_dh_key.p);
/* 接收服務(wù)器公鑰,發(fā)送客戶端公鑰,代碼略 */
// 客戶端計算DH協(xié)議得到的密鑰s
mpz_powm(client_dh_key.s, server_pub_key, client_dh_key.pri_key, client_dh_key.p); // 清除mpz_t變量
mpz_clears(client_dh_key.p,client_dh_key.g,client_dh_key.pri_key, client_dh_key.pub_key,client_dh_key.s,server_pub_key,NULL);
}
AES 加解密詳細設(shè)計
AES 加解密模塊基本全部由我的搭檔完成,在此只做簡單敘述。首先,不管是客戶端、服務(wù)器還是中間人,都需要有 S-Box 和逆向的 S-Box,這個直接寫入代碼即可。因為內(nèi)容過多且并不重要,不再展示。
AES 加解密是定義在有限域上的,因此需要實現(xiàn)有限域乘法。下面給出了 2 乘法和 3 乘法,其他的可類似推出。
static unsigned char x2time(unsigned char x){
if(x & 0x80)
return(((x << 1)^0x1B) & 0xFF);
return x << 1;
}
static unsigned char x3time(unsigned char x){
return (x2time(x) ^ x);
}
AES 加解密都需要擴展密鑰,生成輪密鑰,具體實現(xiàn)如下:
void ScheduleKey(unsigned char *key, unsigned char *expansion_key, int key_col, int en_round){
unsigned char temp[4], t; int x, i;
for (i = 0; i < (4 * key_col); i++)
expansion_key[i] = key[i];
i = key_col;
while (i < (4 * (en_round + 1))){
for (x = 0; x< 4; x++)
temp[x] = expansion_key[(4 * (i - 1)) + x];
if (i % key_col == 0){
t = temp[0];
temp[0] = temp[1];
temp[1] = temp[2];
temp[2] = temp[3];
temp[3] = t;
for (x = 0; x < 4; x++)
temp[x] = sbox[temp[x]];
temp[0] ^= Rcon[(i / key_col) - 1];
}
else if (key_col>6 && (i%key_col) == 4){
for (x = 0; x < 4; x++)
temp[x] = sbox[temp[x]];
for (x=0; x<4; x++)
expansion_key[(4*i)+x]=expansion_key[(4*(i - key_col)) + x]^temp[x];
++i;
}
}
}
生成輪密鑰后,開始進行字節(jié)替換、行移位、列混合等操作。字節(jié)替換很簡單,就是通過查找 S-Box,將每個字節(jié)替換成相應(yīng)的字節(jié),逆向字節(jié)替換類似,只是將 S-Box 換成了 Contrary S-Box。這里只展示正向的。
static void SubBytes(unsigned char *col){
int x;
for (x = 0; x < 16; x++)
col[x] = sbox[col[x]];
}
行移位就是將矩陣中的每個橫列進行循環(huán)式移位,對于長度 256 比特的區(qū)塊,第一行維持不變,第二行、第三行、第四行的偏移量分別是 1 字節(jié)、3 字節(jié)、4 位。這個過程只需聲明中間變量,然后進行交換即可。列混合是為了混合矩陣中各個直行而進行的操作。這個步驟使用線性轉(zhuǎn)換來混合每內(nèi)聯(lián)的四個字節(jié),只有最后一輪才省略掉列混合,而以輪密鑰加替代。
static void MixColumns(unsigned char \*col){
unsigned char tmp\[4\];
int i;
for (i = 0; i \< 4; i++, col += 4){
tmp[0] = x2time(col[0]) ^ x3time(col[1]) ^ col[2] ^ col[3];
tmp[1] = col[0] ^ x2time(col[1]) ^ x3time(col[2]) ^ col[3];
tmp[2] = col[0] ^ col[1] ^ x2time(col[2]) ^ x3time(col[3]);
tmp[3] = x3time(col[0]) ^ col[1] ^ col[2] ^ x2time(col[3]);
col[0] = tmp[0];
col[1] = tmp[1];
col[2] = tmp[2];
col[3] = tmp[3];
}
}
static void AddRoundKey(unsigned char *col, unsigned char *expansionkey, int round){
int x;
for (x = 0; x < 16; x++)
col[x] ^= expansionkey[(round << 4) + x];
}
最終的加密總函數(shù)就是將上面敘述的各個步驟疊加到一起,主要代碼如下:
void AesEncrypt(unsigned char *text, unsigned char *expansionkey, int en_round){
int round;
AddRoundKey(text, expansionkey, 0);
for (round = 1; round <= (en_round - 1); round++){
SubBytes(text);
ShiftRows(text);
MixColumns(text);
AddRoundKey(text, expansionkey, round);
}
SubBytes(text);
ShiftRows(text);
AddRoundKey(text, expansionkey, en_round);
}
解密函數(shù)也類似,如下所示:
void Contrary_AesEncrypt(unsigned char *text, unsigned char *expansionkey, int en_round){
int x;
AddRoundKey(text, expansionkey, en_round);
Contrary_ShiftRows(text);
Contrary_SubBytes(text);
for (x = (en_round - 1); x >= 1; x--){
AddRoundKey(text, expansionkey, x);
Contrary_MixColumns(text);
Contrary_ShiftRows(text);
Contrary_SubBytes(text);
}
AddRoundKey(text, expansionkey, 0);
}
中間人攻擊詳細設(shè)計
中間人攻擊就是截獲局域網(wǎng)內(nèi)兩臺主機通信的數(shù)據(jù)包,并進行提取和修改,讓通信雙方都以為在和對方通信,實際上卻在和中間人通信。具體原理如圖所示。
要進行中間人攻擊,最首先、最關(guān)鍵的一步就是捕獲數(shù)據(jù)包,這里可以使用 libpcap 進行捕獲。使用 libpcap 的主要流程是:
-
pcap_lookupdev()
: 獲取可用的網(wǎng)絡(luò)設(shè)備名指針; -
pcap_open_live()
: 打開指定的網(wǎng)絡(luò)設(shè)備,并返回用于捕獲網(wǎng)絡(luò)數(shù)據(jù)包的描述字; -
pcap_compile()
: 將用戶制定的 BPF 過濾規(guī)則編譯到過濾程序當中; -
pcap_setfilter()
: 應(yīng)用 BPF 過濾規(guī)則,讓過濾規(guī)則生效; -
pcap_loop()
: 循環(huán)抓包,遇到錯誤或者執(zhí)行結(jié)束時退出。 - 自己編寫
callback
函數(shù)傳入pcap_loop()
函數(shù),對捕獲到的每一個數(shù)據(jù)包進行處理。
這里前面五步都是模板,重點是回調(diào)函數(shù),這點在 2.3 節(jié)中間人程序流程圖中已經(jīng)大致展示了,下面具體敘述一下。
在啟動中間人程序之前,需要先進行 ARP 欺騙,不斷地告訴服務(wù)器:客戶端的 MAC 地址為中間人的 MAC;告訴客戶端:服務(wù)器的 MAC 地址為中間人的 MAC。這樣它們發(fā)送的數(shù)據(jù)包都會送到中間人這里。當中間人程序抓到一個數(shù)據(jù)包后,首先需要判斷它是來自客戶端還是來自服務(wù)器的,然后判斷是公鑰還是加密信息或大素數(shù)。如果來自服務(wù)器,那么需要修改數(shù)據(jù)包以太幀頭中的目的 MAC 地址為客戶端(因為有 ARP 欺騙,原來的 MAC 地址為中間人的);如果來自客戶端,那么需要修改以太幀頭中的目的 MAC 地址為服務(wù)器(原來的 MAC 地址為中間人)。如果是服務(wù)器發(fā)送的公鑰,那么需要使用 Diffie Hellman 協(xié)議模塊定義的一些函數(shù)生成自己的私鑰,并計算得到公鑰,然后用自己的公鑰把服務(wù)器的替換掉,發(fā)送給客戶端;如果是客戶端發(fā)送的公鑰,直接將自己的公鑰寫入替換掉客戶端的公鑰,然后發(fā)送給服務(wù)器。收到上面兩個數(shù)據(jù)包后,中間人對服務(wù)器的密鑰、中間人對客戶端的密鑰都已經(jīng)可以計算出來了。如果是服務(wù)器發(fā)送的加密信息,就使用對服務(wù)器的密鑰解密,保存在文件當中,然后使用對客戶端的密鑰加密,發(fā)送給客戶端;如果是客戶端發(fā)送的密文,和上面類似。
具體實現(xiàn)其實并不復(fù)雜,因為邏輯也很簡單,但是一些細節(jié)很容易出錯,需要細心。這里只展示抓到客戶端發(fā)出的數(shù)據(jù)包的代碼,服務(wù)器類似,可在附錄中查看。
if (strncmp(src_ip, ip_t->client_ip, strlen(src_ip)) == 0){
if (strncmp(packet + header_len, "pri", 3) == 0)
mpz_set_str(middle_dh.p, packet + header_len + 3, 16);
else if (strncmp(packet + header_len, "pub", 3) == 0){
mpz_t client_pub_key;
mpz_init_set_str(client_pub_key, packet + header_len + 3, 16); // 計算對客戶端的密鑰
mpz_powm(middle_dh.key2client, client_pub_key, middle_dh.pri_key, middle_dh.p);
mpz_get_str(key2client, 16, middle_dh.key2client);
ScheduleKey(key2client, expansion_key2client, AES256_KEY_LENGTH, AES256_ROUND);
mpz_get_str(packet + header_len + 3, 16, middle_dh.pub_key);
/* 計算校驗和,代碼略 */
}
else if (strncmp(packet + header_len, "msg", 3) == 0){
char *buf = packet + header_len + 3;
bzero(plain_text, 33);
strncpy(plain_text, buf, 32);
Contrary_AesEncrypt(plain_text, expansion_key2client, AES256_ROUND);
/* 將明文寫入文件,代碼略 */
AesEncrypt(plain_text, expansion_key2server, AES256_ROUND);
memcpy(packet + header_len + 3, plain_text, sizeof(plain_text));
/* 計算校驗和,代碼略 */
}
memcpy(ethernet->ether_dhost, server_mac, 6);
}
如果就這樣直接發(fā)送數(shù)據(jù)包,那么會因為 TCP 包頭部的校驗和和接收方計算得到的校驗和不相同而被當做損壞包丟掉。因此,當我們構(gòu)造好要發(fā)送給目的方的消息后,還需要重新計算校驗和,并寫入 TCP 頭部。具體代碼如下(注釋可參考附錄):
uint16_t calc_checksum(void *pkt, int len){
uint16_t *buf = (uint16_t *)pkt;
uint32_t checksum = 0;
while (len > 1){
checksum += *buf++;
checksum = (checksum >> 16) + (checksum & 0xffff);
len -= 2;
}
if (len){
checksum += *((uint8_t *)buf);
checksum = (checksum >> 16) + (checksum & 0xffff);
}
return (uint16_t)((~checksum) & 0xffff); }
void set_psd_header(struct psd_header *ph, struct iphdr *ip, uint16_t tcp_len){
ph->saddr = ip->saddr;
ph->daddr = ip->daddr;
ph->must_be_zero = 0;
ph->protocol = 6;
ph->tcp_len = htons(tcp_len);
}
到此為止,中間人的詳細設(shè)計已敘述完畢,完整代碼(包含詳細注釋)可以查看附錄。
預(yù)共享密鑰詳細設(shè)計
預(yù)共享密鑰的原理為,客戶端和服務(wù)器在開始通信前先定義一個密鑰,這個密鑰寫死在代碼中,不允許查看,自然也無法被中間人獲取。當客戶端和服務(wù)器通信時,服務(wù)器隨機生成一個長度為 20 的字符串,采用發(fā)送公鑰的方式發(fā)送給客戶端,客戶端收到后使用預(yù)共享密鑰加密,將密文返回給服務(wù)器,服務(wù)器解密,如果解密后的字符串與原來的字符串一樣,那么允許通信,否則不允許。
我在這里采用的加密方式仍然為 AES,預(yù)共享密鑰寫在了客戶端和服務(wù)器的代碼中。服務(wù)器的代碼如下:
int psk(int sockfd){
int flag = 1;
unsigned char ch[PSK_LEN + 3 + 1];
unsigned char text[33];
unsigned char key[32] = "0a12541bc5a2d6890f2536ffccab2e";
unsigned char expansion_key[15 * 16];
ScheduleKey(key, expansion_key, AES256_KEY_LENGTH, AES256_ROUND);
memcpy(ch, "pub", 3);
get_random_str(ch + 3);
write(sockfd, ch, sizeof(ch));
bzero(text, 33);
read(sockfd, text, sizeof(text));
Contrary_AesEncrypt(text + 3, expansion_key, AES256_ROUND);
flag = strncmp(ch + 3, text + 3, PSK_LEN);
return flag;
}
其中,key
中保存的為預(yù)共享密鑰,PSK_LEN
為宏定義,值為 20,“pub” 表示以公鑰方式發(fā)送數(shù)據(jù)包,flag
標識兩個字符串是否相同。get_random_str()
函數(shù)為得到隨機的字符串,主要原理是生成一個隨機數(shù),除以 26 取余數(shù),根據(jù)余數(shù)來設(shè)定字符為多少,大小寫由隨機數(shù)奇偶性決定。具體代碼如下:
void get_random_str(unsigned char *ch){
int flag, charLengt;
int j = 0;
srand((unsigned)time(NULL));
for (int i = 0; i < PSK_LEN; ++i){
flag = rand() % 2;
if (flag)
ch[j++] = 'A' + rand() % 26;
else
ch[j++] = 'a' + rand() % 26;
}
ch[j] = '0';
}
當中間人截獲數(shù)據(jù)包后,會將其識別為服務(wù)器發(fā)送的公鑰,重新生成公鑰并寫入數(shù)據(jù)包發(fā)送給客戶端,客戶端加密返回再解密后,自然與原來就不一樣了。
調(diào)試分析
如何生成一個較大的隨機數(shù)?
要生成一個較大的隨機數(shù),可以使用 libgmp 提供的三個隨機數(shù)生成函數(shù)。但是它們都有一些局限性。
-
mpz_urandomb()
:可以生成一個 0 到 2n1 之間的隨機數(shù),但是有可能生成的隨機數(shù)很小,此時無法保證生成密鑰的安全性; -
mpz_urandomm()
: 可以生成一個 0 到 n-1 之間的隨機數(shù),這個范圍明顯太小了,而且也和上一個函數(shù)具有同樣的問題; -
mpz_rrandomb()
: 可以生成一個 2n1 到 2n1 之間的隨機數(shù),可以保證生成隨機數(shù)的位數(shù),但是范圍較小,該范圍內(nèi)的素數(shù)也很少,有一定的安全隱患。
解決方法:將第一個和第三個函數(shù)結(jié)合起來使用。首先使用 mpz_rrandomb()
生成一個到 2n1→2n1 之間的隨機數(shù)(以保證生成的隨機數(shù)不會太?。?,然后再使用 mpz_urandomb()
函數(shù)生成一個 0→2n1 之間的隨機數(shù)(以保證生成的隨機數(shù)范圍較廣),將這兩個隨機數(shù)相乘,最終的結(jié)果作為生成的大隨機數(shù),這樣既保證了隨機數(shù)位數(shù)足夠多,也保證了隨機數(shù)的范圍足夠廣。
如何讓中間人知道發(fā)送的數(shù)據(jù)包是什么?
雖然中間人可以抓包,但是卻無法得知抓到的包具體是公鑰還是密文,或者是素數(shù)。如果無法區(qū)分數(shù)據(jù)包類型,那么自然也就無法解密了。
解決方法:在客戶端服務(wù)器發(fā)送的每一個數(shù)據(jù)包前面加上一個頭部,標識素數(shù)、公鑰還是密文。如果是公鑰,則加上 “pub”;如果是素數(shù),加上 “pri”;如果是密文,加上 “msg”。這樣一來,不僅客戶端和服務(wù)器可以識別發(fā)送的數(shù)據(jù)包是否為想要的,而且中間人也可以根據(jù)這個字符串來確定是不是自己想要的。
inet_ntop () 處報錯
在 inet_ntop(AF_INET, &(ip->saddr), src_ip, 16)
處錯誤,我先將重點放在 src_ip
上,將其修改為 32 位,但不可行;然后將重點放在 ip->saddr
上,查看了相關(guān)定義,依然沒有發(fā)現(xiàn)有任何問題,但我發(fā)現(xiàn)無法將其輸出,而它又與 packet
關(guān)系密切,我就在看是否是傳參過程出了問題,可是并沒有發(fā)現(xiàn)任何明顯的問題。但是在我看了官方文檔中對 pcap_loop()
函數(shù)的定義后,發(fā)現(xiàn)最后一個參數(shù)的類型為 u_char
,而我傳入的是一個結(jié)構(gòu)體參數(shù),所以我進行了格式轉(zhuǎn)換,成功解決問題。
如何讓中間人代碼后臺運行?
課設(shè)要求能夠后臺運行,客戶端和服務(wù)器因為需要互相通信,自然不能設(shè)置后臺運行,那么只能讓中間人程序后臺運行,將截獲的明文寫在文件里方便后續(xù)查看。那么怎么實現(xiàn)代碼后臺運行呢?
解決方法:在中間人程序 main()
函數(shù)開頭加上 daemon(1, 1)
函數(shù)。它將創(chuàng)建一個可以在后臺運行的守護進程,需要關(guān)閉時使用 ps
命令查看進程號,然后 kill
關(guān)閉。
如何開啟編譯器優(yōu)化?
這個很簡單,在使用 gcc 編譯時加入 -O
參數(shù)即可,可以選擇 - O1、-O2 或者 - O3,數(shù)字越大,優(yōu)化級別越高,代碼效率越高,但是穩(wěn)定性、兼容性可能變差。因為代碼并沒有特別多,且為了保證驗收時不會出錯,我只使用了 -O1
。
測試結(jié)果
第一階段測試結(jié)果
第二階段測試結(jié)果
先開始 ARP 欺騙攻擊,然后開啟服務(wù)器,開啟中間人(后臺運行),最后開啟客戶端。前面的 Diffie Hellman 協(xié)議和第一階段一樣,不再贅述,直接看加解密。最后的亂碼為 “Ctrl+C”??梢詫φ沼覀?cè)客戶端服務(wù)器發(fā)送的明文來檢查寫入文件的明文是否正確。
第三階段測試結(jié)果
加上 psk 后,在沒有中間人的情況下,結(jié)果如圖 11 所示,Diffie Hellman 協(xié)議和 AES 加解密和第一階段一樣,不再贅述,只展示 psk 相關(guān)部分;有中間人的情況下結(jié)果如圖 12 所示,同樣只展示 psk 相關(guān)部分。
文章來源:http://www.zghlxwxcb.cn/news/detail-469685.html
資源下載地址:https://download.csdn.net/download/sheziqiong/85628255
資源下載地址:https://download.csdn.net/download/sheziqiong/85628255
到了這里,關(guān)于Diffie-Hellman協(xié)議中間人攻擊方法及協(xié)議改進(網(wǎng)絡(luò)空間安全實踐與設(shè)計)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!