使用openssl的EVP接口使用sm2算法加解密等操作

本文详细介绍了如何使用OpenSSL的EVP接口实现国密SM2算法的加解密、签名验证及秘钥对生成。通过具体代码示例,展示了如何利用PEM格式的公私钥进行数据加密、解密、签名和验证,适用于Ubuntu 18.04环境下OpenSSL 1.1.1b的开发者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

OpenSSL 1.1.1版本提供了对国密SM2算法的支持。但是不知为什么这么设计,将sm2.h sm3.h sm4.h这些头文件放在源码的cryto/include/internall目录下,这样导致用户不能直接调用到sm2.crypt.c中实现的函数。鉴于此,若需要SM2加解密,需要用统一的EVP抽象接口。

本篇主要介绍如何用EVP接口实现国密SM2算法的加解密,签名验签,秘钥对生成等操作。公私钥以PEM格式形式传递。PEM相比于HEX数组具有更容易存储和分发等优点。

#pragma once
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/ec.h>
#include <openssl/ossl_typ.h>
#include <openssl/bn.h>
#include <openssl/crypto.h>
#include <openssl/opensslconf.h>
#include <openssl/obj_mac.h>
#include <openssl/err.h>
#include "log.h"

class SM2 {
private:
	/// <summary>
/// 通过公钥/私钥返回EVP_PKEY
/// </summary>
/// <param name="key">pem格式</param>
/// <param name="is_public"></param>
/// <param name="out_ecKey"></param>
/// <returns></returns>
	static bool CreateEVP_PKEY(unsigned char* key, int is_public, EVP_PKEY** out_ecKey);
public:	
	/// <summary>
	/// 根据私钥计算出公钥
	/// </summary>
	/// <param name="in_priKey"></param>
	/// <param name="out_pubKey"></param>
	/// <returns></returns>
	static bool PriKey2PubKey(string in_priKey, string& out_pubKey);
	/// <summary>
	/// 生成EC秘钥对 
	/// </summary>
	/// <param name="privKey">pem格式的私钥</param>
	/// <param name="pubKey">pem格式的公钥</param>
	/// <returns></returns>
	static int GenEcPairKey(string& out_priKey, string& out_pubKey);

	/// <summary>
	/// 签名 私钥加密 0成功
	/// </summary>
	/// <param name="in_buf">待签名数据</param>
	/// <param name="in_buflen">长度</param>
	/// <param name="out_sig">签名后数据</param>
	/// <param name="len_sig">签名数据长度</param>
	/// <param name="priKey">私钥pem格式</param>
	/// <returns></returns>
	static int Sign(string in_buf, int in_buflen, string& out_sig, int& len_sig, string priKey);

	/// <summary>
	/// 验签 公钥解密 0成功
	/// </summary>
	/// <param name="in_buf">待验签数据 明文</param>
	/// <param name="buflen">数据长度</param>
	/// <param name="sig">签名数据</param>
	/// <param name="siglen">签名数据长度</param>
	/// <param name="pubkey">公钥</param>
	/// <param name="keylen">公钥长度</param>
	/// <returns></returns>
	static int Verify(string in_buf, const int buflen, string sig, const int siglen, 
		string pubkey, const int keylen);

	/// <summary>
	/// 加密 公钥加密 0成功
	/// </summary>
	/// <param name="in_buf"></param>
	/// <param name="in_buflen"></param>
	/// <param name="out_encrypted"></param>
	/// <param name="len_encrypted"></param>
	/// <param name="pubKey">pem格式公钥</param>
	/// <returns></returns>
	static int Encrypt(string in_buf, int in_buflen, string& out_encrypted, int& len_encrypted, string pubKey);

