本文中的模型思路来自于这篇博客:https://www.cnblogs.com/sheng1255blog/p/5121536.html
基本思路:采用安全的RSA加密算法来对生成AES密钥对的种子进行加密传输。通信双方得到种子Seed之后分别生成AES加密密钥和AES解密密钥,此后就可以用AES对socket传输的数据进行加密传输了。
这样做的理由:RSA加密算法安全性高,但是加密效率低,加密速度慢,一般只用来加密短小的数据,不会直接加密大量的数据。AES加密速度快,加密效率高,但是由于是对称密钥密码体制,安全性没有RSA好,详情可见我之前的博客。所以采用RSA+AES的方法,利用了两者的优点,https据说也是采用这种做法。此外AES加密数据一般是固定长度的,所以需要自己实现分块加密,我这里封装了一下,方便使用。
客户端代码:safe_client.cpp
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <assert.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <pthread.h> #include <fcntl.h> #include <openssl/rsa.h> #include <openssl/aes.h> #include "myaes.h" #define SER_PORT 6000 #define SER_IP "127.0.0.1" using namespace std; int main() { //客户端创建流式套接字 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1) { return -1; } //绑定服务端IP和端口 struct sockaddr_in saddr; memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(SER_PORT); saddr.sin_addr.s_addr = inet_addr(SER_IP); int res=connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)); assert(res != -1); //产生RSA密钥对 RSA* rsa = RSA_new(); BIGNUM* bne = BN_new(); //随机产生一个大数 int ret = 0; ret = BN_set_word(bne, RSA_F4); ret = RSA_generate_key_ex(rsa, 128, bne, NULL); // 客户端从RSA结构中提取公钥到BUFF,以便将它传输给对方 // 128位的RSA其公钥提出出来长度是26字节,而私钥提取出来有16字节 // 保险起见,给它们预留一个128字节的空间 unsigned char PublicKey[128]; unsigned char *PKey = PublicKey; // 注意这个指针不是多余,是特意要这样做的, int PublicKeyLen = i2d_RSAPublicKey(rsa, &PKey); //把公钥发送给服务器 send(sockfd, PublicKey, 128, 0); //接收服务器发来的Seed结构 这里的AES_BLOCK_SIZE宏是aes.h里定义的 大小为16 unsigned char Seed[AES_BLOCK_SIZE], EncryptedSeed[AES_BLOCK_SIZE]; memset(Seed, '\0', AES_BLOCK_SIZE); memset(EncryptedSeed, '\0', AES_BLOCK_SIZE); printf("recv Seed\n"); recv(sockfd, EncryptedSeed, sizeof(EncryptedSeed), 0); //对EncryptedSeed用RSA私钥进行解密。这里得到了Seed int rsa_len = RSA_size(rsa); RSA_private_decrypt(AES_BLOCK_SIZE, (unsigned char*)EncryptedSeed, (unsigned char*)Seed, rsa, RSA_NO_PADDING); // 解密 // 逐个字节打印解密后的Seed信息 和服务器发送过来的Seed进行对比 /*printf("Seed after decrypt, Len=%ld\n", sizeof(Seed)); for (int i = 0; i < sizeof(Seed); i++) { printf("0x%d, ", *(Seed+i)); } printf("\n");*/ //这里用封装过的AES加密算法进行加解密 BobAES a(Seed); string str = "ssssssssssssssssssssssss"; string EncryptedData = a.aes_encrypt(str); send(sockfd, EncryptedData.c_str(), strlen(EncryptedData.c_str()), 0); string Data = a.aes_decrypt(EncryptedData); cout << "date after decrypt:" << Data << endl; //最后要释放RSA结构 AES是栈内存,系统自动管理 RSA_free(rsa); //RSA_free(EncryptRsa); //这个是给安全通信对象释放的 从buff里面提取出来的公钥 return 0; }
服务端代码:safe_server.cpp
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <assert.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <pthread.h> #include <fcntl.h> #include <sys/wait.h> #include <sys/sendfile.h> #include <openssl/rsa.h> #include <openssl/aes.h> #include "myaes.h" #define SER_PORT 6000 #define SER_IP "127.0.0.1" using namespace std; int main() { int sockfd=socket(AF_INET, SOCK_STREAM, 0); if(sockfd == -1) { return -1; } struct sockaddr_in saddr; memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(SER_PORT); saddr.sin_addr.s_addr=inet_addr(SER_IP); int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); if(res== -1) { return -1; } listen(sockfd, 5); struct sockaddr_in caddr; socklen_t len = sizeof(caddr); int clientfd = accept(sockfd, (struct sockaddr*)&caddr, &len); //首先接受客户端发送过来的RSA公钥信息 unsigned char buff_rsa[512]; memset(buff_rsa, '\0', sizeof(buff_rsa)); int n = recv(clientfd, buff_rsa, 512, 0); // 逐个字节打印PublicKey信息 看看接收是否正确 printf("PublicKeyBuff, Len=%d\n", 74); for (int i = 0; i < 74; i++) { printf("0x%02x, ", *(buff_rsa+i)); } printf("\n"); //跟据上面提出的公钥信息PublicKey构造一个新RSA密钥(这个密钥结构只有公钥信息,用来对Seed进行加密) unsigned char *PKey = buff_rsa; //int PublicKeyLen = strlen((char*)buff_rsa); RSA *EncryptRsa = d2i_RSAPublicKey(NULL, (const unsigned char**)&PKey, 26); // 使用EncryptRsa加密数据,再使用ClientRsa解密数据 // 注意, RSA加密/解密的数据长度是有限制,例如128位的RSA就只能最多能加密解密16字节的数据 8:1的关系 // 如果采用RSA_NO_PADDING加密方式,128位的RSA就只能加密长度等于16的数据 // 这个长度可以使用RSA_size()来获得 unsigned char Seed[AES_BLOCK_SIZE], EncryptedSeed[AES_BLOCK_SIZE]; //这里给Seed做一下随机 取1-100之间的数 for(int i = 0; i < AES_BLOCK_SIZE; i++) { Seed[i] = rand() % 100 + 1; } memset(EncryptedSeed, '\0', AES_BLOCK_SIZE); // 逐个字节打印Seed信息 对比发送和接收是否正确 printf("Seed, Len=%ld\n", sizeof(Seed)); for (int i = 0; i < AES_BLOCK_SIZE; i++) { printf("0x%d, ", *(Seed+i)); } printf("\n"); RSA_public_encrypt(AES_BLOCK_SIZE, (unsigned char*)Seed, (unsigned char*)EncryptedSeed, EncryptRsa, RSA_NO_PADDING); //这里再把加密后的Seed(EncryptedSeed)发送到客户端 send(clientfd, EncryptedSeed, AES_BLOCK_SIZE, 0); //利用Seed结构生成一个AES解密密钥 //使用封装后的AES进行数据解密 BobAES a(Seed); char EncryptedData[256]; recv(clientfd, EncryptedData, sizeof(EncryptedData), 0); cout << "encryptedata:" << EncryptedData << endl; std::string str(EncryptedData); std::string Data = a.aes_decrypt(str); //输出解密之后的数据 std::cout << "data after decrypt:" << Data << std::endl; //最后释放RSA结构 RSA_free(EncryptRsa); return 0; }
Makefile:
safe_client safe_server: safe_client.cpp safe_server.cpp myaes.cpp g++ -o safe_client safe_client.cpp myaes.cpp -L/usr/local/lib -lssl -lcrypto -lpthread g++ -o safe_server safe_server.cpp myaes.cpp -L/usr/local/lib -lssl -lcrypto -lpthread clean: -rm -f safe_client safe_server
注意使用的openssl需要自行安装,将使用到的库 libcrypto, libssl放在对应的工作目录中即可。
对AES的分块加密封装:
myaes.h:
#ifndef bob_aes_h #define bob_aes_h #include <iostream> #include <stdio.h> #include <string> #include <string.h> #include <cstdlib> #include <openssl/aes.h> class BobAES { public: BobAES(unsigned char* Seed); ~BobAES(); std::string aes_encrypt(std::string msg); std::string aes_decrypt(std::string msg); private: int MSG_LEN; unsigned char key[AES_BLOCK_SIZE]; }; #endif
myaes.cpp:
#include "myaes.h" BobAES::BobAES(unsigned char* Seed) : MSG_LEN(0) { for(int i = 0; i < AES_BLOCK_SIZE; i++) { key[i] = Seed[i]; } } BobAES::~BobAES() { } std::string BobAES::aes_encrypt(std::string msg) { int i = msg.size() / 1024; MSG_LEN = ( i + 1 ) * 1024; //MSG_LEN = msg.size() + 16; char in[MSG_LEN]; char out[MSG_LEN+16]; memset((char*)in,0,MSG_LEN); memset((char*)out,0,MSG_LEN+16); strncpy((char*)in,msg.c_str(),msg.size()); unsigned char iv[AES_BLOCK_SIZE]; //加密的初始化向量 for(int j = 0; j < AES_BLOCK_SIZE; ++j) { iv[j] = 0; } AES_KEY aes; if(AES_set_encrypt_key(key, 128, &aes) < 0) { return NULL; } int len = msg.size(); AES_cbc_encrypt((unsigned char*)in,(unsigned char*)out,len,&aes,iv,AES_ENCRYPT); std::string encrypt_msg(&out[0],&out[MSG_LEN+16]); for(int i= 0;out[i];i++){ printf("%x",(unsigned char)out[i]); //std::cout << dstStringTemp[i]; } std::cout << std::endl; return encrypt_msg; } std::string BobAES::aes_decrypt(std::string msg) { MSG_LEN = msg.size(); char in[MSG_LEN]; char out[MSG_LEN+16]; memset((char*)in,0,MSG_LEN); memset((char*)out,0,MSG_LEN+16); strncpy((char*)in,msg.c_str(),msg.size()); for(int i= 0;in[i];i++){ printf("%x",(unsigned char)in[i]); //std::cout << dstStringTemp[i]; } std::cout << std::endl; unsigned char iv[AES_BLOCK_SIZE]; //加密的初始化向量 for(int j = 0; j < AES_BLOCK_SIZE; ++j) { iv[j] = 0; } AES_KEY aes; if(AES_set_decrypt_key(key, 128, &aes) < 0) { return NULL; } int len = msg.size(); AES_cbc_encrypt((unsigned char*)in,(unsigned char*)out,len,&aes,iv,AES_DECRYPT); std::string decrypt_msg = out; return decrypt_msg; }