使用 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万+

猜你喜欢

转载自blog.csdn.net/AAA123524457/article/details/103288979