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)。
参考: