Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

这篇具有很好参考价值的文章主要介绍了Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

资源下载地址:https://download.csdn.net/download/sheziqiong/85628255
资源下载地址:https://download.csdn.net/download/sheziqiong/85628255文章来源地址https://www.toymoban.com/news/detail-469685.html

第一阶段:Diffie-Hellman 协议的实现

  1. 客户端与服务器之间通过 TCP Socket 通信;
  2. 客户端与服务器之间通过 Diffie-Hellman 协议协商出对称密钥;
  3. 客户端使用协商出的对称密钥对传输内容做加密,并发送给服务端;
  4. 服务端接受客户端发送过来的内容,进行解密;
  5. 对称加密算法采用 AES256-GCM;

第二阶段:Diffie-Hellman 中间人攻击方法研究与实现

  1. 研究 Diffie-Hellman 协议,研究中间人攻击方法并完成相关代码。当通信双方进行通信时,中间人攻击程序可以解密出传输内容;

第三阶段:Diffie-Hellman 协议改进

概要设计

抽象数据类型定义

Socket 结构

数据对象 :客户端与服务器进行通信所需要的所有函数等元素。

数据关系 :根据 IP 地址和端口号,服务器绑定端口号并监听,等待客户端连接;客户端尝试连接指定 IP 的指定端口号,成功连接则可以相互通信。

基本操作

  • socket (): 创建一个 socket 句柄,用于客户端与服务器通信。
  • bind (): 绑定指定的端口号,成功则继续,失败报错并结束。
  • listen (): 监听指定端口号,等待客户端连接。
  • accept (): 当有客户端尝试连接时,接收连接,并返回句柄。
  • connect (): 客户端根据指定的 IP 地址和端口号,连接服务器,成功则返回连接句柄,失败则报错,并终止程序。

Diffie Hellman 结构

数据对象 :进行 Diffie Hellman 密钥交换协议所需要的所有数据对象,包括结构体、函数等。

数据关系 :通过 Socket 进行客户端与服务器的通信,协商出用于 AES256-GCM 加密的密钥。

基本操作

  • struct DH_key: 用于 Diffie Hellman 密钥交换协议的结构体,包含了素数、原根、公钥、私钥、协商出来的密钥等数据。
  • get_random_int (): 输入一个 mpz_t 类型的数据指针,生成一个随机整数,并输出到输入的指针中,这个数会非常大。
  • check_prime (): 输入一个 mpz_t 类型的数据指针,检测对应的数据是否为素数,一定是素数则返回 2,可能是素数则返回 1,不是素数则返回 0.
  • generate_p (): 输入一个 mpz_t 类型的数据指针,输出一个大素数。
  • generate_pri_key (): 输入一个 mp_t 类型的数据指针,输出一个大素数,用作客户端或者服务器进行 Diffie Hellman 协议的私钥。
  • psk (): 预共享密钥函数,主要用于防止中间人攻击。

AES 加密结构

数据对象 :用于 AES256-GCM 加密的所有数据类型、函数等。

数据关系 :使用 Diffie Hellman 结构中的 DH_key 数组中保存的密钥作为 AES 加密的密钥,对需要发送到明文进行加密,然后通过 Socket 结构发送;对通过 Socket 接收到的密文进行解密,输出明文。

基本操作

  • sbox: 256 位数组,AES 加密需要的 S-Box。
  • contrary_sbox: 256 位数组,AES 解密需要的逆向 S-Box。
  • ScheduleKey (): 编排密钥(密钥扩展),输入用于 AES256-GCM 加密的密钥,输出每一轮加密的轮密钥。
  • SubBytes (): 查 S-Box,对输入的明文分组进行字节替换。
  • Contrary_SubBytes (): 查逆向 S-Box,对输入的密文分组进行字节替换。
  • ShiftRows (): 行移位,用于加密。
  • Contrary_ShiftRows (): 逆向行移位,用于解密。
  • Mix_Column (): 列混合,用于加密。
  • Contrary_Mix_Column (): 逆向列混合,用于解密。
  • AesEncrypt (): AES 加密函数,输入明文,输出密文。
  • Contrary_AesEncrypt (): AES 解密函数,输入密文,输出明文。

中间人数据包捕获结构

