OpenSSL RSA相关基本接口和编程示例
本文测试代码基于Openssl版本:1.1.1f
RSA接口
接口简介
- 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会在生成素数之后对其进行处理
- 加密解密接⼝
- 公钥加密--私钥解密
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
- 签名验签接⼝
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
- 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的基本接口使用和编程示例
接口简介
- 初始化相关接⼝
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为成功
- 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