实现两个主机之间的密钥分发和安全传输

一、设计要求

编写一段程序,实现两个主机之间的密钥分发和安全传输。
要求:

  1. 用 RSA 算法实现两个主机之间的密钥分发,分发的密钥为 0x 01
    23 45 67 或 0x 01 23 45 67 89 AB CD EF;
  2. 用分发的密钥和 AES 加密算法,实现两个主机之间的加密数据
    传输,测试数据是“NPU-SCS”和其他自己构造的 7 条消息;
  3. 以上 2 个步骤在程序中自动执行完,无手动参与;程序可以在
    同一台主机上完成,但数据必须经过网络传输(可以本地发送,本地
    接收);

二、设计思路

  1. 在Linux下通过socket编程实现客户端和服务端的双工通信,从而达到两个主机之间的密钥分发以及加密数据传输。
  2. 服务端使用RSA算法生成一对公钥和密钥,通过网络传输将公钥发送给服务端,因为私钥在服务端自己手中不需要经过传输所以很安全,由于RSA是非对称加密算法即使公钥被劫持加密的数据也不会有被破译的风险。
  3. 客户端通过接收网络传输来的公钥加密自己的AES密钥,并用AES密钥加密需要传输的数据得到密文,客户端将RSA公钥加密过的AES密钥和AES密钥加密过的密文发送给服务端。
  4. 服务端用自己手上的RSA私钥解密AES密钥,通过解密后得到的AES密钥解密密文,得到最后想要的明文。

流程图:

三、socket编程:

1、实现步骤
TCP 套接字编程中,服务器端实现的步骤:
(1)使用 socket()函数创建套接字;
(2)为创建的套接字绑定到指定的地址结构;
(3)listen()函数设置套接字为监听模式,使服务器进入被动打开的转态;
(4)接受客户端的连接请求,建立连接;
(5)接收、应答客户端的数据请求;
(6)终止连接。
客户器端实现的步骤相对比较简单:
(1)使用 socket()函数创建套接字;
(2)调用 connect 函数建立一个与 TCP 服务器的连接;
(3)发送数据请求,接收服务器的数据应答;
(4)终止连接。
2、流程图

3、主要函数

  • int socket(int family, int type, int protocol);

socket 函数中 family 参数指明协议族。type 参数指明产生套接字的类型。protocol 参数是
协议标志,一般在调用 socket 函数时将其置为 0,但如果是原始套接字,就需要为 protocol
指定一个常值。
该函数如果调用成功,将返回一个小的非负的整数值,它与文件描述符类似,这里称之
为套接字描述符(socket descriptor),简称套接字,之后的 I/O 操作都由该套接字完成。如果
函数调用失败,则返回-1。

  • int bind(int sockfd, const struct sockaddr *server, socklen_len addrlen);

绑定函数的作用就是为调用 socket 函数产生的套接字分配一个本地协议地址,建立地址
与套接字的对应关系。对于网际协议,协议地址包括 32 位的 IPv4 地址或 128 位的 IPv6 地址
和 16 位的 UDP 或 TCP 的端口号。

  • int listen(int sockfd, int backlog);

在调用 listen 函数后,服务器的状态从 CLOSED 转换到了
LISTEN 状态。参数 sockfd 是要设置的描述符。backlog 参数规定了请求队列中的最大连接个数,它对
队列中等待服务请求的数目进行了限制。如果一个服务请求到来时,输入队列已满,该套接
字将拒绝连接请求。

  • int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connect 函数用于激发 TCP 的三路握手过程,建立与远程服务器的连接。参数 sockfd 是
由 socket函数返回的套接字描述符。第二个参数 addr是指向服务器的套接字地址结构的指针,
如果是 IPv4 地址,server 指向的就是一个 sockaddr_in 地址结构,在进行 connect 函数调用时,
必须将 sockaddr_in 结构转换成通用地址结构 sockaddr。最后一个参数 addrlen 是该套接字地
址结构的大小。

  • int accept(int listenfd, struct sockaddr *client, socklen_t *addrlen);

