资源下载地址:https://download.csdn.net/download/sheziqiong/85628255
资源下载地址:https://download.csdn.net/download/sheziqiong/85628255
第一阶段:Diffie-Hellman 协议的实现
第二阶段:Diffie-Hellman 中间人攻击方法研究与实现
第三阶段:Diffie-Hellman 协议改进
数据对象 :客户端与服务器进行通信所需要的所有函数等元素。
数据关系 :根据 IP 地址和端口号,服务器绑定端口号并监听,等待客户端连接;客户端尝试连接指定 IP 的指定端口号,成功连接则可以相互通信。
基本操作 :
数据对象 :进行 Diffie Hellman 密钥交换协议所需要的所有数据对象,包括结构体、函数等。
数据关系 :通过 Socket 进行客户端与服务器的通信,协商出用于 AES256-GCM 加密的密钥。
基本操作 :
数据对象 :用于 AES256-GCM 加密的所有数据类型、函数等。
数据关系 :使用 Diffie Hellman 结构中的 DH_key 数组中保存的密钥作为 AES 加密的密钥,对需要发送到明文进行加密,然后通过 Socket 结构发送;对通过 Socket 接收到的密文进行解密,输出明文。
基本操作 :
数据对象 :用于中间人程序,捕获经过网卡的数据包,对满足特定要求的数据包进行数据提取和保存。
数据关系 :监听网卡,调用 Diffie Hellman 结构中的函数用于和客户端、服务器分别建立信任关系;调用 AES 结构对提取到的密文进行解密和加密。
基本操作 :
不管是客户端还是服务器,都需要先使用 Socket 结构的相关函数来建立连接。建立连接之后,调用 Diffie Hellman 协议相关的函数协商出密钥,并将中间生成的素数、原根、私钥、公钥等数据保存在一个结构体当中。接下来,调用 AES256-GCM 加密模块,对要发送的数据加密、接收到的数据解密,直到程序结束。
对于中间人程序,不需要使用 Socket,取而代之的是数据包捕获模块。抓到服务器和客户端交换公钥的数据包后,会调用 Diffie Hellman 模块中的函数生成自己的私钥和公钥;抓到加密的信息后,会调用 AES256-GCM 加解密模块来对信息进行解密和加密。完成后再次回到数据包捕获状态。
该内容较简单,如图所示。
Diffie Hellman 协议的原理如下图所示。协议的实现需要大数处理,为了方便,可以使用 libgmp 来进行操作,它可以生成大数、判断一个大数是否为素数、进行大数之间的基本运算等。可以使用该库生成最初的大素数 p,并计算原根 g,然后随机生成两端的私钥 a 和 b,并进行模运算计算出公钥 A 和 B,交换公钥后,就可以利用模运算计算出最终的密钥 K 了,两端的 K 应该是一样的。
(实际上我的搭档交给我的只是 AES,是不是 256 都尚且存疑,但是我懒得去修改了,有兴趣的可以去参考 openssl 库。)GCM 是认证加密模式中的一种,它结合了 CTR 模式和 GMAC 模式的特点,能同时确保数据的保密性、完整性及真实性,另外,它还可以提供附加消息的完整性校验。具体原理如下图所示。
需要的密钥 K 由 Diffie Hellman 协议得到,然后根据矩阵运算写出行移位、列混合的函数,并引入官方的 S-Box 和逆向 S-Box 进行字节替换。不过在此之前,首先要进行密钥扩展,生成 AES 加密的轮密钥。iv 为初始向量,在这里主要用来进行完整性校验。
这里主要用到了 libpcap,该库可以捕获数据包,并对捕获到的每一个数据包进行处理,提取出需要的内容,放入想要放进去的数据,最后还要计算一下校验和,否则接收方收到后没有通过校验和检查,会将其视作损坏包而丢弃。
为了让数据包能够发送到中间人机器上来,还需要进行 ARP 欺骗,告诉服务器自己是客户端,告诉客户端自己是服务器。这里不需要写程序,可以直接使用工具 arpspoof 来实现双向欺骗。
根据要求使用预共享密钥的方法。首先要将一个密钥封装到代码内部,客户端和服务器都有且相同。在 Diffie Hellman 协商密钥后、AES 加密前,服务器先随机生成一段字符串,以公钥的形式发送给客户端,客户端收到后使用预共享密钥进行加密,将密文返回给服务器,服务器使用预共享密钥进行解密,如果解密后的字符串与发送的字符串相同,则允许互相通信,否则拒绝。
因为是以公钥形式发送的数据包,中间人在截获后会将其视作服务器发送的公钥而处理,重新生成私钥和公钥,并将公钥写入数据包发送给客户端,客户端收到的自然也就不是服务器发送的字符串了。
根据课设要求,需要使用 git 进行版本管理,这一步要从项目一开始就进行。由于是在 Linux 机器上进行开发,安装 git 很简单,之后只需要掌握 git 相关的命令,如 git add,git commit,git status,git log 等,就可以轻松使用了。如果使用了 VS Code 或者其他 IDE,可以很方便的利用它们提供的图形界面进行版本控制。
由于是两个人一起进行设计,使用了多个源文件,因此需要使用 makefile,在写好代码后将源代码汇聚到一起,根据其中的逻辑编写 makefile,在修改源代码后只需要输入 make 就可以编译了。
客户端程序的流程图如图所示,具体细节后面解释。
服务器端程序的流程图如下图所示,具体细节后面解释。
中间人程序的流程图如下图所示,具体细节后面解释。
这里用到了 libgmp。GNU MP (gmp) 是一个用 C 语言编写的可移植库,用于对整数、有理数和浮点数进行任意精度的算术运算,它旨在为所有需要比 C 语言直接支持运算的精度更高的应用程序提供尽可能快的算法。根据操作数的大小来选择使用的算法,并将开销控制在最低,这就是 gmp 被设计的目的。
对于 Diffie Hellman 协议,需要用到一个结构体来保存中间过程的数据。p
表示生成的大素数,g
为 p
的原根,这里方便起见,一般取 2 或者 5,pri_key
为随机生成的一个私钥,pub_key
为计算得到的公钥,k
为最终协商得到的密钥。mpz_t
为 libgmp 中定义的数据类型,为大整型数据。在中间人程序中,公钥变量和密钥变量需要有两个,分别为对服务器的和对客户端的。
typedef struct{
mpz_t p;
mpz_t g;
mpz_t pri_key;
mpz_t pub_key;
mpz_t k;}DH_key;
}
要进行 Diffie Hellman 协议,不可避免要生成大随机数,并且还要生成一个大素数,这里可以使用 libgmp 提供的一些函数和数据类型。要生成大随机数,可以结合使用两个随机数生成函数:mpz_rrandomb()
和 mpz_urandomb()
,将这两个数相乘就是最终的随机数,至于为什么这样做在 3.1 节中有详细解释。这两个函数都需要一个 gmp_randstate
类型的数据变量作为传入参数,该变量可以由函数 gmp_randinit_default()
来初始化,中间使用了 libgmp 默认的初始化算法。然后,获取当前时间作为种子传入 gmp_randstate
变量当中,这样就可以将它作为状态传入随机数生成函数当中了。具体代码如下:
void get_random_int(mpz_t z, mp_bitcnt_t n){
mpz_t temp; // 临时mpz_t变量,用于生成随机数,用完即废弃
gmp_randstate_t grt; // gmp状态,用于生成随机数
gmp_randinit_default(grt); // 使用默认算法初始化状态
gmp_randseed_ui(grt, (mp_bitcnt_t)clock()); //将时间作为种子传入状态grt中
mpz_rrandomb(z, grt, n); // 生成2^(n-1)到2^n-1之间一个随机数
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)之间的随机数,可能为0
}while (mpz_cmp_ui(temp, (unsigned long int)0) \<= 0);
mpz_mul(z, z, temp); // 两个随机数相乘
mpz_clear(temp);
}
上面生成的知识一个较大的随机数,可是 Diffie Hellman 协议需要一个大素数。大素数的生成也可以使用 libgmp 提供的一些函数。首先使用上面的函数生成一个大随机数,然后对其进行素性检测,这里用到了 mpz_probab_prime_p()
函数,它可以检测一个数是否为素数,函数会执行一个 Baillie-PSW 概率素性检测,然后执行指定次数的 Miller-Robin 素性检测,根据需要可以设置 15 到 50 次。如果一定是素数,函数返回 2;可能是素数,返回 1;肯定不是素数,返回 0。这里只需要返回非零值即可。mpz_nextprime()
函数可以得到比传入参数大、且离得最近的一个素数,如果随机生成的数不是素数,就使用该函数得到一个素数。
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);
}
}
要生成私钥,和随机生成一个大数没什么不同,而且没有了素数的限制,只需要调用 get_random_int()
函数即可,不再赘述。
不管是生成公钥还是计算得到最终的密钥,都需要进行指数运算和模运算,这里 libgmp 也提供了相关的运算函数:mpz_powm(rlt, a, b, c)
。它可以计算 abmod c,并将结果传递给 rlt。这样一来公钥和密钥的计算也迎刃而解。
以客户端为例(服务器类似),最终进行 Diffie Hellman 协议的相关代码如下:
void exchange_dh_key(int sockfd, mpz_t s){
DH_key client_dh_key; // 客户端生成的密钥
mpz_t server_pub_key; // 服务器公钥
// 初始化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发送给服务器,代码略 */
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);
/* 接收服务器公钥,发送客户端公钥,代码略 */
// 客户端计算DH协议得到的密钥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 加解密模块基本全部由我的搭档完成,在此只做简单叙述。首先,不管是客户端、服务器还是中间人,都需要有 S-Box 和逆向的 S-Box,这个直接写入代码即可。因为内容过多且并不重要,不再展示。
AES 加解密是定义在有限域上的,因此需要实现有限域乘法。下面给出了 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 加解密都需要扩展密钥,生成轮密钥,具体实现如下:
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;
}
}
}
生成轮密钥后,开始进行字节替换、行移位、列混合等操作。字节替换很简单,就是通过查找 S-Box,将每个字节替换成相应的字节,逆向字节替换类似,只是将 S-Box 换成了 Contrary S-Box。这里只展示正向的。
static void SubBytes(unsigned char *col){
int x;
for (x = 0; x < 16; x++)
col[x] = sbox[col[x]];
}
行移位就是将矩阵中的每个横列进行循环式移位,对于长度 256 比特的区块,第一行维持不变,第二行、第三行、第四行的偏移量分别是 1 字节、3 字节、4 位。这个过程只需声明中间变量,然后进行交换即可。列混合是为了混合矩阵中各个直行而进行的操作。这个步骤使用线性转换来混合每内联的四个字节,只有最后一轮才省略掉列混合,而以轮密钥加替代。
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];
}
最终的加密总函数就是将上面叙述的各个步骤叠加到一起,主要代码如下:
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);
}
解密函数也类似,如下所示:
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);
}
中间人攻击就是截获局域网内两台主机通信的数据包,并进行提取和修改,让通信双方都以为在和对方通信,实际上却在和中间人通信。具体原理如图所示。
要进行中间人攻击,最首先、最关键的一步就是捕获数据包,这里可以使用 libpcap 进行捕获。使用 libpcap 的主要流程是:
pcap_lookupdev()
: 获取可用的网络设备名指针;pcap_open_live()
: 打开指定的网络设备,并返回用于捕获网络数据包的描述字;pcap_compile()
: 将用户制定的 BPF 过滤规则编译到过滤程序当中;pcap_setfilter()
: 应用 BPF 过滤规则,让过滤规则生效;pcap_loop()
: 循环抓包,遇到错误或者执行结束时退出。callback
函数传入 pcap_loop()
函数,对捕获到的每一个数据包进行处理。这里前面五步都是模板,重点是回调函数,这点在 2.3 节中间人程序流程图中已经大致展示了,下面具体叙述一下。
在启动中间人程序之前,需要先进行 ARP 欺骗,不断地告诉服务器:客户端的 MAC 地址为中间人的 MAC;告诉客户端:服务器的 MAC 地址为中间人的 MAC。这样它们发送的数据包都会送到中间人这里。当中间人程序抓到一个数据包后,首先需要判断它是来自客户端还是来自服务器的,然后判断是公钥还是加密信息或大素数。如果来自服务器,那么需要修改数据包以太帧头中的目的 MAC 地址为客户端(因为有 ARP 欺骗,原来的 MAC 地址为中间人的);如果来自客户端,那么需要修改以太帧头中的目的 MAC 地址为服务器(原来的 MAC 地址为中间人)。如果是服务器发送的公钥,那么需要使用 Diffie Hellman 协议模块定义的一些函数生成自己的私钥,并计算得到公钥,然后用自己的公钥把服务器的替换掉,发送给客户端;如果是客户端发送的公钥,直接将自己的公钥写入替换掉客户端的公钥,然后发送给服务器。收到上面两个数据包后,中间人对服务器的密钥、中间人对客户端的密钥都已经可以计算出来了。如果是服务器发送的加密信息,就使用对服务器的密钥解密,保存在文件当中,然后使用对客户端的密钥加密,发送给客户端;如果是客户端发送的密文,和上面类似。
具体实现其实并不复杂,因为逻辑也很简单,但是一些细节很容易出错,需要细心。这里只展示抓到客户端发出的数据包的代码,服务器类似,可在附录中查看。
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);
}
如果就这样直接发送数据包,那么会因为 TCP 包头部的校验和和接收方计算得到的校验和不相同而被当做损坏包丢掉。因此,当我们构造好要发送给目的方的消息后,还需要重新计算校验和,并写入 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);
}
到此为止,中间人的详细设计已叙述完毕,完整代码(包含详细注释)可以查看附录。
预共享密钥的原理为,客户端和服务器在开始通信前先定义一个密钥,这个密钥写死在代码中,不允许查看,自然也无法被中间人获取。当客户端和服务器通信时,服务器随机生成一个长度为 20 的字符串,采用发送公钥的方式发送给客户端,客户端收到后使用预共享密钥加密,将密文返回给服务器,服务器解密,如果解密后的字符串与原来的字符串一样,那么允许通信,否则不允许。
我在这里采用的加密方式仍然为 AES,预共享密钥写在了客户端和服务器的代码中。服务器的代码如下:
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
中保存的为预共享密钥,PSK_LEN
为宏定义,值为 20,“pub” 表示以公钥方式发送数据包,flag
标识两个字符串是否相同。get_random_str()
函数为得到随机的字符串,主要原理是生成一个随机数,除以 26 取余数,根据余数来设定字符为多少,大小写由随机数奇偶性决定。具体代码如下:
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';
}
当中间人截获数据包后,会将其识别为服务器发送的公钥,重新生成公钥并写入数据包发送给客户端,客户端加密返回再解密后,自然与原来就不一样了。
要生成一个较大的随机数,可以使用 libgmp 提供的三个随机数生成函数。但是它们都有一些局限性。
mpz_urandomb()
:可以生成一个 0 到 2n1 之间的随机数,但是有可能生成的随机数很小,此时无法保证生成密钥的安全性;mpz_urandomm()
: 可以生成一个 0 到 n-1 之间的随机数,这个范围明显太小了,而且也和上一个函数具有同样的问题;mpz_rrandomb()
: 可以生成一个 2n1 到 2n1 之间的随机数,可以保证生成随机数的位数,但是范围较小,该范围内的素数也很少,有一定的安全隐患。解决方法:将第一个和第三个函数结合起来使用。首先使用 mpz_rrandomb()
生成一个到 2n1→2n1 之间的随机数(以保证生成的随机数不会太小),然后再使用 mpz_urandomb()
函数生成一个 0→2n1 之间的随机数(以保证生成的随机数范围较广),将这两个随机数相乘,最终的结果作为生成的大随机数,这样既保证了随机数位数足够多,也保证了随机数的范围足够广。
虽然中间人可以抓包,但是却无法得知抓到的包具体是公钥还是密文,或者是素数。如果无法区分数据包类型,那么自然也就无法解密了。
解决方法:在客户端服务器发送的每一个数据包前面加上一个头部,标识素数、公钥还是密文。如果是公钥,则加上 “pub”;如果是素数,加上 “pri”;如果是密文,加上 “msg”。这样一来,不仅客户端和服务器可以识别发送的数据包是否为想要的,而且中间人也可以根据这个字符串来确定是不是自己想要的。
在 inet_ntop(AF_INET, &(ip->saddr), src_ip, 16)
处错误,我先将重点放在 src_ip
上,将其修改为 32 位,但不可行;然后将重点放在 ip->saddr
上,查看了相关定义,依然没有发现有任何问题,但我发现无法将其输出,而它又与 packet
关系密切,我就在看是否是传参过程出了问题,可是并没有发现任何明显的问题。但是在我看了官方文档中对 pcap_loop()
函数的定义后,发现最后一个参数的类型为 u_char
,而我传入的是一个结构体参数,所以我进行了格式转换,成功解决问题。
课设要求能够后台运行,客户端和服务器因为需要互相通信,自然不能设置后台运行,那么只能让中间人程序后台运行,将截获的明文写在文件里方便后续查看。那么怎么实现代码后台运行呢?
解决方法:在中间人程序 main()
函数开头加上 daemon(1, 1)
函数。它将创建一个可以在后台运行的守护进程,需要关闭时使用 ps
命令查看进程号,然后 kill
关闭。
这个很简单,在使用 gcc 编译时加入 -O
参数即可,可以选择 - O1、-O2 或者 - O3,数字越大,优化级别越高,代码效率越高,但是稳定性、兼容性可能变差。因为代码并没有特别多,且为了保证验收时不会出错,我只使用了 -O1
。
先开始 ARP 欺骗攻击,然后开启服务器,开启中间人(后台运行),最后开启客户端。前面的 Diffie Hellman 协议和第一阶段一样,不再赘述,直接看加解密。最后的乱码为 “Ctrl+C”。可以对照右侧客户端服务器发送的明文来检查写入文件的明文是否正确。
加上 psk 后,在没有中间人的情况下,结果如图 11 所示,Diffie Hellman 协议和 AES 加解密和第一阶段一样,不再赘述,只展示 psk 相关部分;有中间人的情况下结果如图 12 所示,同样只展示 psk 相关部分。
资源下载地址:https://download.csdn.net/download/sheziqiong/85628255
资源下载地址:https://download.csdn.net/download/sheziqiong/85628255