使用 openssl 進行 RSA 加解密(C++)

 

 

一. 生成密鑰對

在 OPENSSL 中, RSA 是一個很重要的結構體。它的定義在 rsa_locl.h 中,麪包含了在原理中提到的所有重要的變量 隨機質數 p, q, 公鑰指數 e, 私鑰指數 d, 以及模數 n

struct rsa_st {
 // ...
    BIGNUM *n;
    BIGNUM *e;
    BIGNUM *d;
    BIGNUM *p;
    BIGNUM *q;
 // ...
};

生成密鑰函數:


int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb);

 

 

 

  • bits 密鑰的規模(modulus)。小於 1028 位的密鑰是不安全的,小於 512 則會返回 0
  • e 公開的指數。它應該是一個奇數(odd number), 一般是 3, 1765537
  • cb 生成大隨機數的回調函數。一般使用 NULL 即可, 默認爲 BN_GENCB_call()

生成公私鑰並寫入文件的代碼示例:

#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/bn.h>
#include <openssl/pem.h>
#include <openssl/applink.c> //important!
 
void gen_rsa_key() {
    RSA *rsa = RSA_new();
    BIGNUM *e = BN_new();
    BN_set_word(e, 17);
    BN_GENCB* gencb = NULL;
    EVP_PKEY* pkey = EVP_PKEY_new();
    int rst = RSA_generate_key_ex(rsa, 3072, e, gencb);
    rst = EVP_PKEY_set1_RSA(pkey, rsa);
 
    FILE* f_pri = fopen("pri.key", "wb");
    FILE* f_pub = fopen("pub.pem", "wb");
 
    rst = PEM_write_RSAPublicKey(f_pub, rsa);
    rst = PEM_write_RSAPrivateKey(f_pri, rsa, 0, 0, 0, 0, 0);
 
    fclose(f_pri);
    fclose(f_pub);
    f_pri = NULL;
    f_pub = NULL;
 
    RSA_free(rsa);
    BN_free(e);
    EVP_PKEY_free(pkey);
    BN_GENCB_free(gencb);
}

 

 

 

二. 公私鑰加解密

公私鑰加解密兩個步驟, 一是載入密鑰, 二是加解密。
載入密鑰使用 API :

PEM_read_RSAPublicKey()
PEM_read_RSAPrivateKey()
PEM_read_bio_RSAPrivateKey()
PEM_read_bio_RSAPublicKey()

 

當載入失敗時返回 NULL。

而加解密中兩個重要的 API 是:

int RSA_public_encrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding);
int RSA_private_decrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding);

 

 

 

  • flen 是輸入數據的長度,必須小於等於 modulus = RSA_size(rsa) 個字節, 且和參數 padding 相關。當 flen 不滿足要求時,將會出現 "data too small for key size""data too large for key size" 錯誤。 flen 與 padding 的對應關係如下:
    • RSA_PKCS1_PADDING , flen <= modulus – 11
    • RSA_SSLV23_PADDING , flen <= modulus – 11
    • RSA_NO_PADDING , flen = modulus
    • RSA_PKCS1_OAEP_PADDING , flen <= modulus – 41 (2 倍的 SHA1 長度 + 1)
  • padding 是填充模式,當 flen 滿足上述關係時,將會進行填充:
    • RSA_PKCS1_PADDING , 最常用的模式,使用 PKCS #1 v1.5 標準,前兩字節填充 0x00, 0x02,接着的 modulus - flen - 3 字節使用 隨機非 '\0' 值填充,接着填充一個字節的 0x00, 然後是 from 數據
    • RSA_SSLV23_PADDING , 前兩個字節填充 0x00,0x02 , 接着的 modulus - flen - 3 - 8 字節填充 隨機非 '\0' 值,然後填充 8 個 0x03, 接着填充一個字節的 0x00 ,然後是 from 數據
    • RSA_NO_PADDING , 不需要填充
    • RSA_PKCS1_OAEP_PADDING , 前20字節使用 SHA1 填充, 接着填充至少 20 字節的 0x00, 最後填充 0x01,接着是 from 數據

如圖所示:

公私鑰加解密示例:

#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/bn.h>
#include <openssl/pem.h>
#include <openssl/applink.c>  //important!
 
 
const char* pri_key_path = "pri.key";
const char* pub_key_path = "pub.pem";
 
const char *plain_text = "The EVP interface supports the ability to perform authenticated encryption and decryption, as well as the option to attach unencrypted,"
" associated data to the message. Such Authenticated-Encryption with Associated-Data (AEAD) schemes provide confidentiality by encrypting the data, and also"
" provide authenticity assurances by creating a MAC tag over the encrypted data. The MAC tag will ensure the data is not accidentally altered or maliciously"
" tampered during transmission and storage.";
 
bool pub_encrypt(const char* pub_key_path, const unsigned char* in, int in_len, unsigned char*& out, int& out_len) {
    bool ret = false;
    RSA* rsa = RSA_new();
    FILE* f = fopen(pub_key_path, "r");
    if (!f) goto ERR;
 
    rsa = PEM_read_RSAPublicKey(f, &rsa, 0, 0);
    if (!rsa) goto ERR;
    fclose(f);
    f = NULL;
 
    out_len = RSA_size(rsa);
    if (in_len > out_len) goto ERR;
 
    out = (unsigned char*)malloc(out_len);
 
    int padding = RSA_PKCS1_PADDING;
    int rst = RSA_public_encrypt(in_len, in, out, rsa, padding);
    if (rst <= 0) {
        free(out);
        out = NULL;
        printf("%s\n", ERR_reason_error_string(ERR_get_error()));
        goto ERR;
    }
 
    ret = true;
 
ERR:
    if(rsa)
        RSA_free(rsa);
 
    return ret;
}
 
bool pri_decrypt(const char* pri_key_path, const unsigned char* in, int in_len, unsigned char*& out, int& out_len) {
    bool ret = false;
    RSA* rsa = RSA_new();
    FILE* f = fopen(pri_key_path, "r");
    if (!f) goto ERR;
 
    rsa = PEM_read_RSAPrivateKey(f, &rsa, 0, 0);
    if (!rsa) goto ERR;
 
    fclose(f);
    f = NULL;
 
    out_len = RSA_size(rsa);
    if (in_len > out_len) goto ERR;
 
    out = (unsigned char*)malloc(out_len);
 
    int padding = RSA_PKCS1_PADDING;
    int rst = RSA_private_decrypt(in_len, in, out, rsa, padding);
    if (rst < 0) {
        free(out);
        out = NULL;
        printf("%s", ERR_reason_error_string(ERR_get_error()));
        goto ERR;
    }
 
    out_len = rst;
    ret = true;
 
ERR:
    if (rsa)
        RSA_free(rsa);
 
    return ret;
}
 
int main() {
    //init_openssl();
    unsigned char* cipher_text = NULL, *re_plain_text = NULL;
    int enc_out_len = 0, dec_out_len;
    bool rst = pub_encrypt(pub_key_path, reinterpret_cast<const unsigned char*>(plain_text), strlen(plain_text), cipher_text, enc_out_len);
    assert(rst);
 
    rst = pri_decrypt(pri_key_path, cipher_text, enc_out_len, re_plain_text, dec_out_len);
    assert(rst);
 
    assert(strlen(plain_text) == dec_out_len);
    int cmp = memcmp(plain_text, re_plain_text, dec_out_len);
    assert(cmp == 0);
 
    free(cipher_text);
    free(re_plain_text);
 
    //clean_openssl();
    return 0;
}

 

三. Tips

多半是因爲代碼運行庫不匹配導致的。在Windows 中,運行時庫有這幾種:

  • Single Threaded /ML – MS VC++ often defaults to this for the release version of a new project.
  • Debug Single Threaded /MLd – MS VC++ often defaults to this for the debug version of a new project.
  • Multithreaded/MT
  • Debug Multithreaded /MTd
  • Multithreaded DLL /MD – OpenSSL defaults to this.
  • Debug Multithreaded DLL /MDd

運行庫不一致可能導致程序崩潰(通常是在進行IO操作時)。這種情況下可以重新編譯 OPENSSL, 或者修改程序的運行庫。更簡單的辦法,可以嘗試添加一個文件引用:

#include <openssl/applink.c>

 

 

發佈了74 篇原創文章 · 獲贊 127 · 訪問量 91萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章