listenfd 参数是由 socket 函数产生的套接字描述符,在调用 accept 函数前,已经调用 listen函数将此套接字变成了监听套接字。client 和 addrlen 参数用来返回连接对方的套接字地址结构和对应的结构长度。这里的 addrlen 是一个值-结果参数,调用前,将 addrlen 指针所指的整 数值置为 client 所指的套接字地址结构的长度。

  • ssize_t send (int sockfd, const void *buf, size_t len, int flags);

参数 sockfd 是套接字描述符。对于服务器是 accept()函数返回的已连接套接字描述符。对于客户端是调用 socket()函数返回的套接字描述符。参数 buf 是指向一个用于发送信息的数据缓冲区。len 指明传送数据缓冲区的大小。

  • ssize_t recv(int sockfd, void *buf ,size_t len, int flags);

参数 sockfd 是套接字描述符。对于服务器是 accept()函数返回的已连接套接字描述符;
对于客户端是调用 socket()函数返回的套接字描述符。参数 buf 是指向一个用于接收信息的数据缓冲区。len 指明接收数据缓冲区的大小。

...
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen;//前面是声明变量/*现在开始创建socket*/
if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{perror("Create socket failed");exit(1);
}
int opt=SO_REUSEADDR;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//地址重用bzero(&server,sizeof(server));/*开始配置socket*/
server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(PORT);/*开始绑定socket*/
if(bind(listenfd,(struct sockaddr*)&server,sizeof(server))==-1)
{perror("bind error");
exit(1);
}/*开始监听*/
if(listen(listenfd,BACKLOG)==-1)
{perror("listen error");exit(1);}
/*接受客户连接*/
addrlen=sizeof(client);
if((connectfd=accept(listenfd,(struct sockaddr*)&client,&addrlen))==-1)
{perror("connect error");exit(1);
}
...

四、RSA算法

1、实现步骤
在本程序中,RSA算法的步骤如下:
1.由服务器端先随机选择两个不相等的质数p和q,选择的质数越大,则越难破解;然后计算p和q的乘积n,n的长度即为密钥长度,在实际应用中,RSA的密钥一般为1024位;
2.计算n的欧拉函数φ(n)=(p-1)*(q-1),随机选择一个整数e,条件是1< e < φ(n),且e与φ(n)互质;
3. 计算e对于φ(n)的模反元素d,所谓“模反元素”就是指有一个整数d,可以使得ed被φ(n)除的余数为1。
ed ≡ 1 (mod φ(n)),即 ed – 1 = k φ(n)
通过扩展欧几里得算法求解得到d的值作为私钥的一部分。
4.将n和e封装成公钥,n和d封装成私钥,将公钥通过网络传输给客户端,实现RSA密钥的分发,客户端得到RSA公钥后,对AES的密钥进行加密传输给服务器端,即用公钥(n,e)算出m^e≡c (mod n)中c的值;服务器利用私钥解密得到AES的密钥,同样也是利用m^e≡c (mod n)算出c的值即为AES的密钥。

2、主要函数

int modpow(long long a, long long b, int c)// 计算a^b mod c
{int ans=1;a=a%c;while(b>0){if(b%2==1)ans=ans*a%c;a=a*a%c;b=b/2;}return ans;
}
/*计算Jacobi符号(a,n)*/
int jacobi(int a, int n) {int twos, temp;int mult = 1;while(a > 1 && a != n) {a = a % n;if(a <= 1 || a == n) break;twos = 0;while(a % 2 == 0 && ++twos) a /= 2; /* Factor out multiples of 2 ,减去2的倍数*/if(twos > 0 && twos % 2 == 1) mult *= (n % 8 == 1 || n % 8 == 7) * 2 - 1;if(a <= 1 || a == n) break;if(n % 4 != 1 && a % 4 != 1) mult *= -1; /* Coefficient for flipping,翻转系数 */temp = a;a = n;n = temp;}if(a == 0) return 0;else if(a == 1) return mult;else return 0; /* a == n => gcd(a, n) != 1 */
}/*检查a是否为n的欧拉见证 */
int solovayPrime(int a, int n) {int x = jacobi(a, n);if(x == -1) x = n - 1;return x != 0 && modpow(a, (n - 1)/2, n) == x;
}
/*在3和(n-1)之间找一个随机素数*/
int randPrime(int n) {int prime = rand() % n;n += n % 2; /* n needs to be even so modulo wrapping preserves oddness */prime += 1 - prime % 2;while(1) {if(probablePrime(prime, ACCURACY)) return prime;prime = (prime + 2) % n;}
}
/*计算最大公约数*/
int gcd(int a, int b) {int temp;while(b != 0) {temp = b;b = a % b;a = temp;}return a;
}
/*在3和n-1之间找到随机指数x,使得gcd(x,phi)=1*/
int randExponent(int phi, int n) {int e = rand() % n;while(1) {if(gcd(e, phi) == 1) return e;e = (e + 1) % n;if(e <= 2) e = 3;}
}
/*用扩展欧几里得法计算n^-1 mod m*/
int inverse(int n, int modulus) {int a = n, b = modulus;int x = 0, y = 1, x0 = 1, y0 = 0, q, temp;while(b != 0) {q = a / b;temp = a % b;a = b;b = temp;temp = x; x = x0 - q * x; x0 = temp;temp = y; y = y0 - q * y; y0 = temp;}if(x0 < 0) x0 += modulus;return x0;
}
/*使用公共指数和模量对消息m进行编码,c = m^e Mod n*/
int encode(int m, int e, int n) {return modpow(m, e, n);
}/*用私有指数和公共模量解码密码c,m = c^d Mod n*/
int decode(int c, int d, int n) {return modpow(c, d, n);
}
/* 使用私钥(指数、模数)解码给定长度的密码)每个加密的数据包应该按照编码消息表示“字节”字符。返回的消息大小为len*字节。*/
int* decodeMessage(int len, int bytes, int* cryptogram, int exponent, int modulus) {int *decoded = (int *)malloc(len * bytes * sizeof(int));int x, i, j;for(i = 0; i < len; i++) {x = decode(cryptogram[i], exponent, modulus);for(j = 0; j < bytes; j++) {decoded[i*bytes + j] = (x >> (7 * j)) % 128;
#ifndef MEASUREif(decoded[i*bytes + j] != '\0') printf("%c", decoded[i*bytes + j]);
#endif}}return decoded;
}

五、AES算法

实现步骤
1.首先AES加密会把明文按128位16字节切成一段一段的数据,如果数据的最后不够16个字节,则用Padding进行填充;
2.对密钥进行密钥扩展,所谓密钥扩展,就是根据初始密钥生成后面的10轮密钥的操作,在AES-128标准中,一共会对每一组明文进行11轮加密,所以AES会通过一个简单快速的混合操作,根据初始密钥依次生成后面10轮的密钥,每一轮的密钥都是依据上面一轮生成的,在这种模式下,每一轮的密钥都作为下一轮密钥的输入对明文进行异或运算3.初始轮:将128位的明文数据与128位的初始密钥进行异或操作;
4.重复轮:将字节混淆、行移位、列混轮、加轮密钥这四个操作重复执行九轮;
将初始轮得到的状态矩阵经过一个置换盒,会输出一个新的矩阵,我们这里叫它为字节混淆矩阵;
对字节混淆矩阵进行行移位,每一行分别向左挪0、1、2、3个字节,然后重新放一下字节,这样行移位就算完成,得到的新矩阵称之为行移位矩阵;
然后用模不可约多项式将每列混乱,得到一个新的矩阵,我们称之为列混乱矩阵;
在每一轮结束的时候,我们需要把列混乱矩阵和下一轮的密钥做一下异或操作,得到一个新的矩阵,我们这里称之为加轮秘钥矩阵;
128位密钥重复轮重复执行9次:上一轮的加轮密钥矩阵就是下一轮的状态矩阵,拿着这个新的状态矩阵返回去,重复执行字节混淆、行移位、列混乱、加轮密钥这四个操作9次,就会进入加密的最终轮了。
5.最终轮:和重复轮的操作差不多,只是在最终轮我们丢弃了列混乱这个操作,因为我们不会再有下一轮了,所以没必要再进行列混乱,再进行的话也加强不了安全性了,只会白白的浪费时间、拖延加密效率。最终轮结束后,我们就算完成了一次AES加密,就会得到一块明文数据的密文。
因此,每执行一次AES加密,其实内部共进行了11轮加密,包括1个初始轮,9个拥有4个操作的重复轮,1个拥有3个操作的最终轮,才算得到密文。

主要函数

void SubBytes(int * ex_state, int * S_box)                //字节代换函数;
{int i,k;for(i=0;i<16;i++){k=ex_state[i];ex_state[i]=S_box[k];}
}
void ShiftRows(int * ex_state)     //行移位函数;
{int k,temp;/*--------------*////第二行;temp=ex_state[4];ex_state[4]=ex_state[5];ex_state[5]=ex_state[6];ex_state[6]=ex_state[7];ex_state[7]=temp;/*-------------------*////第三行;temp=ex_state[8];ex_state[8]=ex_state[10];ex_state[10]=temp;temp=ex_state[9];ex_state[9]=ex_state[11];ex_state[11]=temp;/*-------------------*///第四行;这里的话就需要两个缓存来辅助了;temp=ex_state[13];ex_state[13]=ex_state[12];k=ex_state[14];ex_state[14]=temp;temp=ex_state[15];ex_state[15]=k;ex_state[12]=temp;/*--------------------*/
}
void AddRoundKey(int * ex_state, int * RoundKey)        //密钥加函数;
{int i;for(i=0;i<4;i++){ex_state[i]^=RoundKey[i];ex_state[i+4]^=RoundKey[i+4];ex_state[i+8]^=RoundKey[i+8];ex_state[i+12]^=RoundKey[i+12];}
}
/*----------------------------------------------------*////以下三个函数都属于MixColumns函数范畴;
int xtime(int n)   //!> 用来把这个数*0x02;
{int temp;temp=n<<1;if(n&0x80){temp=temp^0x1b;}return temp;
}int mixcolumn(int m,int n)
{int temp;for(temp=m,m=0;n;n=n>>1){if(n&1){m=m^temp;}temp=xtime(temp);}return m&0xff;
}
void MixColumns(int * ex_state)     //列混合函数;还需要一个mix()函数来帮助这个函数;
{int i;int p_state[16]={0};             //定义一个新的p-state来记录输入的ex_state,以免ex_state发生变化时,p-state还能使用原先的那个ex_state;for(i=0;i<16;i++)p_state[i]=ex_state[i];for(i=0;i<16;i++){if(i>=0&&i<4){ex_state[i]=mixcolumn(ex_state[i],0x02)^mixcolumn(ex_state[i+1*4],0x03)^p_state[i+2*4]^p_state[i+3*4];}else if(i>=4&&i<8){ex_state[i]=p_state[i-4]^mixcolumn(ex_state[i],0x02)^mixcolumn(ex_state[i+1*4],0x03)^p_state[i+2*4]; }else if(i>=8&&i<12){ex_state[i]=p_state[i-2*4]^p_state[i-1*4]^mixcolumn(ex_state[i],0x02)^mixcolumn(ex_state[i+1*4],0x03);}else if(i>=12&&i<16){ex_state[i]=mixcolumn(p_state[i-3*4],0x03)^p_state[i-2*4]^p_state[i-1*4]^mixcolumn(ex_state[i],0x02);}} }
void Round(int * ex_state, int * S_box,int * RoundKey)     //轮函数;
{SubBytes(ex_state,S_box);ShiftRows(ex_state);MixColumns(ex_state);AddRoundKey(ex_state,RoundKey);
}
void Final_Round(int * ex_state, int * S_box,int * RoundKey)     //最后轮函数;
{SubBytes(ex_state,S_box);ShiftRows(ex_state);AddRoundKey(ex_state,RoundKey);

void RotByte(int * temp)     //移位函数(作为Key_Schedule函数的辅助函数)
{int j=0;j=temp[0];         //对它进行移位;temp[0]=temp[1];temp[1]=temp[2];temp[2]=temp[3];temp[3]=j;
}
void Key_Schedule(int * cphkey,int RoundKey[10][16],int * S_box,int * Rcon) //生成轮密钥的函数;
{int i,j,m,n;int temp_first[4]={0};    int temp_last[4]={0};   for(i=0;i<4;i++)          //!>把最后一列的数据赋给temp_last数组;{temp_last[i]=cphkey[i*4+3];}RotByte(temp_last); //移位for(i=0;i<4;i++)   //进行S_box转换;{temp_last[i]=S_box[temp_last[i]];}for(i=0;i<4;i++)    //把第一列的数据赋给temp_first数组;{temp_first[i]=cphkey[i*4];}for(i=0;i<4;i++)    //生成第一轮的RoundKey;{RoundKey[0][i*4]=temp_first[i]^temp_last[i]^Rcon[i*10];  //!>注意Rcon为*10;}for(i=1;i<4;i++){for(j=0;j<4;j++){RoundKey[0][j*4+i]=RoundKey[0][j*4+i-1]^cphkey[j*4+i];}}for(i=1;i<10;i++){for(m=0;m<4;m++)          //把最后一列的数据赋给temp_last数组;{temp_last[m]=RoundKey[i-1][m*4+3];}RotByte(temp_last); //移位;for(m=0;m<4;m++)   //进行S_box转换;{temp_last[m]=S_box[temp_last[m]];}for(m=0;m<4;m++)  {RoundKey[i][m*4]=RoundKey[i-1][m*4]^temp_last[m]^Rcon[m*10+i];}for(j=1;j<4;j++){for(n=0;n<4;n++){RoundKey[i][n*4+j]=RoundKey[i][n*4+j-1]^RoundKey[i-1][n*4+j];}}}
}
void Aes_Encrypt(int * ex_state, int * S_box,int *cphkey,int RoundKey[10][16]) //加密函数;
{int i,j;Key_Schedule(cphkey,RoundKey,S_box,Rcon);AddRoundKey(ex_state, cphkey);for(i=0;i<9;i++){Round(ex_state,S_box,RoundKey[i]);Final_Round(ex_state, S_box,RoundKey[9]);
}

由于AES算法是对称加密算法所以解密过程其实是加密的逆操作,轮密钥加由于是异或过程,两次异或就可以得到原来的值,其他的几个步骤只要按顺序逆推就好了。发送其他信息也是同样的原理只需要改明文数组就好了。

最终结果图如下所示:

五、总结

这次实验对我来说是不小的挑战,考试周和毕业实习让能研究实验的时间少了很多,所幸之前学过网络编程、应用密码学、信息安全数学基础等课程才能把实验做成这样。实验虽然大体做完但是还是有部分内容存在问题,例如经常编译后会出现乱码,怀疑是利用指针和TCP传输时造成的内存溢出,但是还没有解决。

实现两个主机之间的密钥分发和安全传输相关推荐

  1. 基于国密算法实现主机之间的密钥分发和安全传输-2021西北工业大学网络空间安全学院暑期夏令营

    2021年西北工业大学网络空间安全学院暑期夏令营 一.设计内容与要求: 二.基本思路与实现: 三.设计原理与流程: 3.1 总体流程: 3.2 设计原理: 3.2.1 建立套接字连接: 3.2.2 根 ...

  2. Linux 两台主机之间建立信任关系方式及基本原理

    前言: 去年学过一段时间的现代密码学,最近在配置github, Linux主机之间建立信任关系的时候都用到了其中一些知识,所以刚好整理一下,想直接看操作方式的可直接拉到下面 密码学基本知识 一 现代密 ...

  3. 【网络通信与信息安全】之深入解析两台主机之间的通信过程和原理

    一.前言 本文通过在 Docker 容器中执行命令,来深入了解两台主机之间的通信过程.阅读完本文,您将熟悉以下内容: Docker 的基本操作: 创建 socket 并发送 HTTP 请求: 路由表. ...

  4. Linux两台主机之间建立信任关系

    Linux两台主机之间建立信任关系 一般用ssh命令访问另一台机器,或者用scp命令从别的机器拷贝数据和文件,都要输入对应账户的密码.而在两台机器之间建立信任关系,则可以省略输入密码的过程. 一  : ...

  5. #两台主机之间通信时为什么要用IP地址,而不直接用硬件地址?

    两台主机之间通信时为什么要用IP地址,而不直接用硬件地址?   既然在网络链路上传送的数据帧最终是用硬件地址来寻找目的主机,为什么还要用IP地址进行通信,为什么不直接是用硬件地址进行通信?   首先要 ...

  6. C#.NET通过Socket实现平行主机之间网络通讯(含图片传输的Demo演示)

    C#.NET通过Socket实现平行主机之间网络通讯(含图片传输的Demo演示) 作者:一点一滴的Beer http://beer.cnblogs.com/ 在程序设计中,涉及数据存储和数据交换的时候 ...

  7. 测试两个主机之间的连通性_EEG源连通性:旨在实现大脑网络在时间和空间上的高分辨率...

    请点击上面"思影科技"四个字,选择关注我们,思影科技专注于脑影像数据处理,涵盖(fMRI,结构像,DTI,ASL,EEG/ERP,FNIRS,眼动)等,希望专业的内容可以给关注者带 ...

  8. 测试两个主机之间的连通性_网络连通性测试工具—PING

    PING工具是windows系统自带的网络通断测试工具,也是管理员最常用的一款网络通断测试工具.今天桃子科技就带大家一起来看下PING工具如何使用. 主要功能有: 1.测试本地网卡.在计算机无法上网时 ...

  9. 测试两个主机之间的连通性_UCloud 全链路大规模网络连通性检测系统详解

    虚拟网络排查问题困难,传统的 traceroute 等工具很难起到太大作用,大部分情况下都需要到宿主机.混合云网关上抓包来 troubleshooting,耗时又费力.有些场景中包的传送路径比较长(如 ...

最新文章

  1. ios 静态库合成_iOS链接原理解析与应用实践
  2. Matlab 方括号“[ ]”的作用
  3. 绑定线程到特定CPU处理器
  4. 定义一个复数类Complex,重载运算符+
  5. python web开发环境_Flask_Web 开发环境搭建
  6. Vue 第九天学习
  7. 中职专业课教师资格证计算机,中职专业课教师资格证报考科目是什么?
  8. c语言stm32串口控制单片机,实用STM32的串口控制平台的实现
  9. java常用class类_java常用类
  10. vba 指定列后插入列_Vba代码插入指定数量的空白行
  11. linux什么命令查设备型号,在Linux命令行中查看系统硬件制造商、型号与序列号的六种方法...
  12. DataFrame数据转为list,再逐行写入Excel
  13. 单纯学python能干啥_如何高效学习Python编程,转行的朋友可以过来看看,单纯的经验分享...
  14. thinkserver TS250安装centos7.5经验
  15. abaqus土木结构视频教程
  16. 余世伟视频笔记----如何塑造管理者的性格魅力领袖根性之细心和胆识
  17. php orc 验证码,百度图片识别orc实现普通验证码识别
  18. 回归模型+自变量和因变量之间的关系、回归模型的种类、回归模型的输出类型、个数角度
  19. 手机打车APP的机遇与挑战
  20. 【多媒体】多媒体架构

热门文章

  1. rn 函数式组件获取子组件的实例
  2. 薄膜表面瑕疵在线检测系统实时检测出表面瑕疵的具体位置
  3. java-php-python-ssm校园失物招领系统计算机毕业设计
  4. .net程序config文件中特殊字符的正确写法
  5. html增加语音朗读功能,给wordpress主题添加上语音播放文章内容文本朗读功能
  6. 生成百度网盘文件目录_艾孜尔江撰稿
  7. 连接手表_小米手表体验报告(上)
  8. html5 语音输入小话筒,HTML5语音输入方法
  9. 大数据揭秘哪个省是高考地狱?结论和想象不太一样
  10. c语言有哪些系统函数,C语言常用系统函数.doc