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#


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM