OPENSSL RSA加密与解密

  最近工作中需要把一些数据用RSA密钥进行加解密,在网上找了一些利用OPENSSL RSA API加解密的代码用来参考,结果都是抄来抄去的,这些代码大多都存在一些问题,甚至还有错误。在自己实现过程中也遇到了一些问题,通过搜索以及在stackoverflow上查找,解决了问题,为此花了不少时间,特此记录下来备用。本文不涉及OPENSSL RSA的算法、原理,只展示下自己的代码以及遇到过问题。

  在编码之前,首先要准备好密钥文件,使用如下命令分别生成公钥和私钥:

  生成私钥:

openssl genrsa -out rsa_private_key.pem 1024

  从私钥中提取公钥:

openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

   有了密钥文件,就可以使用这些密钥来加解密了。本文只示例这些密钥的常用使用方法,也就是公钥加密,私钥解密。代码如下:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <openssl/rsa.h> 
#include <openssl/pem.h> 
#include <openssl/err.h> 
#include <iostream> 
#include <string> 
#include "Base64.h" 
 
std::string RsaEncrypt(const std::string& str, const std::string& path) 
{
    RSA *rsa = NULL; 
    FILE *file = NULL; 
    char *ciphertext = NULL; 
    int len = 0; 
    int ret = 0; 
  
    file = fopen(path.c_str(), "r"); 
    if (file == NULL) { 
        return std::string(); 
    } 
  
    //*注解1
    //rsa = PEM_read_RSAPublicKey(file, NULL, NULL, NULL); 
    rsa = PEM_read_RSA_PUBKEY(file, NULL, NULL, NULL); 
    if (rsa == NULL) { 
        ERR_print_errors_fp(stdout); 
        fclose(file); 
        return std::string(); 
    }
  
    len = RSA_size(rsa); 
    ciphertext = (char *)malloc(len + 1); 
    if (ciphertext == NULL) { 
        RSA_free(rsa); 
        fclose(file); 
        return std::string(); 
    }
    memset(ciphertext, 0, len + 1); 
  
   //*注解2
    ret = RSA_public_encrypt(str.length(), (unsigned char *)str.c_str(), (unsigned char*)ciphertext, rsa, RSA_PKCS1_PADDING); 
    if (ret < 0) { 
        ERR_print_errors_fp(stdout); 
        free(ciphertext); 
        RSA_free(rsa); 
        fclose(file); 
        return std::string(); 
    } 
 
    //*注解3
    //std::string s(ciphertext);    //不能使用这个构造函数,有的密文使用这个构造函数构造出的string会缺失部分数据,导致无法解密 
    std::string s(ciphertext, ret); 
  
    free(ciphertext); 
    RSA_free(rsa); 
    fclose(file); 
  
    return s; 
} 
 
std::string RsaDecrypt(const std::string& str, const std::string& path)  
{ 
    RSA *rsa = NULL; 
    FILE *file = NULL; 
    char *plaintext  = NULL; 
    int len = 0; 
    int ret = 0; 
 
    file = fopen(path.c_str(), "r"); 
    if (file == NULL) { 
        return std::string(); 
    }
 
    //*注解4
    rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL); 
    if (rsa == NULL) {
        ERR_print_errors_fp(stdout); 
        fclose(file); 
        return std::string(); 
    } 
 
    len = RSA_size(rsa); 
    plaintext  = (char *)malloc(len + 1); 
    if (ciphertext == NULL) { 
        RSA_free(rsa); 
        fclose(file); 
        return std::string(); 
    }
    memset(ciphertext, 0, len + 1); 
    
    //*注解5
    ret = RSA_private_decrypt(str.length(), (unsigned char *)str.c_str(), (unsigned char*)plaintext, rsa, RSA_PKCS1_PADDING); 
    if (ret < 0) { 
        ERR_print_errors_fp(stdout);
free(plaintext); RSA_free(rsa); fclose(file); return std::string(); } std::string s(plaintext, ret); free(plaintext); RSA_free(rsa); fclose(file); return s; } int main() { std::string text = "357556062717012 / 09"; std::string path = "rsa_public_key.pem"; std::string encry = RsaEncrypt(text, path); std::string encode = util::base64_encode((const unsigned char*)encry.c_str(), encry.size()); std::cout << encode << std::endl; //std::string s1 = "5K4oD28VpB0H1M/c7PuGeCBqQHXBMZxFWXR8IQL4Kp99rRoHblnIPPg2lIaDkBHi3jSBuMAy+VKU/raznuq338v3WyuDK1fJw/iQx171g1O1xHtGTOfcB8UaqLrxrqRridpuEf9l+diy1dMY8Wq1hdeSGuotb9nh9xSwDwcYMWQ="; std::string s2 = "bh5QCzUXZ0JEZQ1lwnnib8MoDIX6ZLPzbZqMqL1538K/HZFD78sulpI+RsqKrt1xjOocgX2Y1d6GccGyFhRV8vyDKq/gPHNMYbpRYsu0DX+Ul8JzbrVw7UY/eNaeN0yVRhV+vYSbAeWsW/GJA6yyVYLki+BjRTj0d46AC4p9jhk="; std::string path2 = "rsa_private_key.pem"; std::string decry = RsaDecrypt(util::base64_decode(s2), path2); //std::string decry = RsaDecrypt(encry, path2); std::cout << decry << std::endl; return 0; }

 

  编译命令:

g++ test.cpp Base64.cpp -lcrypto   

  代码已经过测试,没问题,但是要注意的是,加密后的密文存在不可打印字符,因此无法直接打印,直接打印都是乱码,上述代码在测试时使用Base64编码后进行打印的(Base64编码函数是从项目代码中拿出来的,这里不展示了),也可以把密文string拆成单个字符按16进制打印。

  接下来看看遇到过的问题,也就是代码中标注“注解x”的地方:

 注解1:

  打开公钥文件后需要对此公钥文件进行解析,有两个可用函数:PEM_read_RSAPublicKey() 和 PEM_read_RSA_PUBKEY(),这两个函数解析的公钥文件是不一样。PEM_read_RSA_PUBKEY()这个函数解析的公钥是这样的:

-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----

 而PEM_read_RSAPublicKey()这个函数解析的公钥文件是这样的:

-----BEGIN RSA PUBLIC KEY-----
...
-----END RSA PUBLIC KEY-----

这两种公钥的开头和结尾标记是不一样的(编码方式估计也不一样,这个就不懂了),不能用错。本文开头的那个提取公钥的命令得到的是第一种公钥,因此应该使用PEM_read_RSA_PUBKEY()这个函数。如果使用了PEM_read_RSAPublicKey()函数,会报错:"Expecting: RSA PUBLIC KEY"。如果一定要使用PEM_read_RSAPublicKey()函数,应该使用如下命令获得其对应的公钥:

openssl rsa -in rsa_private_key.pem -RSAPublicKey_out -out key.pub2

另外,这两种公钥也是可以相互转换的:

//PUBLIC KEY(key.pub1) --> RSA PUBLIC KEY(key.pub2_)
openssl rsa -in key.pub1 -pubin -RSAPublicKey_out -out key.pub2_
//RSA PUBLIC KEY(key.pub2) --> PUBLIC KEY(key.pub1_)
openssl rsa -in key.pub2 -RSAPublicKey_in -pubout -out key.pub1_

 

 注解2:

  加密函数RSA_public_encrypt()第一个参数是待加密的明文的长度,网络上有些代码用的是函数RSA_size(rsa)的返回值,这是错误的,官网对这个函数是有解释的:

int RSA_public_encrypt(int flen, const unsigned char *from,
    unsigned char *to, RSA *rsa, int padding);
    
RSA_public_encrypt() encrypts the flen bytes at from (usually a session key) using the public key rsa and stores the ciphertext in to. to must point to RSA_size(rsa) bytes
of memory.

   此加密函数的返回值是加密后的密文长度。

 

 注解3:

  不能使用下面这个构造函数:

std::string s(ciphertext); 

   有的密文使用这个构造函数构造出的string会缺失部分数据,导致无法解密。一定要把RSA_public_encrypt()函数的返回值传入到string构造函数中。使用上述构造函数构造string导致出错的可能性还是很高的,这个问题困扰了我很久,浪费了不少时间。

 

 注解4:

  不像解析公钥文件FILE* 存在两个可用函数,可能用错,解析私钥只有这一个函数PEM_read_RSAPrivateKey(),这块儿不会有问题,知道就行。

  当然,解析公钥、私钥还有其他的函数,本文只考虑直接读取密钥文件进行解析,其他的解析函数就不会涉及。

 

 注解5:

  类似于注解2,解密函数RSA_private_decrypt()的第一个参数 flen 也是待解密密文的长度,不是RSA_size(rsa)。

 

参考:

https://blog.csdn.net/q610098308/article/details/83015943

https://stackoverflow.com/questions/7818117/why-i-cant-read-openssl-generated-rsa-pub-key-with-pem-read-rsapublickey?r=SearchResults#

posted on 2020-05-19 19:16  泣血  阅读(493)  评论(0编辑  收藏

导航