c语言void 交换的符号,C语言模拟D-H密钥交换及中间人攻击

0. 概述

这是一次课程设计,话说这个题目有些复杂,但毕竟是最后一次,可以理解

实现目标:

用C语言在Linux系统环境下模拟D-H密钥交换,并以协商出的密钥对传输信息用AES-256-GCM进行加密,实现加密通信

对其进行中间人攻击,在不干扰正常通信的情况下,截获其通讯内容

使用预共享密钥的方法防止中间人攻击

实现内容:

可自由发挥的空间不多,在密钥交换后,我实现的是客户端可以输入简单指令在服务端执行,并获取反馈

使用工具:

因为限定了C语言和Linux

大数计算:Libtommath

加密: Libtomcrypt

抓包发包: Libpcap

由于这三个库安装起来较简单,这里就不叙述了(网上一堆)

libtommath和libtomcrypt网上资料较少,看github上的pdf文档即可

部分内容凭记忆写的,有问题欢迎指正

1. 密钥交换

libtomcrypt虽然本身有D-H密钥交换的功能实现,但是直接用的话怕是太水了(总不能课设一个include就完事),故以学习为目的手动实现了下,设计如下:

符号&&流程概述:

双方共享p,g,g为p的原根

A生成一个只有自己知道的数字a,计算x=g^a mod p

将x发送给B

B生成一个只有自己知道的数字b,计算y=g^b mod p

将y发送给A

A计算key = y^a mod p

B计算key = x^b mod p

根据交换原理,二者生成key相同

具体实现:

客户端建立连接,计算并发送p,g,x

服务端接收到p, g, x,生成b并计算y,将y返回,同时计算出密钥key

客户端收到y,计算出key

(1) p的生成

libtommath 生成一个大素数

mp_prime_random_ex(p, 8, 256, LTM_PRIME_2MSB_ON | LTM_PRIME_SAFE, rng, NULL);

注意,一定要带上 LTM_PRIME_SAFE 参数,原因在下一部分会说

(2) g的生成

g是p的原根,并不需要多大

根据原根检测技巧(参考自:https://blog.csdn.net/zhang20072844/article/details/11541133)

即有一个数x, 求出x-1所有不同的质因子p1,p2…pm,对于任何2<=a<=x-1,判定a是否为x的原根,只需要检验a^((x-1)/p1) ,

a^((x-1)/p2) …a^((x-1)/pm)这m个数中,是否存在一个数mod x为1,若存在,a不是x的原根,否则就是x的原根

而文档中对LTM_PRIME_SAFE参数的叙述是这样的:

Make a prime p such that (p - 1)/2 is also prime.

This option implies LTM PRIME BBS as well.

即p-1一定只有两个质因子2和(p-1)/2,这大大方便了检测,具体实现代码如下

(可能有些麻烦,不过功能没问题)

void get_primitive_root(mp_int* num, mp_int* root)

{

mp_set(root, 2);

mp_int temp, param1, param2;

// 第一个参数为2

mp_init_set(&param1, 2);

mp_init_multi(&temp, &param2, NULL);

// 第二个参数问 (roo-1)/2

mp_sub_d(num, 1, &param2);

mp_div_2(&param2, &param2);

while (true)

{

mp_exptmod(root, &param1, num, &temp);

if (mp_cmp_d(&temp, 1) != MP_EQ)

{

mp_exptmod(root, &param2, num, &temp);

if (mp_cmp_d(&temp, 1) != MP_EQ)

{

break;

}

}

mp_add_d(root, 1, root);

}

mp_clear_multi(&temp, &param1, &param2, NULL);

}

(3) a,b的生成

随机且足够大的数即可,这里我采用的是mp_rand方法

官方文档中的mp_rand是这样描述的

This function generates a random number of digits bits.

然而实际测试中这个bits并不是二级制位,而是空间占用? 看了下源码,是mp_int.used的值

故如果生成一个和p差不多大的数字:

mp_rand(a, key->p.used);

(4) “协议”的构建

我自定义的协议头:

开头两个字节为df,第三位为1(版本号),第四位为操作特征符

定义特征符为

1:发起密钥交换

2:回应密钥交换

0:交换后的正常数据传输

定义一个特征头部有助于抓包中进行判定和筛选数据包

(5) AES-256-GCM加解密

这里直接调用的libtomcrypt库中相关加密代码(自己写头大且没必要)

由于是单个包加密,所以没那么麻烦

加密:

int aes_256_gcm_encrypt(mp_int* key, unsigned char* plain_text, int pt_len, unsigned char* enc_text, unsigned char* IV, int IV_len, unsigned char* tag, unsigned long tag_len)

{

gcm_state gcm;

unsigned char pass[32];

register_cipher(&aes_desc);

mp_to_unsigned_bin(key, pass);

return gcm_memory(find_cipher("aes"), pass, 32, IV, IV_len, NULL, 0, plain_text, pt_len, enc_text, tag, &tag_len, GCM_ENCRYPT);

}

解密:

int aes_256_gcm_decrypt(mp_int* key, unsigned char* plain_text, int pt_len, unsigned char* enc_text, unsigned char* IV, int IV_len, unsigned char* tag, unsigned long tag_len)

{

gcm_state gcm;

register_cipher(&aes_desc);

unsigned char pass[32];

mp_to_unsigned_bin(key, pass);

return gcm_memory(find_cipher("aes"), pass, 32, IV, IV_len, NULL, 0, plain_text, pt_len, enc_text, tag, &tag_len, GCM_DECRYPT);

}

几乎直接调用库函数,这里IV为32字节随机字符,tag为16字节,无需预先赋值

由于解密也需要iv和tag,故这两个参数随着socket一起发过去

memcpy(buf + 4, iv, 32);

memcpy(buf + 36, tag, 16);

if (aes_256_gcm_encrypt(&k, input, len, output, iv, 32, tag, 16) != CRYPT_OK)

{

printf("Error in encrypt!\n");

continue;

}

memcpy(buf + 52, output, len);

2.中间人攻击

(1) 原理介绍

D-H密钥交换能应付简单的截获数据的中间人,即中间人在只截获数据的情况下无法获得密钥

而专门对付D-H的中间人则是建立双向连接

即分别和双方进行秘钥交换建立连接,双方的通信内容会被先加密后解密

计网相关:

当网卡判断目的IP和本机IP在同一个子网时,会将数据包广播出去(目的MAC为目的主机的MAC),否则会直接交给网关(目的MAC为网关)

当本机网卡收到一个数据包时,会判断数据包的目的MAC是不是本机,如果是,保留并有效,否则直接丢弃

ARP攻击的目的则是让通讯的双方都认为目的主机MAC是中间人机器,发送的数据包虽然是广播(即能到达目的主机),但会因为MAC地址不同而被丢弃,反而是中间人机器会正常收到数据包

本次实验模拟的环境是同一子网下的两台主机,即ARP欺骗两台主机即可

如果是不同子网的,则需要欺骗一台主机和网关

(2) 具体实现思路

首先中间人要保证双方不能收到真实数据包,这里采用ARP欺骗的方法

arpspoof -i ens33 -t 192.168.13.128 -r 192.168.13.129

ens33为网卡名称

这里双向投毒(-r)保证双方的数据包都无法正常到达对方

投毒后实现思路如下:

对于握手、挥手包等不具有协议标记(df1)的数据包,将MAC地址直接改为目的MAC地址后发送

对于密钥交换发起数据包(flag=1), 中间人解析源数据包的src_p, src_g, src_x,计算生成src_y, src_key并缓存下来,同时生成dst_p, dst_g, dst_x,修改数据包的数据字段,并发送至目的MAC

对于回应数据包(flag=2), 中间人获得dst_y,并计算出dst_key,同时发送缓存的src_y给客户端

对于常规通讯数据包,中间人根据数据包方向用src_key和dst_key分别对数据包解密,读取,加密,转发,完成通信

(3) 关于校验和

(这里坑了我一下午。。日)

IP校验和只校验IP头,TCP校验和是校验伪头部+整个TCP头+数据字段,如果你修改了数据字段(上思路第二步第三步),不修改校验和的话,发送的数据包会直接(静悄悄地)被当作错误包丢弃

TCP校验和是计算伪首部(源IP,目的IP,0,6,长度)+校验和字段置为0的TCP字段

(代码来自:https://blog.csdn.net/airarts_/article/details/49496579)

C

uint16_t calc_cksm(void *pkt, int len)

{

uint16_t *buf = (uint16_t*)pkt;

uint32_t cksm = 0;

while (len > 1)

{

cksm += *buf++;

cksm = (cksm >> 16) + (cksm & 0xffff);

len -= 2;

}

if (len)

{

cksm += *((uint8_t*)buf);

cksm = (cksm >> 16) + (cksm & 0xffff);

}

return (uint16_t)((~cksm) & 0xffff);

}

这里实现由于未改动IP头,故IP校验和不用改

(4)其它

发包:

我是用libpcap的pcap_sendpacket直接发的包(libnet发包没发出去也没报错,不知为何)

其它的内容就是bug和代码的堆砌,没什么好说的

3. 预共享密钥

来自: https://bbs.csdn.net/topics/370116470

使用 PSK 时,AP 和客户端必须配置相同的密钥或加密密码。AP 发送一个随机字符串到客户端。客户端接受该字符串,根据密钥对其进行加密(或编码),然后发送回 AP。AP 获取加密的字符串,并使用其密钥解密(或解码)。如果从客户收到的字符串在解密后与原来发送给客户端的字符串匹配,就允许该客户端连接。

具体实现:

客户端建立请求时,将数据字段+两个0字节用密钥加密

服务端接受请求并解密,如果最后两个字段为0,便认为客户端验证通过

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值