数据对象 :用于中间人程序,捕获经过网卡的数据包,对满足特定要求的数据包进行数据提取和保存。

数据关系 :监听网卡,调用 Diffie Hellman 结构中的函数用于和客户端、服务器分别建立信任关系;调用 AES 结构对提取到的密文进行解密和加密。

基本操作

  • pcap_lookupdev (): 包含在 libpcap 中,输入要存放错误信息的数组,输出本机第一个网络设备名称。
  • pcap_open_live (): 包含在 libpcap 中,输入网络设备名字指针,打开对应的网络设备,输出网络设备的句柄。
  • pcap_compile (): 包含在 libpcap 中,编译 BPF 过滤规则。
  • pcap_setfilter (): 包含在 libpcap 中,应用 BPF 过滤规则。
  • pcap_loop (): 包含在 libpcap 中,输入设备句柄,开始捕获数据包,每捕获到一个数据报,就调用一次回调函数处理数据包。
  • pcap_sendpacket (): 包含在 libpcap 中,输入设备句柄和数据包数据与长度,将数据包发往目的地。
  • process_pkt (): 是 pcap_loop () 的回调函数,输入网络地址信息和设备句柄,对抓到的符合 BPF 过滤规则的包进行处理。
  • calc_checksum (): 输入数据包数据和长度,输出 TCP 校验和。
  • set_psd_header (): 设置 TCP 数据包的头部,输入头部指针和需要写入头部的数据指针。

主程序流程

客户端主程序流程

  1. 进行 Socket 连接,成功连接上服务器后进行下一步,否则退出;
  2. 生成大素数 p 和原根 g,并发送给服务器
  3. 生成客户端私钥,并结合 p 和 g 计算出客户端公钥;
  4. 接收服务器的公钥并保存,发送客户端公钥给服务器;
  5. 根据服务器公钥、素数 p 和客户端私钥计算出最终用于 AES 加解密的密钥;
  6. 发送数据给客户端,数据内容为用户输入明文加密后的密文;
  7. 接收服务器发送的密文,并解密输出;
  8. 重复 6 和 7,直到连接中断。

服务器主程序流程

  1. 创建 Socket 连接,当有客户端连接时,接收素数 p 和原根 g;
  2. 生成服务器私钥,并计算出公钥,发送给客户端;
  3. 接收客户端的公钥,并计算出用于 AES 加解密的密钥;
  4. 等待客户端发送的密文,并进行解密和输出;
  5. 对服务器输入的明文加密,将密文发送给客户端;
  6. 重复 4 和 5,直到连接中断。

中间人主程序流程

  1. 使用 arpspoof 进行 ARP 欺骗;
  2. 打开网络设备,设置 BPF 过滤规则,开始捕获数据包;
  3. 每捕获到一个符合要求的数据包,查看是从客户端发送的还是从服务器发送的,前者执行 4) 和 5),后者执行 6) 和 7);
  4. 若是客户端公钥,则自己保存后替换为自己的公钥,重新设置校验和和头部后发送给服务器,返回 3);
  5. 若是客户端发送的加密信息,则使用对客户端的密钥解密信息并写入文件,然后将明文使用对服务器的密钥加密,重新设置校验和和头部后再发送给服务器,返回 3);
  6. 若是服务器公钥,则自己保存后生成自己的私钥和公钥,重新设置校验和和头部后,将公钥发送给客户端,返回 3);
  7. 若是服务器发送的加密信息,则使用对服务器的密钥解密信息并写入文件,然后将明文使用对客户端的密钥加密后,重新设置校验和和头部后,再发送给客户端,返回 3)。

各程序调用关系

不管是客户端还是服务器,都需要先使用 Socket 结构的相关函数来建立连接。建立连接之后,调用 Diffie Hellman 协议相关的函数协商出密钥,并将中间生成的素数、原根、私钥、公钥等数据保存在一个结构体当中。接下来,调用 AES256-GCM 加密模块,对要发送的数据加密、接收到的数据解密,直到程序结束。

对于中间人程序,不需要使用 Socket,取而代之的是数据包捕获模块。抓到服务器和客户端交换公钥的数据包后,会调用 Diffie Hellman 模块中的函数生成自己的私钥和公钥;抓到加密的信息后,会调用 AES256-GCM 加解密模块来对信息进行解密和加密。完成后再次回到数据包捕获状态。

