OpenSSL RSA相关基本接口和编程示例

本文测试代码基于Openssl版本:1.1.1f

RSA接口

接口简介

  1. RSA对象创建
int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb);

功能:创建⼀对rsa的公钥私钥
参数:RSA密钥指针,密钥bit位数,公钥指数的⼤数形式指针,回调函数
返回:成功返回1,失败返回0
e主要有两个取值:第二个更常用
# define RSA_3 0x3L
# define RSA_F4 0x10001L
注意1:旧接口RSA_generate_key已经被废弃
注意2:回调函数可为null,在key的生成过程中会生成素数,cb会在生成素数之后对其进行处理
  1. 加密解密接⼝
  • 公钥加密--私钥解密
int RSA_public_encrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding);

功能:公钥加密,将⻓度为flen的from字符串加密,使用to指针返回密文,返回to的⻓度等于RSA_size(rsa)
参数:明⽂⻓度(flen需要满⾜padding的限制规则),明⽂,密⽂,密钥,padding填充模式
padding填充模式有:
    RSA_PKCS1_PADDING: flen <= RSA_size(rsa) - 11
    RSA_PKCS1_OAEP_PADDING: flen < RSA_size(rsa) - 42
    RSA_NO_PADDING: flen == RSA_size(rsa)
    RSA_SSLV23_PADDING
返回:成功返回密⽂⻓度,失败返回-1


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

功能:私钥解密,将⻓度为flen的from密文解密,使用to指针返回明文
参数:密⽂⻓度,密⽂,明⽂, 密钥,padding填充模式
padding填充模式:
RSA_PKCS1_PADDING、RSA_PKCS1_OAEP_PADDING、RSA_SSLV23_PADDING、RSA_NO_PADDING
返回:成功返回明⽂⻓度,失败返回-1
  • 私钥加密--公钥解密
int RSA_private_encrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding);

功能:私钥加密,将⻓度为flen的from字符串加密,使用to指针返回密文,返回to的⻓度等于RSA_size(rsa)
参数:明⽂⻓度,明⽂,密⽂,密钥,padding填充模式
padding填充模式:
RSA_PKCS1_PADDING、RSA_PKCS1_OAEP_PADDING、RSA_SSLV23_PADDING、RSA_NO_PADDING
返回:成功返回明⽂⻓度,失败返回-1


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

功能:公钥解密,将⻓度为flen的from密文解密,使用to指针返回明文
参数:明⽂⻓度,密⽂,明⽂输出,密钥,padding填充模式
padding填充模式:
RSA_PKCS1_PADDING、RSA_PKCS1_OAEP_PADDING、RSA_SSLV23_PADDING、RSA_NO_PADDING
返回:成功返回明⽂⻓度,失败返回-1
  1. 签名验签接⼝
int RSA_sign(int type, const unsigned char *m, unsigned int m_len, unsigned char *sigret, unsigned int *siglen, RSA *rsa);

功能:RSA签名,输⼊摘要数据,返回签名sigret
参数:type表⽰⽣成m使⽤的摘要算法类型,m表⽰摘要(hash)数据,m_len表示摘要数据⻓度 sigret是返回签名的指针(指向的内存长度需要等于RSA_size(rsa)),siglen是签名⻓度,rsa是签名者的公钥
type类型:NID_md5、NID_sha、NID_sha1、NID_md5_sha1
注意1:摘要m需要和type类型保持一致,接口内部会针对不同的hash长度做padding
注意2:RSA类型本身可同时存储私钥和公钥信息,根据实际接口使用不同的能力
返回:成功返回1


int RSA_verify(int type, const unsigned char *m, unsigned int m_len, unsigned char *sigret, unsigned int siglen, RSA *rsa);

功能:RSA验证签名,输⼊摘要m和签名sigret,返回验签是否通过,即m和解密的sigret是否匹配
参数:type表⽰⽣成m使⽤的摘要算法类型,m表⽰摘要数据,m_len为摘要⻓度,sigret签名
siglen为签名长度,rsa是签名者的公钥
type:NID_md5、NID_sha、NID_sha1、NID_md5_sha1
返回:成功返回1
 
  1. RSA pem⽂件相关
RSA *PEM_read_RSAPublicKey(FILE *fp, RSA **x, pem_password_cb *cb, void *u);
RSA *PEM_read_RSAPrivateKey(FILE *fp, RSA **x, pem_password_cb *cb, void *u);

功能:从pem文件中读取公钥私钥
参数:fp为⽂件,x是输出的RSA类型指针,cb⽤于对加密的pem解密,u是cb函数的参数
注意:当x不为null时,读取的密钥将输出到RSA类型密钥x中;当设置x为null,接口将在返回值中return rsa指针
返回:nullptr 为读取失败


