A和B通信,前提双方都约定好密钥K和哈希函数H。
发送发:
1、A将原始明文M进行哈希运算得到哈希值H(M);
2、再将原始明文M和哈希值H(M)拼接在一起形成新的字符串;
3、最后对这个拼接好的字符串用对称加密的方式加密生成密文C,发从给B。
接受方:
1、B拿到密文C后解密,得到原始明文M和哈希值H(M);
2、对原始明文M进行哈希运算得到新的哈希值H(M)'
3、对比两个新旧哈希值,如果一致说明消息没有被篡改。
如图所示:
完成本例需要用到三个模块,数据通信模块、加解密模块、认证模块。
数据通信模块主要是封装socket,有两个类,一个是数据通信类TcpSocket,另外一个是服务器类TcpServe。参考https://www.subingwen.cn/linux/socket-class/#2-2-1-%E9%80%9A%E4%BF%A1%E7%B1%BB
1、加解密模块
加解密算法采用DES的CBC分组加密模式,DES要求密钥长度64比特(其中有8位是奇偶校验),明文分组64比特。DES算法的OpenSSL接口如下
//@key:长度为8字节的初始密钥; //@shedule:密钥上下文,对初始密钥处理后的结果 int DES_set_key(const_DES_cblock *key, DES_key_schedule *schedule); //@input:输入数据 //@output:输出数据 //@length:加/解密长度 //@schedule:密钥上下文 //@ivec:初始化向量 //@enc:由于加解密的接口一样,由这个参数决定是加密还是解密,DES_ENCRYPT表示加密,DES_DECRYPT表示解密 void DES_ncbc_encrypt(const unsigned char *input, unsigned char *output, long length, DES_key_schedule *schedule, DES_cblock *ivec, int enc);
DES算法的CBC模式要求,原始数据必须是8字节的倍数,可以采用PKCS7对数据进行填充,即如果原始数据需要填充n个字节才对齐,那么填充n个字节,每个字节都是n,如果原始数据本身已经对齐了,则填充一块长度大小为块大小的数据,每个字节都是块大小。如图所示对明文“hello world”进行加密。
xsec.h
#include<string> #include<openssl/des.h> #include<openssl/evp.h> #include<openssl/err.h> enum XsecType { XDES_CBC }; class Xsec { public: //type 加密类型 //pwd 密钥 //is_encrypto true加密 false解密 virtual bool Init(XsecType type, const std::string& pwd, bool is_encrypto); //加解密 virtual int Encrpto(const unsigned char *in, int in_size, unsigned char *out);
protected:
int DES_CBC_Encrypto(const unsigned char *in, int in_size, unsigned char *out); int DES_CBC_DEcrypto(const unsigned char *in, int in_size, unsigned char *out); private: XsecType m_type; //是否是加密 bool m_is_en; int m_blocksize = 0; //DES算法密钥 DES_key_schedule m_ks; //初始化向量 unsigned char m_iv[128] = { 0 }; };
xsec.cpp
#include "Xsec.h" bool Xsec::Init(XsecType type, const std::string& pwd, bool is_encrypto) { m_type = type; m_is_en = is_encrypto; memset(m_iv, 0, sizeof(m_iv));//初始化向量 //这几种算法的密钥长度最多为32字节 unsigned char key[32] = { 0 }; int key_size = pwd.size(); m_blocksize = DES_KEY_SZ; if (key_size > m_blocksize) //密钥过长丢弃,少的补0 key_size = m_blocksize; memcpy(key, pwd.data(), key_size); DES_set_key((const_DES_cblock*)&key, &m_ks); return true; } int Xsec::Encrpto(const unsigned char *in, int in_size, unsigned char *out) { if (m_type == XDES_CBC) { if (m_is_en) //加密 { return DES_CBC_Encrypto(in, in_size, out); } else //解密 { return DES_CBC_DEcrypto(in, in_size, out); } } return 0; } int Xsec::DES_CBC_Encrypto(const unsigned char *in, int in_size, unsigned char *out) { unsigned char pad[8] = { 0 }; int pad_size = m_blocksize - (in_size%m_blocksize); memset(pad, pad_size, sizeof(pad)); //DES_cbc_encrypt(); //block的整数 int size = in_size - (in_size%m_blocksize); DES_ncbc_encrypt(in, out, size, &m_ks, (DES_cblock*)m_iv, DES_ENCRYPT); //补充数据加密 if (in_size%m_blocksize != 0) { memcpy(pad, in + size, (in_size%m_blocksize)); } //加密pad DES_ncbc_encrypt(pad, out + size, sizeof(pad),
&m_ks, (DES_cblock*)m_iv, DES_ENCRYPT); return in_size + pad_size; } //des_cbc 解密 int Xsec::DES_CBC_DEcrypto(const unsigned char *in, int in_size, unsigned char *out) { DES_ncbc_encrypt(in, out, in_size, &m_ks, (DES_cblock*)m_iv, DES_DECRYPT); return in_size - out[in_size-1]; }
2、认证模块
利用单向散列函数验证消息是否被篡改,单向散列函数有如下几个特点
1、单项不可逆,即得到散列值不能推导出原始消息;
2、任意长度的输入,固定长度的输出
3、原始消息改变,散列值改变;
4、抗弱碰撞,对两个不同的原始消息进行哈希运算得到哈希值一定不同。
OpenSSL的EVP接口
//初始化上下文 EVP_MD_CTX *EVP_MD_CTX_new(void); //选择sha512算法 const EVP_MD *EVP_sha512(void); //hash函数的初始化 int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl); //@ctx:上下文 //@d:原始消息 //@cnt:消息长度 int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, size_t cnt); //拿到哈希值 //@md:哈希值 //@s:传入传出参数,哈希值的长度 int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s);
客户端将原始数据M和H(M)拼接起来加密发送给服务器。
client.cpp
#include"TcpServer.h" #include"TcpSocket.h" #include"sec.h" #include<fcntl.h> #include<openssl/evp.h> #include<openssl/sha.h> #include<sys/types.h> #include<sys/stat.h> int main() { // 1. 创建通信的套接字 TcpSocket tcp; // 2. 连接服务器IP port int ret = tcp.connectToHost("127.0.0.1", 10000); if (ret == -1) { return -1; } // 3. 通信 //准备数据 unsigned char data[4096]="this is seckey"; int data_len = strlen((char*)data); //1、生成hash指纹 //1.1初始化上下文 auto ctx= EVP_MD_CTX_new(); auto evp_md = EVP_sha512(); //1.2 hash函数初始化 EVP_DigestInit_ex(ctx, evp_md, NULL); EVP_DigestUpdate(ctx, data, data_len); unsigned char md_hash[64] = { 0 }; unsigned int md_hash_len = 0; char out[1024] = { 0 }; EVP_DigestFinal_ex(ctx, md_hash, &md_hash_len); //将计算完成的哈希值转换成16进制 for (int i = 0; i < SHA512_DIGEST_LENGTH; i++) { sprintf(&out[2 * i], "%02x", md_hash[i]); } cout << "指纹:" << out << endl; //将指纹和原始明文拼接起来 M||H(M) strcat((char*)data,"|"); strcat((char*)data,out); cout<<"拼接好的数据:"<<data<<endl; cout<<"拼接好的数据长度:"<<strlen((char*)data)<<endl; //对拼接好的数据进行加密 unsigned char cipher[4096]={0}; Xsec sec; sec.Init(XDES_CBC, "12345678", true); //指定加密算法和密钥 int size= sec.Encrpto(data, strlen((char*)data), cipher); tcp.sendMsg(string((char *)cipher,size)); sleep(10); return 0; }
服务器接收密文解密,拿到原始明文M做哈希运算生成新的哈希值,将新旧哈希值进行对比。
server.cpp
#include"TcpSocket.h" #include"TcpServer.h" #include"sec.h" #include<openssl/evp.h> #include<openssl/sha.h> #include<pthread.h> #include<string> struct SockInfo { TcpServer* s; TcpSocket* tcp; struct sockaddr_in addr; }; int mystrcmp(char a[],char b[]) { int i=0; while (a[i]!=0&&b[i]!=0) { if(a[i]>b[i]) { return 1; } else if (a[i]<b[i]) { return -1; } i++; } return 0; } void* working(void* arg) { struct SockInfo* pinfo = static_cast<struct SockInfo*>(arg); // 连接建立成功, 打印客户端的IP和端口信息 char ip[32]; printf("客户端的IP: %s, 端口: %d\n", inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(pinfo->addr.sin_port)); // 5. 通信 while (1) { printf("接收数据: .....\n"); string msg = pinfo->tcp->recvMsg(); if (!msg.empty()) { //解密 unsigned char out2[5000] = { 0 }; Xsec sec; sec.Init(XDES_CBC, "12345678", false); int out_size= sec.Encrpto((unsigned char*)msg.data(), msg.size(), out2); cout<<"解密前的数据:"<<msg<<endl; cout << "解密后的数据:"<<out2 << endl << endl << endl; //数据是由原始明文和散列值组成 //1、拿到明文和散列值 char plaintext[1024]={0}; char hashcode[1024]={0}; char *begin=(char*)out2; char*p=strchr((char*)out2,'|'); char *end=p+out_size; strncpy(plaintext,(char*)out2,p-begin); p++; strncpy(hashcode,p,end-p); //cout<<"plaintext="<<plaintext<<endl; //cout<<"hashcode:"<<hashcode<<endl; //2、对明文进行哈希运算得到新的哈希值 //1.1初始化上下文 auto ctx= EVP_MD_CTX_new(); auto evp_md = EVP_sha512(); //1.2 hash函数初始化 EVP_DigestInit_ex(ctx, evp_md, NULL); EVP_DigestUpdate(ctx, plaintext, strlen(plaintext)); unsigned char md_hash[64] = { 0 }; unsigned int md_hash_len = 0; char newhash[1024] = { 0 }; EVP_DigestFinal_ex(ctx, md_hash, &md_hash_len); for (int i = 0; i < SHA512_DIGEST_LENGTH; i++) { sprintf(&newhash[2 * i], "%02x", md_hash[i]); } cout << "新的哈希值:" << newhash<< endl; //3、将新旧哈希值进行比较 int ret= mystrcmp(newhash,hashcode); if(ret==0) { cout<<"此消息没有被篡改!"<<endl; } } else { break; } } delete pinfo->tcp; delete pinfo; return nullptr; } int main() { // 1. 创建监听的套接字 TcpServer s; // 2. 绑定本地的IP port并设置监听 s.setListen(10000); // 3. 阻塞并等待客户端的连接 while (1) { SockInfo* info = new SockInfo; TcpSocket* tcp = s.acceptConn(&info->addr); if (tcp == nullptr) { cout << "重试...." << endl; continue; } // 创建子线程 pthread_t tid; info->s = &s; info->tcp = tcp; pthread_create(&tid, NULL, working, info); pthread_detach(tid); } return 0; }
运行结果
客户端发送消息
服务端接收消息并验证