技术开发思路

Socket 通信的实现

该内容较简单,如图所示。

Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

Diffie Hellman 协议的实现

Diffie Hellman 协议的原理如下图所示。协议的实现需要大数处理,为了方便,可以使用 libgmp 来进行操作,它可以生成大数、判断一个大数是否为素数、进行大数之间的基本运算等。可以使用该库生成最初的大素数 p,并计算原根 g,然后随机生成两端的私钥 a 和 b,并进行模运算计算出公钥 A 和 B,交换公钥后,就可以利用模运算计算出最终的密钥 K 了,两端的 K 应该是一样的。

Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

AES256-GCM 加解密的实现

(实际上我的搭档交给我的只是 AES,是不是 256 都尚且存疑,但是我懒得去修改了,有兴趣的可以去参考 openssl 库。)GCM 是认证加密模式中的一种,它结合了 CTR 模式和 GMAC 模式的特点,能同时确保数据的保密性、完整性及真实性,另外,它还可以提供附加消息的完整性校验。具体原理如下图所示。

Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

需要的密钥 K 由 Diffie Hellman 协议得到,然后根据矩阵运算写出行移位、列混合的函数,并引入官方的 S-Box 和逆向 S-Box 进行字节替换。不过在此之前,首先要进行密钥扩展,生成 AES 加密的轮密钥。iv 为初始向量,在这里主要用来进行完整性校验。

中间人捕获数据包的实现

这里主要用到了 libpcap,该库可以捕获数据包,并对捕获到的每一个数据包进行处理,提取出需要的内容,放入想要放进去的数据,最后还要计算一下校验和,否则接收方收到后没有通过校验和检查,会将其视作损坏包而丢弃。

为了让数据包能够发送到中间人机器上来,还需要进行 ARP 欺骗,告诉服务器自己是客户端,告诉客户端自己是服务器。这里不需要写程序,可以直接使用工具 arpspoof 来实现双向欺骗。

防止中间人攻击的实现

根据要求使用预共享密钥的方法。首先要将一个密钥封装到代码内部,客户端和服务器都有且相同。在 Diffie Hellman 协商密钥后、AES 加密前,服务器先随机生成一段字符串,以公钥的形式发送给客户端,客户端收到后使用预共享密钥进行加密,将密文返回给服务器,服务器使用预共享密钥进行解密,如果解密后的字符串与发送的字符串相同,则允许互相通信,否则拒绝。

因为是以公钥形式发送的数据包,中间人在截获后会将其视作服务器发送的公钥而处理,重新生成私钥和公钥,并将公钥写入数据包发送给客户端,客户端收到的自然也就不是服务器发送的字符串了。

git 版本管理

根据课设要求,需要使用 git 进行版本管理,这一步要从项目一开始就进行。由于是在 Linux 机器上进行开发,安装 git 很简单,之后只需要掌握 git 相关的命令,如 git add,git commit,git status,git log 等,就可以轻松使用了。如果使用了 VS Code 或者其他 IDE,可以很方便的利用它们提供的图形界面进行版本控制。

使用 makefile 将多个源文件结合

由于是两个人一起进行设计,使用了多个源文件,因此需要使用 makefile,在写好代码后将源代码汇聚到一起,根据其中的逻辑编写 makefile,在修改源代码后只需要输入 make 就可以编译了。

详细设计

客户端程序流程图

客户端程序的流程图如图所示,具体细节后面解释。

Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

服务器程序流程图

服务器端程序的流程图如下图所示,具体细节后面解释。

Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

中间人程序流程图

中间人程序的流程图如下图所示,具体细节后面解释。

Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

这里用到了 libgmp。GNU MP (gmp) 是一个用 C 语言编写的可移植库,用于对整数、有理数和浮点数进行任意精度的算术运算,它旨在为所有需要比 C 语言直接支持运算的精度更高的应用程序提供尽可能快的算法。根据操作数的大小来选择使用的算法,并将开销控制在最低,这就是 gmp 被设计的目的。

