Using the EVP interface in openssl to implement RSA and SM2 encrypt decrypt sign and verify (C lauguage)
0. Abstract
Openssl provides a series of interfaces that name is EVP structure. Using the interfaces, it is pretty convenient to implement these algorithms of asymmetric RSA or SM2 encryption decryption signature and verification. This paper sorted out the usage of OPENSSL EVP C-lauguage interface, and implement the SM2 and RSA encrypt decrypt signature and verify. Combining with the code, this paper introduced the related functions of RSA and SM2 algorithm. Finally, ran the program to test module functions.
1. RSA
1.1 What is RSA.
RSA is a algorithm of asymmetric en/decryption which name comes from the first letter of the three men's name who are Rivest Adi and Shamir. There are two types of public key and private key. Using the RSA algorithm to encrypt plaintext, you need to assign the public key string and plaintext body to RSA algorithm, and RSA algorithm return corresponding ciphertext for you finally. When you need to decrypt the ciphertext, a private key required for decryption. So we need to hide the private key information to avoid being obtained by attacker. But public key information can share to everyone who even include the attacker. In genaral, the public key owner should be a person with whom you are communicating confidentially.
RSA cannot only encrypt and decrypt information, but also have the signature and verified function. When we want to sign the information, we need to assign the private key string and information to be signed to algorithm of RSA signature, and RSA signature function will return the signature string that the data length is about one hundred bytes to us. When we want to verify the signature information, use public key to do it. The verified result is bool value, it's either true or false. The signature and verified function can help us to avoid the information being modifed by anyone else. It's one of the security system principles - authentication principle.
1.2 RSA gen .pem file(key pairs)
One of the RSA algorithm key points is the key that includes the public key and private key which are called key pairs collectively. The key is irregular string in nature. You can assign the string in your code directly, also you can save it in a "pem" file that is standard key information carrier.
So the first step is gen key pairs for RSA algorithm. This paper and code base openssl EVP C-lauguage interface rather than openssl cmd mode. You should compile the openssl source code on your pc, and source code can be downloaded on https://github.com/openssl/openssl/tags website. I recommand the openssl 1.1.1 that no any letter in the version, because of sm2 supported.
The RSA pem file have different format, that are pkcs#1, pkcs#8 as follows:
1.2.1 PKCS#1 Format
The RSA public key PEM file is specific for RSA keys.
It starts with the tags:
-----BEGIN RSA PUBLIC KEY-----
and it ends with the tags:
------END RSA PUBLIC KEY-----
What's more the PEM have base64 encoded data format that still is PKCS#1
That the format with the tags:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
The RSA privae key PEM file is basically same with public key PEM file. It's just different from the public key PEM file is PUBLIC string to PRIVATE string.
-----BEGIN RSA PUBLIC KEY-----
...content
------END RSA PUBLIC KEY-----
1.2.2 PKCS#8 Format
Because RSA is not used exclusively inside X509 and SSL/TLS, a more generic key format is available in the form of PKCS#8, that identifies the type of public key and containsum the relevant data.
The difference between the PKCS#1 and PKCS#8 in content respect is that the PKCS#8 not existing RSA words.
-----BEGIN PUBLIC KEY-----
...content
------END PUBLIC KEY-----
The private key PEM file same as the public one.
**So, when we read key information from the PEM file, must ensure that the PKCS is matching. **
1.2.3 How to generate RSA key
How to generate the RSA key pairs using the C-language. Firstly, Some header files should be included in your project before using the openssl EVP interface.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <openssl/ssl.h>
#include <openssl/md5.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/bn.h>
#include <openssl/err.h>
#include <openssl/x509.h>
And gen the PEM file need to prepared some essential information that contains the RSA key length, random seed, and path and name of the PEM file.
#define RSA_KEY_LENGTH 1024
static const char rnd_seed[] = "string to make the random number generator initialized";
#define PRIVATE_RSA_KEY_FILE "fotaprikey.pem"
#define PUBLIC_RSA_KEY_FILE "fotapubkey.pem"
There is the code that generate RSA key file. In the code, RSA structure is defined for including all the RSA algorithm information that are large prime number P-value, Q-value etc. All the PEM file operation which is took on BIO struct, using the BIO_new() BIO_write_file etc. The key information will be read from RSA structure to BIO function, finally, genarate a pairs of PEM file for public key and private key.
int generate_rsa_key_files(const char *pub_keyfile, const char *pri_keyfile,
const unsigned char *passwd, int passwd_len)
{
RSA *rsa = NULL;
RAND_seed(rnd_seed, sizeof(rnd_seed));
rsa = RSA_generate_key(RSA_KEY_LENGTH, RSA_F4, NULL, NULL);
if(rsa == NULL) {
printf("RSA_generate_key error!\n");
return -1;
}
BIO *bp = BIO_new(BIO_s_file());
if (NULL == bp) {
printf("generate_key bio file new error!\n");
return -1;
}
if(BIO_write_filename(bp, (void *)pub_keyfile) <= 0) {
printf("BIO_write_filename error!\n");
return -1;
}
if(PEM_write_bio_RSAPublicKey(bp, rsa) != 1) {
printf("PEM_write_bio_RSAPublicKey error!\n");
return -1;
}
printf("Create rsa public key ok!\n");
BIO_free_all(bp);
bp = BIO_new_file(pri_keyfile, "w+");
if(NULL == bp){
printf("generate_key bio file new error2!\n");
return -1;
}
if(PEM_write_bio_RSAPrivateKey(bp, rsa,
EVP_des_ede3_ofb(), (unsigned char *)passwd,
passwd_len, NULL, NULL) != 1) {
printf("PEM_write_bio_RSAPublicKey error!\n");
return -1;
}
printf("Create rsa private key ok!\n");
BIO_free_all(bp);
RSA_free(rsa);
return 0;
}
You should notice that function PEM_wirte_bio_RSAPriateKey(), private key can be assigned the password. If you set the password for private key, everyone try to read the private PEM file, the password need to be inputted as required.
And the other tips you should notice is that bio interface will gen the PKCS#1 PEM files. If you want to gen the PKCS#8 format PEM files, you can use PEM_write_bio_PKCS8PrivateKey() interface to implement it.
1.3 RSA encrypt
Using the RSA to encrypt message, I abstract it to openssl_evp_rsa_encrypt function that need user to transform plaintext, ciphertext buffer, and public key PEM file.
The flow of the function is check user input -> read public key from PEM file to EVP_PKEY structure -> using the EVP_PKEY structure do message encrypt.
/*openssl rsa cipher evp using*/
int openssl_evp_rsa_encrypt( unsigned char *plain_text, size_t plain_len,
unsigned char *cipher_text, size_t *cipher_len,
unsigned char *pem_file)
{
int ret = 0;
RSA *rsa = NULL;
EVP_PKEY* public_evp_key = NULL;
FILE *fp = NULL;
BIO *bp = NULL;
EVP_PKEY_CTX *ctx = NULL;
/*Check the user input.*/
if (plain_text == NULL || plain_len == 0 || cipher_text == NULL || *cipher_len == 0) {
printf("input parameters error, plain_text cipher_text or plain_len is NULL or 0.\n");
ret = -1;
goto finish;
}
if (NULL == pem_file) {
printf("input pem_file name is invalid\n");
ret = -1;
goto finish;
}
fp = fopen((const char*)pem_file, "r");
if (NULL == fp) {
printf("input pem_file is not exit.\n");
ret = -1;
goto finish;
}
fclose(fp);
fp = NULL;
//OpenSSL_add_all_algorithms();
bp = BIO_new(BIO_s_file());
if (bp == NULL) {
printf("BIO_new is failed.\n");
ret = -1;
goto finish;
}
/*read public key from pem file.*/
ret = BIO_read_filename(bp, pem_file);
rsa = PEM_read_bio_RSAPublicKey(bp, NULL, NULL, NULL);
if (rsa == NULL) {
ret = -1;
printf("open_public_key failed to PEM_read_bio_RSAPublicKey Failed, ret=%d\n", ret);
goto finish;
}
public_evp_key = EVP_PKEY_new();
if (public_evp_key == NULL) {
ret = -1;
printf("open_public_key EVP_PKEY_new failed\n");
goto finish;
}
EVP_PKEY_assign_RSA(public_evp_key, rsa);
/*do cipher.*/
ctx = EVP_PKEY_CTX_new(public_evp_key, NULL);
if (ctx == NULL) {
ret = -1;
printf("EVP_PKEY_CTX_new failed\n");
goto finish;
}
ret = EVP_PKEY_encrypt_init(ctx);
if (ret < 0) {
printf("ras_pubkey_encrypt failed to EVP_PKEY_encrypt_init. ret = %d\n", ret);
goto finish;
}
ret = EVP_PKEY_CTX_set_rsa_padding(ctx, EVP_PADDING_PKCS7);
if (ret != 1) {
printf("EVP_PKEY_CTX_set_rsa_padding failed. ret = %d\n", ret);
goto finish;
}
ret = EVP_PKEY_encrypt(ctx, cipher_text, cipher_len, plain_text, plain_len);
if (ret < 0) {
printf("ras_pubkey_encrypt failed to EVP_PKEY_encrypt. ret = %d\n", ret);
goto finish;
}
ret = 0;
finish:
if (public_evp_key != NULL)
EVP_PKEY_free(public_evp_key);
if (bp != NULL)
BIO_free(bp);
if (ctx != NULL)
EVP_PKEY_CTX_free(ctx);
return ret;
}
1.4 RSA decrypt
Same as encryption procession, the decryption is revert procession of encryption. The funtion as follow need transform ciphertext and plaintext buffer, and private key PEM file is required by decrypting. When you generate the private key PEM file assigned a password, the password should be transformed in the function, if not, a NULL (void*0) parameter is filled in this position.
/*openssl rsa decrypt evp using*/
int openssl_evp_rsa_decrypt(unsigned char *cipher_text, size_t cipher_len,
unsigned char *plain_text, size_t *plain_len,
const unsigned char *pem_file, const unsigned char *passwd)
{
int ret = 0;
size_t out_len = 0;
EVP_PKEY* private_evp_key = NULL;
RSA *rsa = NULL;
BIO *bp = NULL;
FILE *fp = NULL;
EVP_PKEY_CTX *ctx = NULL;
/*Check the user input.*/
if (plain_text == NULL || *plain_len == 0 || cipher_text == NULL || cipher_len == 0) {
printf("input parameters error, plain_text cipher_text or plain_len is NULL or 0.\n");
ret = -1;
goto finish;
}
if (NULL == pem_file) {
printf("input pem_file name is invalid\n");
ret = -1;
goto finish;
}
fp = fopen((const char*)pem_file, "r");
if (NULL == fp) {
printf("input pem_file is not exit.\n");
ret = -1;
goto finish;
}
fclose(fp);
fp = NULL;
//OpenSSL_add_all_algorithms();
bp = BIO_new(BIO_s_file());
if (bp == NULL) {
printf("BIO_new is failed.\n");
ret = -1;
goto finish;
}
/*read private key from pem file.*/
ret = BIO_read_filename(bp, pem_file);
rsa = PEM_read_bio_RSAPrivateKey(bp, &rsa, NULL, (void*)passwd);
if (rsa == NULL) {
ret = -1;
printf("open_private_key failed to PEM_read_bio_RSAPrivateKey Failed, ret=%d\n", ret);
goto finish;
}
private_evp_key = EVP_PKEY_new();
if (private_evp_key == NULL) {
ret = -1;
printf("open_private_key EVP_PKEY_new failed\n");
goto finish;
}
EVP_PKEY_assign_RSA(private_evp_key, rsa);
/*do cipher.*/
ctx = EVP_PKEY_CTX_new(private_evp_key, NULL);
if (ctx == NULL) {
ret = -1;
printf("EVP_PKEY_CTX_new failed\n");
goto finish;
}
ret = EVP_PKEY_decrypt_init(ctx);
if (ret != 1) {
printf("rsa_private_key decrypt failed to EVP_PKEY_decrypt_init. ret = %d\n", ret);
goto finish;
}
ret = EVP_PKEY_CTX_set_rsa_padding(ctx, EVP_PADDING_PKCS7);
if (ret != 1) {
printf("EVP_PKEY_CTX_set_rsa_padding failed. ret = %d\n", ret);
goto finish;
}
/* Determine buffer length */
ret = EVP_PKEY_decrypt(ctx, NULL, &out_len, cipher_text, cipher_len);
if (ret != 1) {
printf("rsa_prikey_decrypt failed to EVP_PKEY_decrypt. ret = %d\n", ret);
goto finish;
}
*plain_len = out_len;
ret = EVP_PKEY_decrypt(ctx, plain_text, plain_len, cipher_text, cipher_len);
if (ret != 1) {
printf("rsa_prikey_decrypt failed to EVP_PKEY_decrypt. ret = %d\n", ret);
goto finish;
}
ret = 0;
finish:
if (private_evp_key != NULL)
EVP_PKEY_free(private_evp_key);
if (bp != NULL)
BIO_free(bp);
if (ctx != NULL)
EVP_PKEY_CTX_free(ctx);
return ret;
}
1.5 RSA En/Decryption Experiment
I designed a test case for testing the RSA En/Decryption functions. Firstly, generate a pairs of public private key, then using the encryption function to encrypt the "hello carlos!" message, finally, clear the plaintext buffer, and transform it to decryption function and check the result of decryption wether same to original plaintext.
My environment as follow:
- System OS: MacOS Catalina 10.15.6
- OpenSSL Version: 1.1.1
- Compiler: clang-gcc
- IDE: Clion - cmake
I wrote the test code as folllows:
int test_evp_rsa_encrypt_decrypt()
{
int ret = 0, i = 0;
unsigned char cipher_out[1024];
unsigned char plain_in[] = "hello carlos rsa...";
size_t out_len = 1024;
size_t in_len = strlen(plain_in);
ret = openssl_evp_rsa_encrypt(plain_in, in_len, cipher_out, &out_len, PUBLIC_RSA_KEY_FILE);
if (ret != 0) {
printf("error in encrypt %d\n", ret);
}
printf("rsa plain text is : %s \n", plain_in);
printf("rsa cipher len = %d text is :\n", out_len);
for (i = 0; i < out_len; i ++) {
printf("%02X", cipher_out[i]);
}
printf("\n");
memset(plain_in, '\0', in_len);
in_len = 1;
ret = openssl_evp_rsa_decrypt(cipher_out, out_len, plain_in, &in_len, PRIVATE_RSA_KEY_FILE, "12345");
if (ret != 0) {
printf("error in decrypt %d\n", ret);
}
printf("rsa decrypt len = %d and text is : %s \n", in_len, plain_in);
return ret;
}
int test_evp_sm2_encrypt_decrypt()
{
int ret = 0, i = 0;
unsigned char cipher_out[1024];
unsigned char plain_in[] = "hello carlos sm2...";
size_t out_len = 1024;
size_t in_len = strlen(plain_in);
ret = openssl_evp_sm2_encrypt(plain_in, strlen(plain_in), cipher_out, &out_len, PUBLIC_SM2_KEY_FILE);
if (ret != 0) {
printf("error in encrypt %d\n", ret);
return ret;
}
printf("sm2 plain text is : %s \n", plain_in);
printf("sm2 cipher len = %d text is :\n", out_len);
for (i = 0; i < out_len; i ++) {
printf("%02X", cipher_out[i]);
}
printf("\n");
memset(plain_in, '\0', in_len);
in_len = 1;
ret = openssl_evp_sm2_decrypt(cipher_out, out_len, plain_in, &in_len, PRIVATE_SM2_KEY_FILE, "12345");
if (ret != 0) {
printf("error in decrypt %d\n", ret);
}
printf("sm2 decrypt len = %d and text is : %s \n", in_len, plain_in);
return ret;
}
![image](/img/bVbMpSc)
1.6 RSA signature
RSA sign procession is different from encryption and decryption, that is contains other asymmetric algorithm, Message Digest and Secure Hash Algorithm. And anther difference is that finishing the signature need the private key. It's the opposite of the RSA encryption/decryption process.
// RSA_PKCS1_PADDING RSA_OAEP_PADDING
int openssl_evp_rsa_signature(unsigned char *sign_rom, size_t sign_rom_len,
unsigned char *result, size_t *result_len,
const unsigned char *priv_pem_file, const unsigned char *passwd)
{
int ret = 0;
FILE *fp = NULL;
EVP_PKEY* private_evp_key = NULL;
RSA *rsa = NULL;
BIO *bp = NULL;
EVP_PKEY_CTX *ctx = NULL;
EVP_MD_CTX *evp_md_ctx = NULL;
/*Check the user input.*/
if (sign_rom == NULL || sign_rom_len == 0 || result == NULL || *result_len == 0) {
printf("input parameters error, content or len is NULL or 0.\n");
ret = -1;
goto finish;
}
if (NULL == priv_pem_file) {
printf("input pem_file name is invalid\n");
ret = -1;
return ret;
}
fp = fopen((const char*)priv_pem_file, "r");
if (NULL == fp) {
printf("input pem_file is not exit.\n");
ret = -1;
goto finish;
}
fclose(fp);
fp = NULL;
/*read private key from pem file to private_evp_key*/
//OpenSSL_add_all_algorithms();
bp = BIO_new(BIO_s_file());
if (bp == NULL) {
printf("BIO_new is failed.\n");
ret = -1;
goto finish;
}
ret = BIO_read_filename(bp, priv_pem_file);
rsa = PEM_read_bio_RSAPrivateKey(bp, &rsa, NULL, (void*)passwd);
if (rsa == NULL) {
ret = -1;
printf("open_private_key failed to PEM_read_bio_RSAPrivateKey Failed, ret=%d\n", ret);
goto finish;
}
private_evp_key = EVP_PKEY_new();
if (private_evp_key == NULL) {
ret = -1;
printf("open_private_key EVP_PKEY_new failed\n");
goto finish;
}
EVP_PKEY_assign_RSA(private_evp_key, rsa);
/*do signature*/
evp_md_ctx = EVP_MD_CTX_new();
if (evp_md_ctx == NULL) {
printf("EVP_MD_CTX_new failed.\n");
ret = -1;
goto finish;
}
EVP_MD_CTX_init(evp_md_ctx);
ret = EVP_SignInit_ex(evp_md_ctx, EVP_md5(), NULL);
if (ret != 1) {
printf("EVP_SignInit_ex failed, ret = %d\n", ret);
goto finish;
}
ret = EVP_SignUpdate(evp_md_ctx, sign_rom, sign_rom_len);
if (ret != 1) {
printf("EVP_SignUpdate failed, ret = %d\n", ret);
goto finish;
}
ret = EVP_SignFinal(evp_md_ctx, result, (unsigned int*)result_len, private_evp_key);
if (ret != 1) {
printf("EVP_SignFinal failed, ret = %d\n", ret);
goto finish;
}
ret = 0;
finish:
if (private_evp_key != NULL)
EVP_PKEY_free(private_evp_key);
if (bp != NULL)
BIO_free(bp);
if (evp_md_ctx != NULL)
EVP_MD_CTX_free(evp_md_ctx);
if (ctx != NULL)
EVP_PKEY_CTX_free(ctx);
return ret;
}
The second parameter in function EVP_SignInit_ex(evp_md_ctx, EVP_md5(), NULL); is sub-algorithm of RSA sign, multiple message digest and secure hash algorithm are available applying the RSA.
1.7 RSA verify
RSA verify just return a bool result to you that it's either ture or false. If the true, it's surely verified message success, and vice versa.
int openssl_evp_rsa_verify(unsigned char *sign_rom, size_t sign_rom_len,
unsigned char *result, size_t result_len,
const unsigned char *pub_pem_file)
{
int ret = 0;
FILE *fp = NULL;
EVP_PKEY* public_evp_key = NULL;
RSA *rsa = NULL;
BIO *bp = NULL;
EVP_PKEY_CTX *ctx = NULL;
EVP_MD_CTX *evp_md_ctx = NULL;
/*Check the user input.*/
if (sign_rom == NULL || sign_rom_len == 0 || result == NULL || result_len == 0) {
printf("input parameters error, content or len is NULL or 0.\n");
ret = -1;
goto finish;
}
if (NULL == pub_pem_file) {
printf("input pem_file name is invalid\n");
ret = -1;
goto finish;
}
fp = fopen((const char*)pub_pem_file, "r");
if (NULL == fp) {
printf("input pem_file is not exit.\n");
ret = -1;
goto finish;
}
fclose(fp);
fp = NULL;
/*read public key from pem file to private_evp_key*/
//OpenSSL_add_all_algorithms();
bp = BIO_new(BIO_s_file());
if (bp == NULL) {
printf("BIO_new is failed.\n");
ret = -1;
goto finish;
}
ret = BIO_read_filename(bp, pub_pem_file);
rsa = PEM_read_bio_RSAPublicKey(bp, NULL, NULL, NULL);
if (rsa == NULL) {
ret = -1;
printf("open_public_key failed to PEM_read_bio_RSAPublicKey Failed, ret=%d\n", ret);
goto finish;
}
public_evp_key = EVP_PKEY_new();
if (public_evp_key == NULL) {
ret = -1;
goto finish;
}
EVP_PKEY_assign_RSA(public_evp_key, rsa);
/*do verify*/
evp_md_ctx = EVP_MD_CTX_new();
if (evp_md_ctx == NULL) {
printf("EVP_MD_CTX_new failed.\n");
ret = -1;
goto finish;
}
EVP_MD_CTX_init(evp_md_ctx);
ret = EVP_VerifyInit_ex(evp_md_ctx, EVP_md5(), NULL);
if (ret != 1) {
printf("EVP_VerifyInit_ex failed, ret = %d\n", ret);
goto finish;
}
ret = EVP_VerifyUpdate(evp_md_ctx, result, result_len);
if (ret != 1) {
printf("EVP_VerifyUpdate failed, ret = %d\n", ret);
goto finish;
}
ret = EVP_VerifyFinal(evp_md_ctx, sign_rom, (unsigned int)sign_rom_len, public_evp_key);
if (ret != 1) {
printf("EVP_VerifyFinal failed, ret = %d\n", ret);
goto finish;
}
ret = 0;
finish:
if (public_evp_key != NULL)
EVP_PKEY_free(public_evp_key);
if (bp != NULL)
BIO_free(bp);
if (evp_md_ctx != NULL)
EVP_MD_CTX_free(evp_md_ctx);
if (ctx != NULL)
EVP_PKEY_CTX_free(ctx);
return ret;
}
1.8 RSA Sign/Verify Experment
I have prepared test code for RSA and Verify similarly. Firstly, gave the message to add signature information using the private key. Secondly, I transformed the signature information and origin message to verify funtion. Finally, the verify funtion return me a boolean result told me the status verified.
int test_evp_rsa_signature_verify()
{
int ret = 0, i = 0;
unsigned char sign_out[1024];
unsigned char plain_in[] = "hello carlos.";
size_t out_len = 256;
size_t in_len = strlen(plain_in);
ret = openssl_evp_rsa_signature(plain_in, in_len, sign_out, &out_len, PRIVATE_RSA_KEY_FILE, "12345");
if (ret != 0) {
printf("rsa signature failed!\n");
return ret;
}
printf("rsa %s openssl sign len = %d, signature result: \n", plain_in, out_len);
for(i = 0; i < out_len; i++) {
printf("%02X", sign_out[i]);
}
printf("\n");
ret = openssl_evp_rsa_verify(sign_out, out_len, plain_in, in_len, PUBLIC_RSA_KEY_FILE);
if (ret != 0) {
printf("rsa verify failed!\n");
} else {
printf("rsa verify succeed!\n");
}
}
2. SM2
2.1 What is SM2
SM is the first letter of abbreviations (商密:Shang Mi). It's invented by National Cryptography Department of China. SM2 is one of the algorithm of the SM series cryptography and it belongs to asymmetric cryptography. So there are public key and private key required and SM2 PEM format file of key pairs is meet PKCS#1 PKCS#8 standards.
Using the SM2 in openssl, the #include "openssl_sm2.h" should be written at head of .c file.
2.2 SM2 gen .pem file(key pairs)
SM2 algorithm belongs to the type of ellipse encryption, so when we use SM2 algorithm, we need to set the type of SM2 curve. There are many curves you can select to ellipse encryption. SM2 pem file is different from RSA. This chapter will introduce that how to generate the sm2 .pem file by giving the code examples.
int generate_sm2_key_files(const char *pub_keyfile, const char *pri_keyfile,
const unsigned char *passwd, int passwd_len)
{
int ret = 0;
EC_KEY *ec_key = NULL;
EC_GROUP *ec_group = NULL;
#ifdef MAKE_KEY_TO_RAM
size_t prikey_len = 0;
size_t pubkey_len = 0;
unsigned char *prikey_buffer = NULL;
unsigned char *pubkey_buffer = NULL;
#endif
BIO *pri_bio = NULL;
BIO *pub_bio = NULL;
ec_key = EC_KEY_new();
if (ec_key == NULL) {
ret = -1;
printf("EC_KEY_new() failed return NULL.\n");
goto finish;
}
ec_group = EC_GROUP_new_by_curve_name(NID_sm2);
if (ec_group == NULL) {
ret = -1;
printf("EC_GROUP_new_by_curve_name() failed, return NULL.\n");
goto finish;
}
ret = EC_KEY_set_group(ec_key, ec_group);
if (ret != 1) {
printf("EC_KEY_set_group() failed, ret = %d\n", ret);
ret = -1;
goto finish;
}
ret = EC_KEY_generate_key(ec_key);
if (!ret) {
printf("EC_KEY_generate_key() failed, ret = %d\n", ret);
ret = -1;
goto finish;
}
printf("Create sm2 private key ok!");
#ifdef MAKE_KEY_TO_RAM
pri_bio = BIO_new(BIO_s_mem());
#else
pri_bio = BIO_new(BIO_s_file());
#endif
if (pri_bio == NULL) {
ret = -1;
printf("pri_bio = BIO_new(BIO_s_file()) failed, return NULL. \n");
goto finish;
}
ret = BIO_write_filename(pri_bio, (void *)pri_keyfile);
if (ret <= 0) {
printf("BIO_write_filename error!\n");
goto finish;
}
ret = PEM_write_bio_ECPrivateKey(pri_bio, ec_key, NULL, (unsigned char *)passwd, passwd_len, NULL, NULL);
if (ret != 1) {
printf("PEM_write_bio_ECPrivateKey error! ret = %d \n", ret);
ret = -1;
goto finish;
}
#ifdef MAKE_KEY_TO_RAM
pub_bio = BIO_new(BIO_s_mem());
#else
pub_bio = BIO_new(BIO_s_file());
#endif
if (pub_bio == NULL) {
ret = -1;
printf("pub_bio = BIO_new(BIO_s_file()) failed, return NULL. \n");
goto finish;
}
ret = BIO_write_filename(pub_bio, (void *)pub_keyfile);
if (ret <= 0) {
printf("BIO_write_filename error!\n");
goto finish;
}
ret = PEM_write_bio_EC_PUBKEY(pub_bio, ec_key);
if (ret != 1) {
ret = -1;
printf("PEM_write_bio_EC_PUBKEY error!\n");
goto finish;
}
printf("Create sm2 public key ok!");
#ifdef MAKE_KEY_TO_RAM
PEM_write_bio_EC_PUBKEY(pub_bio, ec_key);
prikey_len = BIO_pending(pri_bio);
pubkey_len = BIO_Pending(pub_bio);
prikey_buffer = (unsigned char*)OPENSSL_malloc((prikey_len + 1) * sizeof(unsigned char));
if (prikey_buffer == NULL) {
ret = -1;
printf("prikey_buffer OPENSSL_malloc failed, return NULL. \n");
goto finish;
}
pubkey_buffer = (unsigned char*)OPENSSL_malloc((pubkey_len + 1) * sizeof(unsigned char));
if (pubkey_buffer == NULL) {
ret = -1;
printf("pubkey_buffer OPENSSL_malloc failed, return NULL. \n");
goto finish;
}
BIO_read(pri_bio, prikey_buffer, prikey_len);
BIO_read(pub_bio, pubkey_buffer, pubkey_len);
prikey_buffer[prikey_len] = '\0';
pubkey_buffer[pubkey_len] = '\0';
#endif
finish:
if (ec_key != NULL)
EC_KEY_free(ec_key);
if (ec_group != NULL)
EC_GROUP_free(ec_group);
#ifdef MAKE_KEY_TO_RAM
if (prikey_buffer != NULL)
OPENSSL_free(prikey_buffer);
if (pubkey_buffer != NULL)
OPENSSL_free(pubkey_buffer);
#endif
if (pub_bio != NULL)
BIO_free_all(pub_bio);
if (pri_bio != NULL)
BIO_free_all(pri_bio);
return ret;
}
2.3 SM2 encrypt
/*openssl sm2 cipher evp using*/
int openssl_evp_sm2_encrypt( unsigned char *plain_text, size_t plain_len,
unsigned char *cipher_text, size_t *cipher_len,
unsigned char *pem_file)
{
int ret = 0;
size_t out_len = 512;
unsigned char cipper[512];
FILE *fp = NULL;
BIO *bp = NULL;
EC_KEY *ec_key = NULL;
EVP_PKEY* public_evp_key = NULL;
EVP_PKEY_CTX *ctx = NULL;
/*Check the user input.*/
if (plain_text == NULL || plain_len == 0 || cipher_text == NULL || *cipher_len == 0) {
printf("input parameters error, plain_text cipher_text or plain_len is NULL or 0.\n");
ret = -1;
return ret;
}
if (NULL == pem_file) {
printf("input pem_file name is invalid\n");
ret = -1;
return ret;
}
fp = fopen(pem_file, "r");
if (NULL == fp) {
printf("input pem_file is not exit.\n");
ret = -1;
return ret;
}
fclose(fp);
fp = NULL;
//OpenSSL_add_all_algorithms();
bp = BIO_new(BIO_s_file());
if (bp == NULL) {
printf("BIO_new is failed.\n");
ret = -1;
return ret;
}
/*read public key from pem file.*/
ret = BIO_read_filename(bp, pem_file);
ec_key = PEM_read_bio_EC_PUBKEY(bp, NULL, NULL, NULL);
if (ec_key == NULL) {
ret = -1;
printf("open_public_key failed to PEM_read_bio_EC_PUBKEY Failed, ret=%d\n", ret);
goto finish;
}
public_evp_key = EVP_PKEY_new();
if (public_evp_key == NULL) {
ret = -1;
printf("open_public_key EVP_PKEY_new failed\n");
goto finish;
}
ret = EVP_PKEY_set1_EC_KEY(public_evp_key, ec_key);
if (ret != 1) {
ret = -1;
printf("EVP_PKEY_set1_EC_KEY failed\n");
goto finish;
}
ret = EVP_PKEY_set_alias_type(public_evp_key, EVP_PKEY_SM2);
if (ret != 1) {
printf("EVP_PKEY_set_alias_type to EVP_PKEY_SM2 failed! ret = %d\n", ret);
ret = -1;
goto finish;
}
/*modifying a EVP_PKEY to use a different set of algorithms than the default.*/
/*do cipher.*/
ctx = EVP_PKEY_CTX_new(public_evp_key, NULL);
if (ctx == NULL) {
ret = -1;
printf("EVP_PKEY_CTX_new failed\n");
goto finish;
}
ret = EVP_PKEY_encrypt_init(ctx);
if (ret < 0) {
printf("sm2_pubkey_encrypt failed to EVP_PKEY_encrypt_init. ret = %d\n", ret);
EVP_PKEY_free(public_evp_key);
EVP_PKEY_CTX_free(ctx);
return ret;
}
ret = EVP_PKEY_encrypt(ctx, cipher_text, cipher_len, plain_text, plain_len);
if (ret < 0) {
printf("sm2_pubkey_encrypt failed to EVP_PKEY_encrypt. ret = %d\n", ret);
EVP_PKEY_free(public_evp_key);
EVP_PKEY_CTX_free(ctx);
return ret;
}
ret = 0;
finish:
if (public_evp_key != NULL)
EVP_PKEY_free(public_evp_key);
if (ctx != NULL)
EVP_PKEY_CTX_free(ctx);
if (bp != NULL)
BIO_free(bp);
if (ec_key != NULL)
EC_KEY_free(ec_key);
return ret;
}
It's different from the RSA encryption is there is a ec_key need to set except evp_key, the ec_key need be assigned by EVP_PKEY_set1_EC_KEY and using the EVP_PKEY_set_alias_type to set which curve is your select.(SM2 is EVP_PKEY_SM2 macro define)
2.4 SM2 decrypt
/*openssl sm2 decrypt evp using*/
int openssl_evp_sm2_decrypt(unsigned char *cipher_text, size_t cipher_len,
unsigned char *plain_text, size_t *plain_len,
const unsigned char *pem_file, const unsigned char *passwd)
{
int ret = 0;
size_t out_len = 0;
FILE *fp = NULL;
BIO *bp = NULL;
EC_KEY *ec_key = NULL;
EVP_PKEY* private_evp_key = NULL;
EVP_PKEY_CTX *ctx = NULL;
/*Check the user input.*/
if (plain_text == NULL || cipher_len == 0 || cipher_text == NULL || *plain_len == 0) {
printf("input parameters error, plain_text cipher_text or plain_len is NULL or 0.\n");
ret = -1;
return ret;
}
if (NULL == pem_file) {
printf("input pem_file name is invalid\n");
ret = -1;
return ret;
}
fp = fopen(pem_file, "r");
if (NULL == fp) {
printf("input pem_file is not exit.\n");
ret = -1;
return ret;
}
fclose(fp);
fp = NULL;
//OpenSSL_add_all_algorithms();
bp = BIO_new(BIO_s_file());
if (bp == NULL) {
printf("BIO_new is failed.\n");
ret = -1;
return ret;
}
/*read public key from pem file.*/
ret = BIO_read_filename(bp, pem_file);
ec_key = PEM_read_bio_ECPrivateKey(bp, &ec_key, NULL, (void*)passwd);
if (ec_key == NULL) {
ret = -1;
printf("open_private_key failed to PEM_read_bio_ECPrivateKey Failed, ret=%d\n", ret);
goto finish;
}
private_evp_key = EVP_PKEY_new();
if (private_evp_key == NULL) {
ret = -1;
printf("open_public_key EVP_PKEY_new failed\n");
goto finish;
}
ret = EVP_PKEY_set1_EC_KEY(private_evp_key, ec_key);
if (ret != 1) {
ret = -1;
printf("EVP_PKEY_set1_EC_KEY failed\n");
goto finish;
}
ret = EVP_PKEY_set_alias_type(private_evp_key, EVP_PKEY_SM2);
if (ret != 1) {
printf("EVP_PKEY_set_alias_type to EVP_PKEY_SM2 failed! ret = %d\n", ret);
ret = -1;
goto finish;
}
/*modifying a EVP_PKEY to use a different set of algorithms than the default.*/
/*do cipher.*/
ctx = EVP_PKEY_CTX_new(private_evp_key, NULL);
if (ctx == NULL) {
ret = -1;
printf("EVP_PKEY_CTX_new failed\n");
goto finish;
}
ret = EVP_PKEY_decrypt_init(ctx);
if (ret < 0) {
printf("sm2 private_key decrypt failed to EVP_PKEY_decrypt_init. ret = %d\n", ret);
goto finish;
}
/* Determine buffer length */
ret = EVP_PKEY_decrypt(ctx, NULL, &out_len, cipher_text, cipher_len);
if (ret < 0) {
printf("sm2_prikey_decrypt failed to EVP_PKEY_decrypt. ret = %d\n", ret);
goto finish;
}
*plain_len = out_len;
ret = EVP_PKEY_decrypt(ctx, plain_text, plain_len, cipher_text, cipher_len);
if (ret < 0) {
printf("sm2_prikey_decrypt failed to EVP_PKEY_decrypt. ret = %d\n", ret);
goto finish;
}
ret = 0;
finish:
if (private_evp_key != NULL)
EVP_PKEY_free(private_evp_key);
if (ctx != NULL)
EVP_PKEY_CTX_free(ctx);
if (bp != NULL)
BIO_free(bp);
if (ec_key != NULL)
EC_KEY_free(ec_key);
return ret;
}
2.5 SM2 signature
int openssl_evp_sm2_signature(unsigned char *sign_rom, size_t sign_rom_len,
unsigned char *result, size_t *result_len,
const unsigned char *priv_pem_file, const unsigned char *passwd)
{
int ret = 0;
size_t out_len = 0;
FILE *fp = NULL;
BIO *bp = NULL;
EC_KEY *ec_key = NULL;
EVP_PKEY* private_evp_key = NULL;
EVP_PKEY_CTX *ctx = NULL;
EVP_MD_CTX *evp_md_ctx = NULL;
/*Check the user input.*/
if (sign_rom == NULL || sign_rom_len == 0 || result == NULL || *result_len == 0) {
printf("input parameters error, plain_text cipher_text or plain_len is NULL or 0.\n");
ret = -1;
return ret;
}
if (NULL == priv_pem_file) {
printf("input pem_file name is invalid\n");
ret = -1;
return ret;
}
fp = fopen(priv_pem_file, "r");
if (NULL == fp) {
printf("input pem_file is not exit.\n");
ret = -1;
return ret;
}
fclose(fp);
fp = NULL;
//OpenSSL_add_all_algorithms();
bp = BIO_new(BIO_s_file());
if (bp == NULL) {
printf("BIO_new is failed.\n");
ret = -1;
return ret;
}
/*read public key from pem file.*/
ret = BIO_read_filename(bp, priv_pem_file);
ec_key = PEM_read_bio_ECPrivateKey(bp, &ec_key, NULL, (void*)passwd);
if (ec_key == NULL) {
ret = -1;
printf("open_private_key failed to PEM_read_bio_ECPrivateKey Failed, ret=%d\n", ret);
goto finish;
}
private_evp_key = EVP_PKEY_new();
if (private_evp_key == NULL) {
ret = -1;
printf("open_public_key EVP_PKEY_new failed\n");
goto finish;
}
ret = EVP_PKEY_set1_EC_KEY(private_evp_key, ec_key);
if (ret != 1) {
ret = -1;
printf("EVP_PKEY_set1_EC_KEY failed\n");
goto finish;
}
ret = EVP_PKEY_set_alias_type(private_evp_key, EVP_PKEY_SM2);
if (ret != 1) {
printf("EVP_PKEY_set_alias_type to EVP_PKEY_SM2 failed! ret = %d\n", ret);
ret = -1;
goto finish;
}
/*modifying a EVP_PKEY to use a different set of algorithms than the default.*/
/*do signature.*/
evp_md_ctx = EVP_MD_CTX_new();
if (evp_md_ctx == NULL) {
printf("EVP_MD_CTX_new failed.\n");
ret = -1;
goto finish;
}
EVP_MD_CTX_init(evp_md_ctx);
ret = EVP_SignInit_ex(evp_md_ctx, EVP_sm3(), NULL);
if (ret != 1) {
printf("EVP_SignInit_ex failed, ret = %d\n", ret);
goto finish;
}
ret = EVP_SignUpdate(evp_md_ctx, sign_rom, sign_rom_len);
if (ret != 1) {
printf("EVP_SignUpdate failed, ret = %d\n", ret);
goto finish;
}
ret = EVP_SignFinal(evp_md_ctx, result, (unsigned int*)result_len, private_evp_key);
if (ret != 1) {
printf("EVP_SignFinal failed, ret = %d\n", ret);
goto finish;
}
ret = 0;
finish:
if (private_evp_key != NULL)
EVP_PKEY_free(private_evp_key);
if (ctx != NULL)
EVP_PKEY_CTX_free(ctx);
if (bp != NULL)
BIO_free(bp);
if (ec_key != NULL)
EC_KEY_free(ec_key);
return ret;
}
2.6 SM2 verify
int openssl_evp_sm2_verify(unsigned char *sign_rom, size_t sign_rom_len,
unsigned char *result, size_t result_len,
const unsigned char *pub_pem_file)
{
int ret = 0;
FILE *fp = NULL;
BIO *bp = NULL;
EVP_MD_CTX *evp_md_ctx = NULL;
EC_KEY *ec_key = NULL;
EVP_PKEY* public_evp_key = NULL;
/*Check the user input.*/
if (sign_rom == NULL || sign_rom_len == 0 || result == NULL || result_len == 0) {
printf("input parameters error, content or len is NULL or 0.\n");
ret = -1;
goto finish;
}
if (NULL == pub_pem_file) {
printf("input pem_file name is invalid\n");
ret = -1;
goto finish;
}
fp = fopen((const char*)pub_pem_file, "r");
if (NULL == fp) {
printf("input pem_file is not exit.\n");
ret = -1;
goto finish;
}
fclose(fp);
fp = NULL;
/*read public key from pem file to private_evp_key*/
//OpenSSL_add_all_algorithms();
bp = BIO_new(BIO_s_file());
if (bp == NULL) {
printf("BIO_new is failed.\n");
ret = -1;
return ret;
}
/*read public key from pem file.*/
ret = BIO_read_filename(bp, pub_pem_file);
ec_key = PEM_read_bio_EC_PUBKEY(bp, NULL, NULL, NULL);
if (ec_key == NULL) {
ret = -1;
printf("open_public_key failed to PEM_read_bio_EC_PUBKEY Failed, ret=%d\n", ret);
goto finish;
}
public_evp_key = EVP_PKEY_new();
if (public_evp_key == NULL) {
printf("open_public_key EVP_PKEY_new failed\n");
ret = -1;
goto finish;
}
ret = EVP_PKEY_set1_EC_KEY(public_evp_key, ec_key);
if (ret != 1) {
ret = -1;
printf("EVP_PKEY_set1_EC_KEY failed\n");
goto finish;
}
ret = EVP_PKEY_set_alias_type(public_evp_key, EVP_PKEY_SM2);
if (ret != 1) {
printf("EVP_PKEY_set_alias_type to EVP_PKEY_SM2 failed! ret = %d\n", ret);
ret = -1;
goto finish;
}
/*modifying a EVP_PKEY to use a different set of algorithms than the default.*/
/*do verify*/
evp_md_ctx = EVP_MD_CTX_new();
if (evp_md_ctx == NULL) {
printf("EVP_MD_CTX_new failed.\n");
ret = -1;
goto finish;
}
EVP_MD_CTX_init(evp_md_ctx);
ret = EVP_VerifyInit_ex(evp_md_ctx, EVP_sm3(), NULL);
if (ret != 1) {
printf("EVP_VerifyInit_ex failed, ret = %d\n", ret);
ret = -1;
goto finish;
}
ret = EVP_VerifyUpdate(evp_md_ctx, result, result_len);
if (ret != 1) {
printf("EVP_VerifyUpdate failed, ret = %d\n", ret);
ret = -1;
goto finish;
}
ret = EVP_VerifyFinal(evp_md_ctx, sign_rom, (unsigned int)sign_rom_len, public_evp_key);
if (ret != 1) {
printf("EVP_VerifyFinal failed, ret = %d\n", ret);
ret = -1;
goto finish;
}
ret = 0;
finish:
if (bp != NULL)
BIO_free(bp);
if (evp_md_ctx != NULL)
EVP_MD_CTX_free(evp_md_ctx);
if (ec_key != NULL)
EC_KEY_free(ec_key);
if (public_evp_key != NULL)
EVP_PKEY_free(public_evp_key);
return ret;
}
Code Share
You get the driver code and testcase from my github as follows:
https://github.com/carloscn/cryptography
That is a Clion format project.
Reference List
[1]https://blog.csdn.net/weixin_41761608/article/details/107623909
[2]https://www.openssl.org/docs/man1.1.0/man3/EVP_PKEY_decrypt.html
[3]https://www.openssl.org/docs/man1.1.0/man3/EVP_PKEY_encrypt.html
[4]http://man.sourcentral.org/debian-unstable/3+PEM_read_PUBKEY
[5]https://stackoverflow.com/questions/31482186/generating-a-pem-with-openssl-in-c
[6]https://www.cnblogs.com/cocoajin/p/6134382.html
[7]https://github.com/greendow/SM2-signature-creation-and-verification/