使用OpenSSL进行RSA加密和解密(非对称)

1. RSA加密和解密基础概念

    RSA是一种非对称加密。

    RSA秘钥:私钥和公钥,一对私钥和公钥就像夫妻一样是唯一的,用私钥加密后必须用对应的公钥才能解密,用公钥加密后必须用对应的私钥才能解密。

    加密和解密方式:公钥加密-私钥解密,私钥加密-公钥解密

2. 使用OpenSSL库进行RSA加密和解密的基础过程

加密基础过程
       1) 调用OpenSSL库生成秘钥(非必要步骤,如果已经有秘钥对了,就不需要进行这步了)

       2) 调用OpenSSL库对明文进行加密

       2) 对加密后密文进行BASE64转码(非必要步骤,一般开发过程中,为了传输or存贮方便,都会对密文进行BASE64编码)

       注意:OpenSSL的RSA加密接口,每次加密数据的最大长度是有限制的,所以对“较大数据”进行加密,需要循环对“较大数据”分段加密

解密基础过程
       1)对BASE64内容进行BASE64解码

       2) 调用OpenSSL库对密文进行解密

       注意:OpenSSL的RSA解密接口,每次解密数据的最大长度是有限制的,所以对“较大数据”进行解密,需要循环对“较大数据”分段解密

3. 直接上代码

私钥和公钥格式
   说明:C++ OpenSSL中RSA秘钥(公钥和私钥)是有起止标识的,并且每64个字节会有一个换行符(\n),这个可能和其他编程语言(例如Java)是不同的。据我所知Java中秘钥是没有起止标识,只有秘钥内容的,也没有换行符(\n)(个人开发中遇到的情形,也不排除有其他情形的)

   1)私钥格式

        (据我所知)私钥的起止标识只有一种。

        起始标识:-----BEGIN RSA PRIVATE KEY-----
        结束标识:-----END RSA PRIVATE KEY-----

   2)·公钥格式

     (据我所知)公钥的起止标识有两种。

第一种
        起始标识:-----BEGIN RSA PUBLIC KEY-----
        结束标识:-----END RSA PUBLIC KEY-----

第二种
        起始标识:-----BEGIN PUBLIC KEY-----
        结束标识:-----END PUBLIC KEY-----

生成秘钥对(公钥和私钥)

#include 
#include "openssl/rsa.h"
 
#define KEY_LENGTH  2048             // 密钥长度
#define PUB_KEY_FILE "pubkey.pem"    // 公钥路径
#define PRI_KEY_FILE "prikey.pem"    // 私钥路径
 
/*
制造密钥对:私钥和公钥
**/
void GenerateRSAKey(std::string & out_pub_key, std::string & out_pri_key)
{
    size_t pri_len = 0; // 私钥长度
    size_t pub_len = 0; // 公钥长度
    char *pri_key = nullptr; // 私钥
    char *pub_key = nullptr; // 公钥
 
    // 生成密钥对
    RSA *keypair = RSA_generate_key(KEY_LENGTH, RSA_3, NULL, NULL);
 
    BIO *pri = BIO_new(BIO_s_mem());
    BIO *pub = BIO_new(BIO_s_mem());
 
    // 生成私钥
    PEM_write_bio_RSAPrivateKey(pri, keypair, NULL, NULL, 0, NULL, NULL);
    // 注意------生成第1种格式的公钥
    //PEM_write_bio_RSAPublicKey(pub, keypair);
    // 注意------生成第2种格式的公钥(此处代码中使用这种)
    PEM_write_bio_RSA_PUBKEY(pub, keypair);
 
    // 获取长度  
    pri_len = BIO_pending(pri);
    pub_len = BIO_pending(pub);
 
    // 密钥对读取到字符串  
    pri_key = (char *)malloc(pri_len + 1);
    pub_key = (char *)malloc(pub_len + 1);
 
    BIO_read(pri, pri_key, pri_len);
    BIO_read(pub, pub_key, pub_len);
 
    pri_key[pri_len] = '\0';
    pub_key[pub_len] = '\0';
 
    out_pub_key = pub_key;
    out_pri_key = pri_key;
 
    // 将公钥写入文件
    std::ofstream pub_file(PUB_KEY_FILE, std::ios::out);
    if (!pub_file.is_open())
    {
        perror("pub key file open fail:");
        return;
    }
    pub_file << pub_key;
    pub_file.close();
 
    // 将私钥写入文件
    std::ofstream pri_file(PRI_KEY_FILE, std::ios::out);
    if (!pri_file.is_open())
    {
        perror("pri key file open fail:");
        return;
    }
    pri_file << pri_key;
    pri_file.close();
 
    // 释放内存
    RSA_free(keypair);
    BIO_free_all(pub);
    BIO_free_all(pri);
 
    free(pri_key);
    free(pub_key);
}


私钥加密-公钥解密
   1)对长度较短的数据加密和解密(数据长度小于RSA单次处理的最大长度)

/*
@brief : 私钥加密
@para  : clear_text  -[i] 需要进行加密的明文
         pri_key     -[i] 私钥
@return: 加密后的数据
**/
std::string RsaPriEncrypt(const std::string &clear_text, std::string &pri_key)
{
    std::string encrypt_text;
    BIO *keybio = BIO_new_mem_buf((unsigned char *)pri_key.c_str(), -1);
    RSA* rsa = RSA_new();
    rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);
    if (!rsa)
    {
        BIO_free_all(keybio);
        return std::string("");
    }
 
    // 获取RSA单次可以处理的数据的最大长度
    int len = RSA_size(rsa);
 
    // 申请内存:存贮加密后的密文数据
    char *text = new char[len + 1];
    memset(text, 0, len + 1);
 
    // 对数据进行私钥加密(返回值是加密后数据的长度)
    int ret = RSA_private_encrypt(clear_text.length(), (const unsigned char*)clear_text.c_str(), (unsigned char*)text, rsa, RSA_PKCS1_PADDING);
    if (ret >= 0) {
        encrypt_text = std::string(text, ret);
    }
 
    // 释放内存  
    free(text);
    BIO_free_all(keybio);
    RSA_free(rsa);
 
    return encrypt_text;
}
/*
@brief : 公钥解密
@para  : cipher_text -[i] 加密的密文
         pub_key     -[i] 公钥
@return: 解密后的数据
**/
std::string RsaPubDecrypt(const std::string & cipher_text, const std::string & pub_key)
{
    std::string decrypt_text;
    BIO *keybio = BIO_new_mem_buf((unsigned char *)pub_key.c_str(), -1);
    RSA *rsa = RSA_new();
    
    // 注意--------使用第1种格式的公钥进行解密
    //rsa = PEM_read_bio_RSAPublicKey(keybio, &rsa, NULL, NULL);
    // 注意--------使用第2种格式的公钥进行解密(我们使用这种格式作为示例)
    rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa, NULL, NULL);
    if (!rsa)
    {
        unsigned long err= ERR_get_error(); //获取错误号
        char err_msg[1024] = { 0 };
        ERR_error_string(err, err_msg); // 格式:error:errId:库:函数:原因
        printf("err msg: err:%ld, msg:%s\n", err, err_msg);
        BIO_free_all(keybio);
        return decrypt_text;
    }
 
    int len = RSA_size(rsa);
    char *text = new char[len + 1];
    memset(text, 0, len + 1);
    // 对密文进行解密
    int ret = RSA_public_decrypt(cipher_text.length(), (const unsigned char*)cipher_text.c_str(), (unsigned char*)text, rsa, RSA_PKCS1_PADDING);
    if (ret >= 0) {
        decrypt_text.append(std::string(text, ret));
    }
 
    // 释放内存  
    delete text;
    BIO_free_all(keybio);
    RSA_free(rsa);
 
    return decrypt_text;
}


 2)对长度较长的数据加密和解密(数据长度大于RSA单次处理数据块的最大长度)

