本文測試程式碼基於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