最近工作中需要把一些數據用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)。
參考: