实现两个主机之间的密钥分发和安全传输
一、设计要求
编写一段程序,实现两个主机之间的密钥分发和安全传输。
要求:
- 用 RSA 算法实现两个主机之间的密钥分发,分发的密钥为 0x 01 23 45 67 或 0x 01 23 45 67 89 AB CD EF;
- 用分发的密钥和 AES 加密算法,实现两个主机之间的加密数据 传输,测试数据是“NPU-SCS”和其他自己构造的 7 条消息;
- 以上 2 个步骤在程序中自动执行完,无手动参与;程序可以在 同一台主机上完成,但数据必须经过网络传输(可以本地发送,本地 接收);
二、设计思路
- 在Linux下通过socket编程实现客户端和服务端的双工通信,从而达到两个主机之间的密钥分发以及加密数据传输。
- 服务端使用RSA算法生成一对公钥和密钥,通过网络传输将公钥发送给服务端,因为私钥在服务端自己手中不需要经过传输所以很安全,由于RSA是非对称加密算法即使公钥被劫持加密的数据也不会有被破译的风险。
- 客户端通过接收网络传输来的公钥加密自己的AES密钥,并用AES密钥加密需要传输的数据得到密文,客户端将RSA公钥加密过的AES密钥和AES密钥加密过的密文发送给服务端。
- 服务端用自己手上的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 指明接收数据缓冲区的大小。
...structsockaddr_in server;structsockaddr_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,(structsockaddr*)&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,(structsockaddr*)&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、主要函数:
intmodpow(longlong a,longlong 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)*/intjacobi(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)return0;elseif(a ==1)return mult;elsereturn0;/* a == n => gcd(a, n) != 1 */}/*检查a是否为n的欧拉见证 */intsolovayPrime(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)之间找一个随机素数*/intrandPrime(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;}}
/*计算最大公约数*/intgcd(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*/intrandExponent(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*/intinverse(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*/intencode(int m,int e,int n){returnmodpow(m, e, n);}/*用私有指数和公共模量解码密码c,m = c^d Mod n*/intdecode(int c,int d,int n){returnmodpow(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;#ifndefMEASUREif(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个操作的最终轮,才算得到密文。
主要函数
voidSubBytes(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];}}
voidShiftRows(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;/*--------------------*/}
voidAddRoundKey(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函数范畴;intxtime(int n)//!> 用来把这个数*0x02;{int temp;
temp=n<<1;if(n&0x80){
temp=temp^0x1b;}return temp;}intmixcolumn(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;}voidMixColumns(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];}elseif(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];}elseif(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);}elseif(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);}}}
voidRound(int* ex_state,int* S_box,int* RoundKey)//轮函数;{SubBytes(ex_state,S_box);ShiftRows(ex_state);MixColumns(ex_state);AddRoundKey(ex_state,RoundKey);}voidFinal_Round(int* ex_state,int* S_box,int* RoundKey)//最后轮函数;{SubBytes(ex_state,S_box);ShiftRows(ex_state);AddRoundKey(ex_state,RoundKey);
voidRotByte(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;}voidKey_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];}}}}voidAes_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传输时造成的内存溢出,但是还没有解决。
版权归原作者 D3sT1ny622 所有, 如有侵权,请联系我们删除。