对于 Diffie Hellman 协议,需要用到一个结构体来保存中间过程的数据。p 表示生成的大素数,gp 的原根,这里方便起见,一般取 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 加解密详细设计

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);
}  

中间人攻击详细设计

中间人攻击就是截获局域网内两台主机通信的数据包,并进行提取和修改,让通信双方都以为在和对方通信,实际上却在和中间人通信。具体原理如图所示。

Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

要进行中间人攻击,最首先、最关键的一步就是捕获数据包,这里可以使用 libpcap 进行捕获。使用 libpcap 的主要流程是:

  1. pcap_lookupdev(): 获取可用的网络设备名指针;
  2. pcap_open_live(): 打开指定的网络设备,并返回用于捕获网络数据包的描述字;
  3. pcap_compile(): 将用户制定的 BPF 过滤规则编译到过滤程序当中;
  4. pcap_setfilter(): 应用 BPF 过滤规则,让过滤规则生效;
  5. pcap_loop(): 循环抓包,遇到错误或者执行结束时退出。
  6. 自己编写 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 提供的三个随机数生成函数。但是它们都有一些局限性。

  1. mpz_urandomb():可以生成一个 0 到 2n1 之间的随机数,但是有可能生成的随机数很小,此时无法保证生成密钥的安全性;
  2. mpz_urandomm(): 可以生成一个 0 到 n-1 之间的随机数,这个范围明显太小了,而且也和上一个函数具有同样的问题;
  3. mpz_rrandomb(): 可以生成一个 2n1 到 2n1 之间的随机数,可以保证生成随机数的位数,但是范围较小,该范围内的素数也很少,有一定的安全隐患。

解决方法:将第一个和第三个函数结合起来使用。首先使用 mpz_rrandomb() 生成一个到 2n1→2n1 之间的随机数(以保证生成的随机数不会太小),然后再使用 mpz_urandomb() 函数生成一个 0→2n1 之间的随机数(以保证生成的随机数范围较广),将这两个随机数相乘,最终的结果作为生成的大随机数,这样既保证了随机数位数足够多,也保证了随机数的范围足够广。

如何让中间人知道发送的数据包是什么?

虽然中间人可以抓包,但是却无法得知抓到的包具体是公钥还是密文,或者是素数。如果无法区分数据包类型,那么自然也就无法解密了。

解决方法:在客户端服务器发送的每一个数据包前面加上一个头部,标识素数、公钥还是密文。如果是公钥,则加上 “pub”;如果是素数,加上 “pri”;如果是密文,加上 “msg”。这样一来,不仅客户端和服务器可以识别发送的数据包是否为想要的,而且中间人也可以根据这个字符串来确定是不是自己想要的。

inet_ntop () 处报错

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

测试结果

第一阶段测试结果

Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

第二阶段测试结果

先开始 ARP 欺骗攻击,然后开启服务器,开启中间人(后台运行),最后开启客户端。前面的 Diffie Hellman 协议和第一阶段一样,不再赘述,直接看加解密。最后的乱码为 “Ctrl+C”。可以对照右侧客户端服务器发送的明文来检查写入文件的明文是否正确。

Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

第三阶段测试结果

加上 psk 后,在没有中间人的情况下,结果如图 11 所示,Diffie Hellman 协议和 AES 加解密和第一阶段一样,不再赘述,只展示 psk 相关部分;有中间人的情况下结果如图 12 所示,同样只展示 psk 相关部分。


Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)

资源下载地址:https://download.csdn.net/download/sheziqiong/85628255
资源下载地址:https://download.csdn.net/download/sheziqiong/85628255