/*
@brief : 私钥加密
@para  : clear_text  -[i] 需要进行加密的明文
         pri_key     -[i] 私钥
@return: 加密后的数据
**/
std::string RsaPriEncrypt(const std::string &clear_text, std::string &pri_key)
{
    std::string encrypt_text;
    BIO *keybio = BIO_new_mem_buf((unsigned char *)pri_key.c_str(), -1);
    RSA* rsa = RSA_new();
    rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);
    if (!rsa)
    {
        BIO_free_all(keybio);
        return std::string("");
    }
 
    // 获取RSA单次可以处理的数据块的最大长度
    int key_len = RSA_size(rsa);
    int block_len = key_len - 11;    // 因为填充方式为RSA_PKCS1_PADDING, 所以要在key_len基础上减去11
 
    // 申请内存:存贮加密后的密文数据
    char *sub_text = new char[key_len + 1];
    memset(sub_text, 0, key_len + 1);
    int ret = 0;
    int pos = 0;
    std::string sub_str;
    // 对数据进行分段加密(返回值是加密后数据的长度)
    while (pos < clear_text.length()) {
        sub_str = clear_text.substr(pos, block_len);
        memset(sub_text, 0, key_len + 1);
        ret = RSA_private_encrypt(sub_str.length(), (const unsigned char*)sub_str.c_str(), (unsigned char*)sub_text, rsa, RSA_PKCS1_PADDING);
        if (ret >= 0) {
            encrypt_text.append(std::string(sub_text, ret));
        }
        pos += block_len;
    }
    
    // 释放内存  
    delete sub_text;
    BIO_free_all(keybio);
    RSA_free(rsa);
 
    return encrypt_text;
}
/*
@brief : 公钥解密
@para  : cipher_text -[i] 加密的密文
         pub_key     -[i] 公钥
@return: 解密后的数据
**/
std::string RsaPubDecrypt(const std::string & cipher_text, const std::string & pub_key)
{
    std::string decrypt_text;
    BIO *keybio = BIO_new_mem_buf((unsigned char *)pub_key.c_str(), -1);
    RSA* rsa = RSA_new();
    
    // 注意-------使用第1种格式的公钥进行解密
    //rsa = PEM_read_bio_RSAPublicKey(keybio, &rsa, NULL, NULL);
    // 注意-------使用第2种格式的公钥进行解密(我们使用这种格式作为示例)
    rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa, NULL, NULL);
    if (!rsa)
    {
        unsigned long err = ERR_get_error(); //获取错误号
        char err_msg[1024] = { 0 };
        ERR_error_string(ulErr, szErrMsg); // 格式:error:errId:库:函数:原因
        printf("err msg: err:%ld, msg:%s\n", err , err_msg);
        BIO_free_all(keybio);
        
        return decrypt_text;
    }
 
    // 获取RSA单次处理的最大长度
    int len = RSA_size(rsa);
    char *sub_text = new char[len + 1];
    memset(sub_text, 0, len + 1);
    int ret = 0;
    std::string sub_str;
    int pos = 0;
    // 对密文进行分段解密
    while (pos < cipher_text.length()) {
        sub_str = cipher_text.substr(pos, len);
        memset(sub_text, 0, len + 1);
        ret = RSA_public_decrypt(sub_str.length(), (const unsigned char*)sub_str.c_str(), (unsigned char*)sub_text, rsa, RSA_PKCS1_PADDING);
        if (ret >= 0) {
            decrypt_text.append(std::string(sub_text, ret));
            printf("pos:%d, sub: %s\n", pos, sub_text);
            pos += len;
        }
    }
 
    // 释放内存  
    delete sub_text;
    BIO_free_all(keybio);
    RSA_free(rsa);
 
    return decrypt_text;
}


公钥加密-私钥解密
   该部分只对数据块较长的数据的处理为例