	/// <summary>
	/// 解密 私钥解密 0成功
	/// </summary>
	/// <param name="in_buf"></param>
	/// <param name="in_buflen"></param>
	/// <param name="out_plaint"></param>
	/// <param name="len_plaint"></param>
	/// <param name="prikey">pem格式私钥</param>
	/// <returns></returns>
	static int Decrypt(string in_buf, int in_buflen, string& out_plaint, int& len_plaint, string prikey);
};

inline int SM2::GenEcPairKey(string& out_priKey, string& out_pubKey)
{
	EC_KEY* ecKey;
	EC_GROUP* ecGroup;
	int ret_val = -1;
	if (NULL == (ecKey = EC_KEY_new()))
	{
		return -1;
	}

	if (NULL == (ecGroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)))
	{
		EC_KEY_free(ecKey);
		return -2;
	}

	if (EC_KEY_set_group(ecKey, ecGroup) != 1)
	{
		EC_GROUP_free(ecGroup);
		EC_KEY_free(ecKey);
		return -3;
	}

	if (!EC_KEY_generate_key(ecKey))
	{
		EC_GROUP_free(ecGroup);
		EC_KEY_free(ecKey);
		return -3;
	}

	//可以从EC_KEY类型返回char*数组
	size_t pri_len;
	size_t pub_len;
	char* pri_key = NULL;
	char* pub_key = NULL;

	BIO* pri = BIO_new(BIO_s_mem());
	BIO* pub = BIO_new(BIO_s_mem());

	PEM_write_bio_ECPrivateKey(pri, ecKey, NULL, NULL, 0, NULL, NULL);
	PEM_write_bio_EC_PUBKEY(pub, ecKey);

	pri_len = BIO_pending(pri);
	pub_len = BIO_pending(pub);

	pri_key = new char[pri_len + 1];
	pub_key = new char[pub_len + 1];

	BIO_read(pri, pri_key, pri_len);
	BIO_read(pub, pub_key, pub_len);

	pri_key[pri_len] = '\0';
	pub_key[pub_len] = '\0';

	out_pubKey = pub_key;
	out_priKey = pri_key;

	BIO_free_all(pub);
	BIO_free_all(pri);
	delete[] pri_key;
	delete[] pub_key;
	EC_GROUP_free(ecGroup);
	EC_KEY_free(ecKey);
	return 0;
}

inline bool SM2::CreateEVP_PKEY(unsigned char* key, int is_public, EVP_PKEY** out_pKey)
{
	BIO* keybio = NULL;
	keybio = BIO_new_mem_buf(key, -1);

	if (keybio == NULL) {
		LOGE("BIO_new_mem_buf failed.\n");
		return false;
	}

	if (is_public) {
		//*out_ecKey = PEM_read_bio_EC_PUBKEY(keybio, NULL, NULL, NULL);
		*out_pKey = PEM_read_bio_PUBKEY(keybio, NULL, NULL, NULL);
	}
	else {
		//*out_ecKey = PEM_read_bio_ECPrivateKey(keybio, NULL, NULL, NULL);
		*out_pKey = PEM_read_bio_PrivateKey(keybio, NULL, NULL, NULL);
	}

	if (*out_pKey == NULL) {
		LOGE("Failed to Get Key");
		BIO_free(keybio);
		return false;
	}

	BIO_free(keybio);
	return true;
}

inline bool SM2::PriKey2PubKey(string in_priKey, string& out_pubKey)
{
	BIO* keybio = NULL;
	keybio = BIO_new_mem_buf(in_priKey.c_str(), -1);

	if (keybio == NULL) {
		LOGE("BIO_new_mem_buf failed.\n");
		return false;
	}

	EC_KEY* ecKey = PEM_read_bio_ECPrivateKey(keybio, NULL, NULL, NULL);
	if (ecKey == NULL)
	{
		LOGE("PEM_read_bio_ECPrivateKey failed.");
		BIO_free(keybio);
		return false;
	}

	BIO* pub = BIO_new(BIO_s_mem());
	PEM_write_bio_EC_PUBKEY(pub, ecKey);
	int pub_len = BIO_pending(pub);
	char* pub_key = new char[pub_len + 1];
	BIO_read(pub, pub_key, pub_len);
	pub_key[pub_len] = '\0';

	out_pubKey = pub_key;

	delete[] pub_key;
	BIO_free(pub);
	BIO_free(keybio);
	return true;
}