int PEM_write_RSAPublicKey(FILE *fp, RSA *x);
int PEM_write_RSAPrivateKey(FILE *fp, RSA *x, const EVP_CIPHER *enc, unsigned
char *kstr, int klen, pem_password_cb *cb, void *u);
参数:fp输⼊⽂件,x为待写入的密钥,enc为指定要使用的加密算法使得私钥文件不为明文存储,后续参数均可以设置为null
功能:写⼊公钥私钥
返回:1 success,0 failed

编程示例

#include <iostream>
#include <string>
#include <cstring>
#include <openssl/bio.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/pem.h> //NID_md5_sha1
#include <openssl/md5.h>

using namespace std;

void dump_hex(const uint8_t *hex, uint32_t size) {
  uint32_t i = 0;
  for (i = 0; i < size; ++i) {
    if ((i % 8) == 0) {
      printf("\n");
    }
    printf("0x%02x ", hex[i]);
  }
  printf("\n");
}

RSA* rsa_create(){
    RSA* rsa = RSA_new();//分配空间
    BIGNUM* pBNe = BN_new();//分配空间
    BN_set_word(pBNe, RSA_F4);
    int ret = RSA_generate_key_ex(rsa, 1024, pBNe, NULL);
    if(ret < 0 ){
        ERR_print_errors_fp(stderr);
        return nullptr;
    }
    
    BN_free(pBNe);

    return rsa;
}

void PubEnc_PriDec(RSA* rsa, unsigned char* plaintext, int plaintext_len){
    //公钥加密
    int ciphertext_len = RSA_size(rsa);
    std::cout << "ciphertext_len: " << ciphertext_len << std::endl;
    unsigned char ciphertext[ciphertext_len]{}; //加密后密文长度需要等于RSA_size(rsa)
    int ret = RSA_public_encrypt(plaintext_len,plaintext,ciphertext,rsa,RSA_PKCS1_PADDING);
    if(ret < 0){
        ERR_print_errors_fp(stderr);
        return;
    }
    std::cout << "ciphertext: " << std::endl;
    dump_hex(ciphertext, ciphertext_len);

    //私钥解密
    unsigned char plaintext_decrypt[plaintext_len]{};
    int plaintext_decrypt_len{};
    ret = RSA_private_decrypt(ciphertext_len,ciphertext,plaintext_decrypt,rsa,RSA_PKCS1_PADDING);
    if(ret < 0 ){
        ERR_print_errors_fp(stderr);
        return;
    }
    std::cout << "decrypt plaintext: " << std::endl;
    std::cout << plaintext_decrypt << std::endl;
}

void PriEnc_PubDec(RSA* rsa, unsigned char* plaintext, int plaintext_len){
    //私钥加密
    int ciphertext_len = RSA_size(rsa);
    unsigned char ciphertext[ciphertext_len]{}; //加密后密文长度需要等于RSA_size(rsa)
    int ret = RSA_private_encrypt(plaintext_len,plaintext,ciphertext,rsa,RSA_PKCS1_PADDING);
    if(ret < 0 ){
        ERR_print_errors_fp(stderr);
    }
    std::cout << "ciphertext: " << std::endl;
    dump_hex(ciphertext, ciphertext_len);

    //公钥解密
    unsigned char plaintext_decrypt[plaintext_len]{};
    int plaintext_decrypt_len{};
    ret = RSA_public_decrypt(ciphertext_len,ciphertext,plaintext_decrypt,rsa,RSA_PKCS1_PADDING);
    if(ret < 0 ){
        ERR_print_errors_fp(stderr);
    }
    std::cout << "decrypt plaintext: " << std::endl;
    std::cout << plaintext_decrypt << std::endl;
}

void Sign_Verify(RSA* rsa, unsigned char* plaintext, unsigned int plaintext_len){
    unsigned int sign_text_len{RSA_size(rsa)};
    unsigned char sign_text[sign_text_len];

    unsigned char md5[16];
    MD5(plaintext,plaintext_len,md5);

    int ret = RSA_sign(NID_md5,md5,16,sign_text,&sign_text_len,rsa);
    if(ret != 1 ){
        ERR_print_errors_fp(stderr);
    }
    std::cout << "sign_text_len: " <<sign_text_len<< std::endl;
    dump_hex(sign_text, sign_text_len);

    ret = RSA_verify(NID_md5,md5,16,sign_text,sign_text_len,rsa);
    if(ret != 1 ){
        ERR_print_errors_fp(stderr);
    }
    std::cout << "verify result: " << ret << std::endl;
}