/*
@brief : 公钥加密
@para  : clear_text  -[i] 需要进行加密的明文
         pri_key     -[i] 私钥
@return: 加密后的数据
**/
std::string RsaPubEncrypt(const std::string &clear_text, const std::string &pub_key)
{
    std::string encrypt_text;
    BIO *keybio = BIO_new_mem_buf((unsigned char *)pub_key.c_str(), -1);
    RSA* rsa = RSA_new();
    // 注意-----第1种格式的公钥
    //rsa = PEM_read_bio_RSAPublicKey(keybio, &rsa, NULL, NULL);
    // 注意-----第2种格式的公钥(这里以第二种格式为例)
    rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa, NULL, NULL);
 
    // 获取RSA单次可以处理的数据块的最大长度
    int key_len = RSA_size(rsa);
    int block_len = key_len - 11;    // 因为填充方式为RSA_PKCS1_PADDING, 所以要在key_len基础上减去11
 
    // 申请内存:存贮加密后的密文数据
    char *sub_text = new char[key_len + 1];
    memset(sub_text, 0, key_len + 1);
    int ret = 0;
    int pos = 0;
    std::string sub_str;
    // 对数据进行分段加密(返回值是加密后数据的长度)
    while (pos < clear_text.length()) {
        sub_str = clear_text.substr(pos, block_len);
        memset(sub_text, 0, key_len + 1);
        ret = RSA_public_encrypt(sub_str.length(), (const unsigned char*)sub_str.c_str(), (unsigned char*)sub_text, rsa, RSA_PKCS1_PADDING);
        if (ret >= 0) {
            encrypt_text.append(std::string(sub_text, ret));
        }
        pos += block_len;
    }
    
    // 释放内存  
    BIO_free_all(keybio);
    RSA_free(rsa);
    delete[] sub_text;
 
    return encrypt_text;
}
/*
@brief : 私钥解密
@para  : cipher_text -[i] 加密的密文
         pub_key     -[i] 公钥
@return: 解密后的数据
**/
std::string RsaPriDecrypt(const std::string &cipher_text, const std::string &pri_key)
{
    std::string decrypt_text;
    RSA *rsa = RSA_new();
    BIO *keybio;
    keybio = BIO_new_mem_buf((unsigned char *)pri_key.c_str(), -1);
 
    rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);
    if (rsa == nullptr) {
        unsigned long err = ERR_get_error(); //获取错误号
        char err_msg[1024] = { 0 };
        ERR_error_string(err, err_msg); // 格式:error:errId:库:函数:原因
        printf("err msg: err:%ld, msg:%s\n", err, err_msg);
        return std::string();
    }
 
    // 获取RSA单次处理的最大长度
    int key_len = RSA_size(rsa);
    char *sub_text = new char[key_len + 1];
    memset(sub_text, 0, key_len + 1);
    int ret = 0;
    std::string sub_str;
    int pos = 0;
    // 对密文进行分段解密
    while (pos < cipher_text.length()) {
        sub_str = cipher_text.substr(pos, key_len);
        memset(sub_text, 0, key_len + 1);
        ret = RSA_private_decrypt(sub_str.length(), (const unsigned char*)sub_str.c_str(), (unsigned char*)sub_text, rsa, RSA_PKCS1_PADDING);
        if (ret >= 0) {
            decrypt_text.append(std::string(sub_text, ret));
            printf("pos:%d, sub: %s\n", pos, sub_text);
            pos += key_len;
        }
    }
    // 释放内存  
    delete[] sub_text;
    BIO_free_all(keybio);
    RSA_free(rsa);
 
    return decrypt_text;
}


测试代码

int main()
{
    // 原始明文  
    std::string src_text = "test begin\n this is an rsa test example!!! this is an rsa test example!!! this is an rsa test example!!! this is an rsa test example!!! this is an rsa test example!!! this is an rsa test example!!! this is an rsa test example!!! this is an rsa test example!!! this is an rsa test example!!! this is an rsa test example!!! this is an rsa test example!!! this is an rsa test example!!! this is an rsa test example!!! \ntest end";
    //src_text = "rsa test";
 
    std::string encrypt_text;
    std::string decrypt_text;
 
    // 生成密钥对
    std::string pub_key;
    std::string pri_key;
    OpensslTool::GenerateRSAKey(pub_key, pri_key);
    printf("public key:\n");
    printf("%s\n", pub_key.c_str());
    printf("private key:\n");
    printf("%s\n", pri_key.c_str());
 
    // 私钥加密-公钥解密
    encrypt_text = OpensslTool::RsaPriEncrypt(src_text, pri_key);
    printf("encrypt: len=%d\n", encrypt_text.length());
    decrypt_text = OpensslTool::RsaPubDecrypt(encrypt_text, pub_key);
    printf("decrypt: len=%d\n", decrypt_text.length());
    printf("decrypt: %s\n", decrypt_text.c_str());
 
    // 公钥加密-私钥解密
    encrypt_text = OpensslTool::RsaPubEncrypt(src_text, pub_key);
    printf("encrypt: len=%d\n", encrypt_text.length());
    decrypt_text = OpensslTool::RsaPriDecrypt(encrypt_text, pri_key);
    printf("decrypt: len=%d\n", decrypt_text.length());
    printf("decrypt: %s\n", decrypt_text.c_str());
 
    return 0;
}