inline int SM2::Sign(string in_buf, int in_buflen, string& out_sig, int& len_sig, string priKey)
{
	int ret_val = 0;
	//通过私钥得到EC_KEY
	EC_KEY* eckey = NULL;
	BIO* keybio = NULL;
	keybio = BIO_new_mem_buf(priKey.c_str(), -1);
	if (keybio == NULL)
	{
		LOGE("BIO_new_mem_buf failed\n");
		return -1;
	}
	eckey = PEM_read_bio_ECPrivateKey(keybio, NULL, NULL, NULL);
	if (eckey==NULL)
	{
		LOGE("PEM_read_bio_ECPrivateKey failed\n");
		BIO_free(keybio);
		return -2;
	}

	unsigned char szSign[256] = { 0 };
	if (1 != ECDSA_sign(0, (const unsigned char*)in_buf.c_str(), in_buflen, szSign, (unsigned int*)&len_sig, eckey)) {
		LOGE("ECDSA_sign failed\n");
		ret_val = -3;
	}
	else
	{
		out_sig = string((char*)szSign, len_sig);
		ret_val = 0;
	}
	BIO_free(keybio);
	EC_KEY_free(eckey);
	return ret_val;
}

inline int SM2::Verify(string in_buf, const int buflen, string sig, const int siglen,
	string pubkey, const int keylen)
{
	int ret_val = 0;
	//通过公钥得到EC_KEY
	EC_KEY* eckey = NULL;
	BIO* keybio = NULL;
	keybio = BIO_new_mem_buf(pubkey.c_str(), -1);
	if (keybio == NULL)
	{
		LOGE("BIO_new_mem_buf failed\n");
		return -1;
	}
	eckey = PEM_read_bio_EC_PUBKEY(keybio, NULL, NULL, NULL);
	if (eckey == NULL)
	{
		LOGE("PEM_read_bio_EC_PUBKEY failed\n");
		BIO_free(keybio);
		return -2;
	}
	if (1 != ECDSA_verify(0, (const unsigned char*)in_buf.c_str(), buflen, 
		(const unsigned char*)sig.c_str(), siglen, eckey))
	{
		LOGE("ECDSA_verify failed\n");
		ret_val = -3;
	}
	else
	{
		ret_val = 0;
	}
	BIO_free(keybio);
	EC_KEY_free(eckey);

	return ret_val;
}


inline int SM2::Encrypt(string in_buf, int in_buflen, string& out_encrypted, int& len_encrypted, string pubKey)
{
	int ret = -1, i;
	EVP_PKEY_CTX * ectx = NULL;
	EVP_PKEY* pkey = NULL;
	EC_KEY* key_pair = NULL;
	unsigned char* ciphertext = NULL;
	size_t ciphertext_len, plaintext_len;

	CreateEVP_PKEY((unsigned char*)pubKey.c_str(), 1, &pkey);

	/* compute SM2 encryption */
	if ((EVP_PKEY_set_alias_type(pkey, EVP_PKEY_SM2)) != 1)
	{
		LOGE("EVP_PKEY_set_alias_type failed.");
		goto clean_up;
	}

	if (!(ectx = EVP_PKEY_CTX_new(pkey, NULL)))
	{
		LOGE("EVP_PKEY_CTX_new failed.");
		goto clean_up;
	}

	if ((EVP_PKEY_encrypt_init(ectx)) != 1)
	{
		LOGE("EVP_PKEY_encrypt failed.");
		goto clean_up;
	}

	if ((EVP_PKEY_encrypt(ectx, NULL, &ciphertext_len, (const unsigned char*)in_buf.c_str(), in_buflen)) != 1)
	{
		LOGE("EVP_PKEY_set_alias_type failed.");
		goto clean_up;
	}

	if (!(ciphertext = (unsigned char*)malloc(ciphertext_len)))
	{
		goto clean_up;
	}

	if ((EVP_PKEY_encrypt(ectx, ciphertext, &ciphertext_len, (const unsigned char*)in_buf.c_str(), in_buflen)) != 1)
	{
		LOGE("EVP_PKEY_encrypt failed.");
		goto clean_up;
	}
	out_encrypted = string((char*)ciphertext, ciphertext_len);
	len_encrypted = ciphertext_len;
	ret = 0;
clean_up:
	if (pkey)
	{
		EVP_PKEY_free(pkey);
	}

	if (ectx)
	{
		EVP_PKEY_CTX_free(ectx);
	}

	if (ciphertext)
	{
		free(ciphertext);
	}

	return ret;
}