RSA* Read_Key(){
    FILE *fp_pub;
	if ((fp_pub = fopen("/your_path/rsa_public_key.pem", "r")) == NULL) {
        return nullptr;
    }

    FILE *fp_pri;
    if ((fp_pri = fopen("/your_path/rsa_private_key.pem", "r")) == NULL) {
        return nullptr;
    }

    RSA* rsa{PEM_read_RSAPublicKey(fp_pub, nullptr, nullptr,nullptr)};
    PEM_read_RSAPrivateKey(fp_pri,&rsa,nullptr,nullptr);
    fclose(fp_pub);
    fclose(fp_pri);
    return rsa;
}

void Write_Key(RSA* rsa){
    FILE *fp_pub;
	if ((fp_pub = fopen("/your_path/rsa_public_key1.pem", "wt")) == NULL) {
        return;
    }
    int ret{PEM_write_RSAPublicKey(fp_pub,rsa)};
    std::cout << "PEM_write_RSAPublicKey: " << ret << std::endl;
    fclose(fp_pub);
    
    FILE *fp_pri;
	if ((fp_pri = fopen("/your_path/rsa_private_key1.pem", "wt")) == NULL) {
        return;
    }
    ret = PEM_write_RSAPrivateKey(fp_pri,rsa,nullptr,nullptr,0,nullptr,nullptr);
    std::cout << "PEM_write_RSAPrivateKey: " << ret << std::endl;
    fclose(fp_pri);
}

int main(){
    // RSA* rsa = rsa_create();
    RSA* rsa = Read_Key();

    int plaintext_len = 100;
    // RSA要求明文长度 < 密钥长度,假如明文过长,则需要分段加密解密
    // 根据填充方式不同,明文长度小于等于RSA_size(rsa)-x
    unsigned char plaintext[plaintext_len]{"123"};
    std::cout << "plaintext: " << std::endl;
    std::cout << plaintext << std::endl;

    PubEnc_PriDec(rsa,plaintext,plaintext_len);
    PriEnc_PubDec(rsa,plaintext,plaintext_len);

    Sign_Verify(rsa,plaintext,plaintext_len);

    Write_Key(rsa);

    RSA_free(rsa);

    return 0;
}


//编译 g++ rsa.cpp -lcrypto

RSA EVP接口

EVP:Openssl实现了密码学中非常多种算法实现,例如上一节中的RSA接口就是一种非对称算法的接口,此类接口和算法较近,需要使用者了解算法参数意义,EVP则是将此类的底层算法进行了封装,屏蔽算法细节,易于用户直接使用
下面介绍EVP--RSA的基本接口使用和编程示例

接口简介

  1. 初始化相关接⼝
EVP_PKEY *EVP_PKEY_new(void);

功能:返回⼀个EVP_PKEY* 对象


EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e);

功能:创建⼀个EVP_PKEY_CTX 上下⽂
参数:EVP_PKEY 该结构⽤来存放⾮对称密钥信息,可以是 RSA、DSA、DH 或 ECC 密钥, 也可以指定使⽤engine的算法,不需要加载engine可设置e为null
返回:nullptr则为失败


EVP_PKEY_CTX *EVP_PKEY_CTX_new_id(int id, ENGINE *e);

功能:创建⼀个EVP_PKEY_CTX 上下⽂
参数:id:指定的算法类型,也可以指定使⽤engine的算法,不需要加载engine可设置e为null
返回:nullptr则为失败


int EVP_PKEY_encrypt_init(EVP_PKEY_CTX *ctx);

功能:利⽤ctx上下文初始化加密环境
返回:1为成功
  1. RSA相关接⼝
int EVP_PKEY_assign_RSA(EVP_PKEY *pkey, RSA *key);
RSA *EVP_PKEY_get1_RSA(EVP_PKEY *pkey);

功能:密钥类型相互转换
返回:1为success,非null为success


int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX *ctx, int pad);

功能:设置ctx的rsa加密的padding模式
padding模式有:
RSA_PKCS1_PADDING、RSA_SSLV23_PADDING、RSA_NO_PADDING、RSA_PKCS1_OAEP_PADDING、RSA_X931_PADDING、RSA_PKCS1_PSS_PADDING
返回:1为成功


int EVP_PKEY_encrypt(EVP_PKEY_CTX *ctx, unsigned char *out, size_t *outlen, const unsigned char *in, size_t inlen);

功能:根据ctx的信息进⾏加密
参数:上下文ctx,out是输出的密⽂,outlen密⽂⻓度,in输⼊明⽂,inlen明⽂⻓度
注意1:out是nullptr时,调⽤此接⼝会将密⽂⻓度通过outlen返回;out不为nullptr时,调⽤接⼝
会将密⽂返回到out中,同时密⽂⻓度等于outlen
注意2:这个接⼝可以调⽤两次,第⼀次获取密⽂⻓度,为返回数组out分配内存后,第⼆次调用该接口再获取密⽂
返回:1为成功

编程示例

#include <openssl/evp.h>
#include <openssl/engine.h>
#include <iostream>

void dump_hex(const uint8_t *hex, uint32_t size) {
  uint32_t i = 0;
  for (i = 0; i < size; ++i) {
    if ((i % 8) == 0) {
      printf("\n");
    }
    printf("0x%02x ", hex[i]);
  }
  printf("\n");
}

int rsa_pub_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding){
  int ciphertext_len = RSA_size(rsa);
  for(int i{0};i<ciphertext_len;++i){
    to[i] = 'x';
  }
  std::cout << "rsa_pub_enc call " << std::endl;
  return ciphertext_len;
}


RSA* rsa_create(){
    RSA* rsa = RSA_new();//分配空间
    BIGNUM* pBNe = BN_new();//分配空间
    BN_set_word(pBNe, RSA_F4);
    int ret = RSA_generate_key_ex(rsa, 1024, pBNe, NULL);
    if(ret < 0 ){
        printf("encrypt failed, ret:%d \n", ret);
        return nullptr;
    }
    BN_free(pBNe);
    return rsa;
}


RSA* Read_Key(){
  FILE *fp_pub;
	if ((fp_pub = fopen("/your_path/rsa_public_key.pem", "r")) == NULL) {
        return nullptr;
    }

    FILE *fp_pri;
    if ((fp_pri = fopen("/your_path/rsa_private_key.pem", "r")) == NULL) {
        return nullptr;
    }

    RSA* rsa{PEM_read_RSAPublicKey(fp_pub, nullptr, nullptr,nullptr)};
    PEM_read_RSAPrivateKey(fp_pri,&rsa,nullptr,nullptr);
    fclose(fp_pub);
    fclose(fp_pri);
    return rsa;
}

int main(){
  //RSA* rsa = rsa_create();
  RSA* rsa = Read_Key();
  if(rsa == nullptr){
    std::cout << "Read_Key" << std::endl;
  }

  EVP_PKEY* pkey = EVP_PKEY_new();
  
  int ret;
  ret = EVP_PKEY_assign_RSA(pkey, rsa);
  if(ret == 0){
    std::cout << "EVP_PKEY_assign_RSA fail" << std::endl;
    ERR_print_errors_fp(stderr);
  }

  EVP_PKEY_CTX* ctx;
  ctx = EVP_PKEY_CTX_new(pkey, nullptr);
  if (ctx == nullptr) {
    std::cout << "EVP_PKEY_CTX_new fail" << std::endl;
    ERR_print_errors_fp(stderr);
  }

  ret = EVP_PKEY_encrypt_init(ctx);
  if (ret == 0) {
    std::cout << "EVP_PKEY_encrypt_init fail" << std::endl;
    ERR_print_errors_fp(stderr);
  }

  ret = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING);
  if (ret == 0) {
    std::cout << "EVP_PKEY_CTX_set_rsa_padding fail" << std::endl;
    ERR_print_errors_fp(stderr);
  }

  int plaintext_len = 100;
  unsigned char plaintext[plaintext_len]{"123"};
  std::cout << "plaintext: " << std::endl;
  std::cout << plaintext << std::endl;

  size_t ciphertext_len;
  ret = EVP_PKEY_encrypt(ctx, nullptr, &ciphertext_len, plaintext, plaintext_len);//先获取ciphertext_len
  if (ret == 0) {
    std::cout << "EVP_PKEY_encrypt fail" << std::endl;
  }

  std::cout << "ciphertext_len: " << ciphertext_len << std::endl;

  unsigned char ciphertext[ciphertext_len]{};//构造输出数组
  ret = EVP_PKEY_encrypt(ctx, ciphertext, &ciphertext_len, plaintext, plaintext_len);
  if (ret == 0) {
    std::cout << "EVP_PKEY_encrypt fail" << std::endl;
  }

  dump_hex(ciphertext, ciphertext_len);

  return 0;
}

总结

本文总结了RSA的基础接口和EVP RSA接口的参数和编程示例
计划下一篇对OpenSSL engine的几种加载方式进行总结

参考官方文档:
https://www.openssl.org/docs/man1.1.1/man3/
https://openssl-programing.readthedocs.io/en/latest/index.html

热门相关:秦吏   呆萌配腹黑:绝宠小冤家   傲娇小萌妃:殿下太腹黑   娇女种田,掌家娘子俏夫郎   战神无双