4. 总结
1)单次加密数据的最大长度(block_len),由RSA秘钥模长RSA_size()和填充模式有关

      A. 填充模式:RSA_PKCS1_PADDING, block_len=RSA_size() - 11

      B. 填充模式:RSA_PKCS1_OAEP_PADDING,block_len=RSA_size() - 41

      C. 填充模式:RSA_NO_PADDING(不填充),block_len=RSA_size()

      调用加密接口时,如果传入的加密数据的长度大于block_len,则加密接口将返回错误

2)公钥的使用(公钥和公钥加密解密接口),公钥的类型 和 生成公钥RSA对象指针的接口必须是一一对应的,否则将操作失败

      A. 第1种格式的公钥(-----BEGIN RSA PUBLIC KEY----- / -----END RSA PUBLIC KEY-----),对应的接口如下:

          生成公钥:PEM_write_bio_RSAPublicKey()

          生成公钥RSA对象指针:PEM_read_bio_RSAPublicKey()

      B. 第2种格式的公钥(-----BEGIN PUBLIC KEY----- / -----END PUBLIC KEY-----),对应的接口如下:

          生成公钥:PEM_write_bio_RSA_PUBKEY()

          生成公钥RSA对象指针:PEM_read_bio_RSA_PUBKEY()

3) 在使用其他编程语言生成的RSA秘钥对时,可能(同时)遇到以下问题(本人在使用java生成的公钥时全部遇到了,血的经验……):

      A. 秘钥对只有“秘钥内容”,而没有“秘钥起止标识”;(公钥+私钥都可能遇到)

      B. 秘钥内容没有换行符(\n);(公钥可能会遇到)

      遇到这个问题,请不要紧张,自己写个函数对“秘钥内容”包装一下,加上“起止标识”和换行符(\n)。对于“起止标识”,一种格式不行就换另一种试试,本人遇到的就是需要使用第二种“起止标识”的,代码请参考:

#define RSA_KEYSUB_LEN    64
 
std::string iifs::IIRSA::FormatPubKey(const std::string & key)
{
    std::string pub_key = "-----BEGIN PUBLIC KEY-----\n";
    
    auto pos = key.length();
    pos = 0;
    while (pos < key.length()) {
        pub_key.append(key.substr(pos, RSA_KEYSUB_LEN));
        pub_key.append("\n");
        pos += RSA_KEYSUB_LEN;
    }
 
    pub_key.append("-----END PUBLIC KEY-----");
    return pub_key;
}


5. 贴上一对秘钥(起止标识结束位置有换行,每64字节有换行)
/* 公钥 */
-----BEGIN RSA PUBLIC KEY-----
MIIBCAKCAQEAwDpa2EOEu7vCx88mVXzLHxdw1Yn0Hm7gkUEdnzdXzPenbL4NVLoj
6lxtX2UQ10wZLQfHuv5elaBn9jkCxpZgb9zkD3hKqmVLJI6HXRi2OECEnOp0Edus
crCr3N/FXvf7ALT4i9LeUlfmUmTB+kIR7VpyPY2Pg88DvA5n7QXQRRqRu8d3NBXF
ZhB9nZd8Zb8XyTStKLwwd11e7JV4vUTiA3heoNnSsBEeLN1DWdQNjCqq35AFP2yd
M1pncM4Zjhbyv0z0l776vCJyS4/iS78qund4i1dxp2l4gDoDuTd4Nck6oN/HC9Ba
nuIqrv/RP0Sw01Dij7g59nVxYA1aN8cvxQIBAw==
-----END RSA PUBLIC KEY-----
/* 私钥 */
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwDpa2EOEu7vCx88mVXzLHxdw1Yn0Hm7gkUEdnzdXzPenbL4N
VLoj6lxtX2UQ10wZLQfHuv5elaBn9jkCxpZgb9zkD3hKqmVLJI6HXRi2OECEnOp0
EduscrCr3N/FXvf7ALT4i9LeUlfmUmTB+kIR7VpyPY2Pg88DvA5n7QXQRRqRu8d3
NBXFZhB9nZd8Zb8XyTStKLwwd11e7JV4vUTiA3heoNnSsBEeLN1DWdQNjCqq35AF
P2ydM1pncM4Zjhbyv0z0l776vCJyS4/iS78qund4i1dxp2l4gDoDuTd4Nck6oN/H
C9BanuIqrv/RP0Sw01Dij7g59nVxYA1aN8cvxQIBAwKCAQEAgCbnOtet0n0shTTE
OP3cv2T147FNaZ9AYNYTv3o6iKUaSH6zjdFtRuhI6kNgj4gQyK/afKmUY8BFTtCs
hGRASpNCtPrccZjcwwmvk2XO0CsDE0b4C+fITHXH6JUuP0/8qyNQXTc+4Y/u4ZiB
UYFhSOb207O1AooCfV7v81k1g2XkBlZqUGXBCYoo7ec5X1PoCImTHrlbwTLOaA6h
GIr0HgrsvLMVjNNvzx8p7v18jBRzolkDa3Ch3dp61QHQ9Z3lehpXByTplLHxCNDp
fa8KtmdmQVZSKsosjBoMvtxHtEEpAhhAAL+6HtlQ67Y4LHsfLtIDk/PKjMNKyeht
mDKV8wKBgQDgWdxBbLw30cos87Tk98JlWYmkuo6YtVQL0qyj4J6jFnH2LTMSiJPP
2ETcUXt2rkd5Zs6NCNQEhHifgbmvIOILqD7txNGRIDksVf5UKRC5X3+90RbQff/x
XLLzNqKIEhIrdgNQzptoTpJyj5Llzw5Sj1f0MtnKAomW4l3zmuf2BwKBgQDbWGmW
TsDsBfcTRQfBXv7WYtyrwBeOID0dfdLjN9XQv/YFWJof1EAmnemoIdxcC8SEBTvz
FW+l4hoPr5Gw/MgO3+aESDYLPN5caFgv5ifhSVyhWD8l6TpEUV/9ZEqElVVRp7gW
PBVbIgm+vduXLX2vfb3o/vDAIMbqTtLCOJNY0wKBgQCVkT2A8yglNobIoniYpSxD
kQZt0bRlzjgH4chtQGnCDvakHiIMWw01OtiS4Pz5yYT7md8IsI1YWFBqVnvKFewH
xX9JLeELatDIOVQ4G2B7lP/T4LngU//2PcyiJGxatrbHpAI13xJFibb3CmHuigmM
X4/4IeaGrFu57D6iZ0VOrwKBgQCSOvEO3ytIA/oM2K/WP1SO7JMdKrpewCi+U+Hs
z+Pgf/lY5bwVOCrEaUZwFpLoB9hYA31MuPUZQWa1H7Z1/dq0lURYMCQHfemS8DrK
mW/rhj3A5X9um3wti5VTmDGtuOOLxSVkKA48wVvUfpJkyP50/n6bVKCAFdnxieHW
0GI7NwKBgCNVncsGnj/sIwoTJ62udRTxKW5VxtmUmPcDlG05qfjCB/itrJmPC5nv
Pmzq2doZXlu9SCqTN/tEgeyJ8PGBGJFDS03T42VnjuNu4Eravbmgm4AJYLdxip4O
oCF6GkBGNYJaCCdcPHQnouW5cvTILlAvJVYn99w0Vei/VmajwCIZ
-----END RSA PRIVATE KEY-----

其他链接:https://www.jianshu.com/p/011303dc9429

你可能感兴趣的:(安全,C/C++,c++,加密解密)