inline int SM2::Decrypt(string in_buf, int in_buflen, string& out_plaint, int& len_plaint, string prikey)
{
	int ret = -1, i;
	EVP_PKEY_CTX* pctx = NULL, * ectx = NULL;
	EVP_PKEY* pkey = NULL;
	EC_KEY* key_pair = NULL;
	unsigned char * plaintext = NULL;
	size_t ciphertext_len, plaintext_len;

	CreateEVP_PKEY((unsigned char*)prikey.c_str(), 0, &pkey);

	if ((EVP_PKEY_set_alias_type(pkey, EVP_PKEY_SM2)) != 1)
	{
		LOGE("EVP_PKEY_set_alias_type failed.");
		goto clean_up;
	}

	if (!(ectx = EVP_PKEY_CTX_new(pkey, NULL)))
	{
		LOGE("EVP_PKEY_CTX_new failed.");
		goto clean_up;
	}

	/* compute SM2 decryption */
	if ((EVP_PKEY_decrypt_init(ectx)) != 1)
	{
		LOGE("EVP_PKEY_decrypt_init failed.");
		goto clean_up;
	}

	if ((EVP_PKEY_decrypt(ectx, NULL, &plaintext_len, (const unsigned char*)in_buf.c_str(), in_buflen)) != 1)
	{
		LOGE("EVP_PKEY_decrypt failed.");
		goto clean_up;
	}

	if (!(plaintext = (unsigned char*)malloc(plaintext_len)))
	{
		goto clean_up;
	}

	if ((EVP_PKEY_decrypt(ectx, plaintext, &plaintext_len, (const unsigned char*)in_buf.c_str(), in_buflen)) != 1)
	{
		LOGE("EVP_PKEY_decrypt failed.");
		goto clean_up;
	}
	out_plaint = string((char*)plaintext, plaintext_len);
	len_plaint = plaintext_len;
	ret = 0;
clean_up:
	if (pctx)
	{
		EVP_PKEY_CTX_free(pctx);
	}

	if (pkey)
	{
		EVP_PKEY_free(pkey);
	}

	if (ectx)
	{
		EVP_PKEY_CTX_free(ectx);
	}

	if (plaintext)
	{
		free(plaintext);
	}

	return ret;
}

主要参考文章:

https://blog.csdn.net/u012849539/article/details/105207489

https://blog.csdn.net/henter/article/details/84970970

但网上也有人使用sm2.h中的API进行加解密,但我不会操作,我的环境是ubuntu18.04 openssl1.1.1b,如有知道的请告知。

SM2.h定义如下

int sm2_encrypt(const EC_KEY *key, const EVP_MD *digest, const uint8_t *msg,size_t msg_len, uint8_t *ciphertext_buf, size_t *ciphertext_len);
int sm2_decrypt(const EC_KEY *key, const EVP_MD *digest, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *ptext_buf, size_t *ptext_len);

包含头文件如下:

#include <openssl/evp.h>
#include <openssl/sm2.h>
#include <openssl/sm3.h>

 

基于OpenSSL 3.0版本的SM2算法实现涉及到使用OpenSSL提供的API来完成SM2加密和解密操作。下面是一个简单的示例代码,展示了如何使用OpenSSL 3.0 API进行SM2的加密和解密。 ```cpp #include <iostream> #include <openssl/ec.h> #include <openssl/pem.h> #include <openssl/evp.h> #include <openssl/bn.h> // 初始化EVP加密上下文 static EVP_CIPHER_CTX *create_sm2_context() { EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (!ctx) { std::cerr << "EVP_CIPHER_CTX_new failed" << std::endl; return nullptr; } if (1 != EVP_EncryptInit_ex(ctx, EVP_sm4_ecb(), nullptr, nullptr, nullptr)) { std::cerr << "EVP_EncryptInit_ex failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return nullptr; } return ctx; } // SM2加密 bool sm2_encrypt(const unsigned char *plaintext, int plaintext_len, const unsigned char *key, unsigned char *ciphertext) { EVP_CIPHER_CTX *ctx = create_sm2_context(); if (!ctx) { return false; } int len = 0; int ciphertext_len = 0; if (1 != EVP_EncryptInit_ex(ctx, nullptr, nullptr, key, nullptr)) { std::cerr << "EVP_EncryptInit_ex failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return false; } if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) { std::cerr << "EVP_EncryptUpdate failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return false; } ciphertext_len = len; if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) { std::cerr << "EVP_EncryptFinal_ex failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return false; } ciphertext_len += len; EVP_CIPHER_CTX_free(ctx); return true; } // SM2解密 bool sm2_decrypt(const unsigned char *ciphertext, int ciphertext_len, const unsigned char *key, unsigned char *plaintext) { EVP_CIPHER_CTX *ctx = create_sm2_context(); if (!ctx) { return false; } int len = 0; int plaintext_len = 0; if (1 != EVP_DecryptInit_ex(ctx, nullptr, nullptr, key, nullptr)) { std::cerr << "EVP_DecryptInit_ex failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return false; } if (1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) { std::cerr << "EVP_DecryptUpdate failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return false; } plaintext_len = len; if (1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) { std::cerr << "EVP_DecryptFinal_ex failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return false; } plaintext_len += len; EVP_CIPHER_CTX_free(ctx); return true; } int main() { // 这里需要提供SM2公钥和私钥来完成加密和解密 // 以下是示例密钥,实际使用时应使用有效的密钥对 const unsigned char public_key[] = { /* 公钥数据 */ }; const unsigned char private_key[] = { /* 私钥数据 */ }; const unsigned char data[] = "This is a test message for SM2 encryption."; unsigned char encrypted[256]; unsigned char decrypted[256]; int encrypted_len, decrypted_len; // 加密 if (sm2_encrypt(data, sizeof(data), public_key, encrypted)) { encrypted_len = sizeof(data); std::cout << "Encrypted text is: "; for (int i = 0; i < encrypted_len; i++) { std::cout << std::hex << static_cast<int>(encrypted[i]); } std::cout << std::endl; } // 解密 if (sm2_decrypt(encrypted, encrypted_len, private_key, decrypted)) { decrypted_len = sizeof(data); std::cout << "Decrypted text is: "; std::cout.write(reinterpret_cast<const char*>(decrypted), decrypted_len); std::cout << std::endl; } return 0; } ``` 注意: 1. 上述代码仅作为示例,实际使用时需要替换示例中的`public_key`和`private_key`为实际有效的SM2公钥和私钥数据。 2. 代码中的加密和解密函数是简化版本,没有处理错误情况和资源释放的完整逻辑。 3. 在实际环境中,密钥和数据的处理需要更加安全和完整,可能涉及到密钥存储、数据填充和错误处理等。
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zy100Papa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值