本文中的模型思路来自于这篇博客: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;
}