到了这里,关于Diffie-Hellman协议中间人攻击方法及协议改进(网络空间安全实践与设计)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 如何预防ssl中间人攻击?

    当我们连上公共WiFi打开网页或邮箱时,殊不知此时可能有人正在监视着我们的各种网络活动。打开账户网页那一瞬间,不法分子可能已经盗取了我们的银行凭证、家庭住址、电子邮件和联系人信息,而这一切我们却毫不知情。这是一种网络上常见的“中间人攻击”,通过拦截

    2024年02月13日
    浏览(36)
  • 使用中间人攻击的arp欺骗教程

    中间人攻击是网络路由器上最常尝试的攻击之一。它们主要用于获取登录凭据或个人信息、监视受害者或破坏通信或损坏数据。 中间人攻击是指攻击者拦截双方之间的来回消息流以更改消息或只是读取消息。 在本文中,我们将了解如何在连接到与我们相同的WiFi网络上的设备

    2024年02月14日
    浏览(36)
  • HTTPS连接过程中的中间人攻击

    https协议就是http+ssl/tls协议,如下图所示为其连接过程: HTTPS连接的整个工程如下: https请求:客户端向服务端发送https请求; 生成公钥和私钥:服务端收到请求之后,生成公钥和私钥。公钥相当于是锁,私钥相当于是钥匙,只有私钥才能够打开公钥锁住的内容; 返回公钥:

    2024年02月15日
    浏览(121)
  • 【每天学习一点新知识】中间人攻击是什么

    目录 中间人攻击介绍  中间人攻击原理 1. ARP欺骗 2. DNS欺骗 防御方法   中间人攻击(man-in-the-middle attack, abbreviated to MITM),顾名思义,就是攻击者躲在通信双方之间,窃听甚至篡改通信信息,而这个攻击是不可见的,通信双方并不知道消息已经被截获甚至篡改了。 这个图片很

    2024年02月09日
    浏览(43)
  • 网络安全 中间人攻击-ARP欺骗 工具:Cain

    两台虚拟机:A和B,A中有工具Cain A是攻击机, B是靶机 首先,在B中使用cmd查看IP地址,注意网关 再查看B的ARP表 都查看完之后,回带有工具的虚拟机A中,打开Cain 下面开始在Cain中进行ARP毒化: 开启监听 开始嗅探: Sniffer——先清除以前的记录——添加扫描列表——选择目标,

    2024年02月06日
    浏览(47)
  • 安全攻防实战丨如何预防利用中间人攻击进行小程序刷分

    本文分享自华为云社区《【安全攻防】深入浅出实战系列专题-利用中间人攻击进行小程序刷分》,作者: MDKing 。 中间人攻击(Man-in-the-middle attack,简称MITM)是攻击者在进行网络通信的双方中间,分别与两端建立独立的联系,并进行数据嗅探甚至篡改,而通信的双方却对中

    2024年02月08日
    浏览(36)
  • HTTPS——HTTPS如何加密数据,“证书“为什么可以应对 “中间人攻击“

    本人是一个普通程序猿!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果你也对编程感兴趣的话,互关一下,以后互相学习,共同进步。这篇文章能够帮助到你的话,劳请大家点赞转发支持一下! HTTPS 也是一个应用层协议, 是在 HTTP 协议的基础上引入了一个加

    2024年02月15日
    浏览(50)
  • Diffie-Hellman的C++语言描述简单实现

    网络安全中Diffie-Hellman的C++语言描述简单实现。 yezhening/Programming-examples: 编程实例 (github.com) Programming-examples: 编程实例 (gitee.com) 纯C++语言: 相对规范和整洁 一定程度地面向对象 使用一部分高级特性 考虑优化性能 详细注释: 提示规范和整洁 提示面向对象 提示高级特性 提示

    2024年02月04日
    浏览(45)
  • 信息与网络安全 Diffie-Hellman密匙交换算法 题目练习

    1.考虑公共素数 q = 11 本原元 a = 2 的Diffie-Hellman方案 (1)如果用户A有公钥 YA = 9 ,请问A的私钥XA是什么? (2)如果用户B有公钥 YB = 3 ,请问共享的密钥K是什么? 解: (1)根据Diffie-Hellman密钥交换原理——设g是一个质数,n是g的本原元,要求n和g是公开的,则网络中的某一用

    2024年01月16日
    浏览(37)
  • Diffie-Hellman Key Agreement Protocol 资源管理错误漏洞(CVE-2002-20001)

    项目在等保漏洞扫描时扫描出来了“资源管理错误漏洞”,实操修复 Diffie-Hellman Key Agreement Protocol是一种密钥协商协议。它最初在 Diffie 和 Hellman 关于公钥密码学的开创性论文中有所描述。该密钥协商协议允许 Alice 和 Bob 交换公钥值,并根据这些值和他们自己对应的私钥的知识

    2024年02月11日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包