基于 gmssl实现的sm2加密(C++)

项目中需要用到sm2加密,在网上搜索了一下相关的库,发现只有openssl和gmssl这两个库可以用,于是基于gmssl库做了封装,gmssl的版本是:GmSSL 2.5.4 - OpenSSL 1.1.0d  19 Jun 2019

搞这个库的确要费不少功夫,现在分享出来给需要的人。目前我是用在linux环境中,因此编译成linux动态库,并且屏蔽相关库的头文件和符号,只暴露sm2加解密相关的接口符号,gmssl库通过静态库的方式引用。

关于sm2加密有几个比较重要的参数,第一个是椭圆曲线参数,第二个是密文编码方式,第三个是哈希算法,目前我们用的都是固定的参数,所以封装的时候没有提供参数选择这些功能,需要的可以自行扩展。下面的sm2加密相关参数为:使用默认的椭圆曲线参数(sm2p256v1),ASN.1/DER编码方式(C1|C3|C2编码方式) ,哈希(杂凑)算法使用sm3

一些初学者可能还不大懂怎么引用外部库,我把我自己的工程上传到github上了,可以作为参考,gmssl库我已经编译好,生成的是静态库,但是gmssl库的编译其实还是应该用自己的机器去编译的,因为自己拿我编译好的静态库去用的话,不同的linux版本有可能不兼容。gmssl库文件我也放在该工程下了,可以不用去官网找,文件为:GmSSL-master.zip。 github地址:qingfeng1992/gmutil: 基于gmssl加密库封装sm2加解密方法 (github.com)

可以直接使用git clone git@github.com:qingfeng1992/gmutil.git 拉取代码

下载下来后请先看README.txt后再操作

gmutil.h头文件:

#ifndef __GM_UTIL_H__
#define __GM_UTIL_H__
#include <string>
using namespace std;

#ifdef _WIN32
#define UNIX_EXPORT
#else
#define UNIX_EXPORT __attribute__((visibility("default")))
#endif
// namespace GM
//{

// 错误码
enum EGMErrorCode
{
    GM_UTIL_CODE_OK = 0,
    GM_UTIL_CODE_CREATE_EV_KEY_FAILED, // 密钥解析失败
    GM_UTIL_CODE_SM2_ENCRYPT_FAILED,   // SM2加密失败
    GM_UTIL_CODE_SM2_DECRYPT_FAILED,   // SM2解密失败
    GM_UTIL_CODE_NOT_SM2P256V1,        // 不是默认的sm2p256v1椭圆曲线参数
    GM_UTIL_CODE_INIT_BIO_FAILED,      // 初始化BIO失败
    GM_UTIL_CODE_CIPHER_TEXT_TO_BIO_FAILED,      // 加密数据存储到BIO失败
    GM_UTIL_CODE_BIO_DATA_TO_MEM_FAILED,      // BIO数据转存到缓冲区失败
    GM_UTIL_CODE_BIO_DATA_TO_CIPHER_TEXT_FAILED,      // BIO数据转成Ciphertext结构失败
};
extern "C"
{
    // 从文件中读入公钥/私钥数据到string中,失败返回空字符串
    UNIX_EXPORT string GmReadKeyFromFile(string strFileName);

    /**
     * @brief sm2加密,使用默认的椭圆曲线参数(NID_sm2p256v1),ASN.1/DER编码方式(C1|C3|C2编码方式) ,哈希(杂凑)算法使用sm3
     * @param strPubKey 公钥数据
     * @param strIn 需要加密的数据
     * @param strCiphertext 密文,加密后的密文不是可见字符
     * @return 返回GM_UTIL_ERR_OK表示加密成功,否则失败,具体见EGMErrorCode定义
     */
    UNIX_EXPORT int GmSm2Encrypt(string strPubKey, const string &strIn, string &strCiphertext);

    /**
     * @brief sm2解密,使用默认的椭圆曲线参数(NID_sm2p256v1),ASN.1/DER编码方式(C1|C3|C2编码方式),哈希(杂凑)算法使用sm3
     * @param strPubKeyFile 私钥数据
     * @param strCiphertext 需要解密的数据(不是可见字符)
     * @param strOut 解密后的明文
     * @return 返回GM_UTIL_ERR_OK表示解密成功,否则失败,具体见EGMErrorCode定义
     */
    UNIX_EXPORT int GmSm2Decrypt(string strPriKey, const string &strCiphertext, string &strOut);

    // 将二进制数据转换成十六进制字符串
    UNIX_EXPORT string GmByte2HexStr(const string &data, bool bLowerCase = true);

    // 将十六进制字符串转换成二进制
    UNIX_EXPORT string GmHexStr2Byte(const string& hex, bool bLowerCase = true);
}

// } // namespace GM
#endif // end __GM_UTIL_H__

gmutil.cpp文件:

#include "gmutil.h"
#include <iostream>
#include<fstream>
#include<sstream>
#include <stdio.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/sm2.h>
#include <openssl/bio.h>
//using namespace GM;

/**
 * @brief 使用公钥/私钥数据获取EV_KEY对象
 * @param key 公钥/私钥数据
 * @param is_public 是否公钥
 * @return 失败返回NULL
 */
static EC_KEY *CreateEC(unsigned char *key, int is_public)
{
    EC_KEY *ec_key = NULL;
    BIO *keybio = NULL;
    keybio = BIO_new_mem_buf(key, -1);
 
    if (keybio == NULL) {
        printf("%s", "[BIO_new_mem_buf]->key len=%d,Failed to Get Key", strlen((char *) key));
        return NULL;
    }
 
    if (is_public) {
        ec_key = PEM_read_bio_EC_PUBKEY(keybio, NULL, NULL, NULL);
    } else {
        ec_key = PEM_read_bio_ECPrivateKey(keybio, NULL, NULL, NULL);
    }
 
    if (ec_key == NULL) {
        printf("Failed to Get Key");
        BIO_free_all(keybio);
        return NULL;
    }

    BIO_free_all(keybio); // 此处是不是要free?
    return ec_key;
}

int GmSm2Encrypt(string strPubKey, const string &strIn, string &strCiphertext)
{
    EC_KEY *evKey = CreateEC((unsigned char *)strPubKey.c_str(), 1);
    if (NULL == evKey)
    {
        return GM_UTIL_CODE_CREATE_EV_KEY_FAILED;
    }
    
    // 目前只支持默认的sm2p256v1椭圆曲线参数
    if (!EC_KEY_is_sm2p256v1(evKey))
    {
        EC_KEY_free(evKey);
        return GM_UTIL_CODE_NOT_SM2P256V1;
    }

    // 加密后的密文会比明文长97字节
    unsigned char *buff = NULL;
    size_t outLen = 0;
    SM2CiphertextValue *cval = NULL;
    size_t mlen, clen;
    unsigned char *p;

    if (NULL == (cval = SM2_do_encrypt(EVP_sm3(), (const unsigned char *)strIn.c_str(), strIn.size(), evKey)))
    {
        EC_KEY_free(evKey);
        return GM_UTIL_CODE_SM2_ENCRYPT_FAILED;
    }
    
    BIO *bOut = BIO_new(BIO_s_mem());
    if (NULL == bOut)
    {
        EC_KEY_free(evKey);
        SM2CiphertextValue_free(cval);
        return GM_UTIL_CODE_INIT_BIO_FAILED;
    }

    if (i2d_SM2CiphertextValue_bio(bOut, cval) <= 0)
    {
        SM2CiphertextValue_free(cval);
        BIO_free(bOut);
        EC_KEY_free(evKey);
        return GM_UTIL_CODE_CIPHER_TEXT_TO_BIO_FAILED;
    }

    if (0 == (outLen = BIO_get_mem_data(bOut, (char **)&buff)))
    {
        SM2CiphertextValue_free(cval);
        BIO_free(bOut);
        EC_KEY_free(evKey);
        return GM_UTIL_CODE_BIO_DATA_TO_MEM_FAILED;
    }

    strCiphertext.assign((char *)buff, outLen);
    // 释放内存
    SM2CiphertextValue_free(cval);
    BIO_free(bOut);
    EC_KEY_free(evKey);
    // OPENSSL_free(buff); //此处释放会挂掉,不应该free,应该是在BIO_free的时候内存已经被释放掉
    return GM_UTIL_CODE_OK;
}


int GmSm2Decrypt(string strPriKey, const string &strCiphertext, string &strOut)
{
    EC_KEY *evKey = CreateEC((unsigned char *)strPriKey.c_str(), 0);
    if (NULL == evKey)
    {
        return GM_UTIL_CODE_CREATE_EV_KEY_FAILED;
    }

    if (!EC_KEY_is_sm2p256v1(evKey))
    {
        EC_KEY_free(evKey);
        return GM_UTIL_CODE_NOT_SM2P256V1;
    }
    BIO *bIn = NULL;
    bIn = BIO_new_mem_buf(strCiphertext.c_str(), strCiphertext.size());
    if (bIn == NULL)
    {
        EC_KEY_free(evKey);
        return GM_UTIL_CODE_INIT_BIO_FAILED;
    }

    int ret = 0;
	SM2CiphertextValue *cval = NULL;
	void *buf = NULL;
	size_t siz;
    const EVP_MD* md = EVP_sm3();

    if (NULL == (cval = d2i_SM2CiphertextValue_bio(bIn, NULL)))
    {
        BIO_free(bIn);
        EC_KEY_free(evKey);
        return GM_UTIL_CODE_BIO_DATA_TO_CIPHER_TEXT_FAILED;
    }

	if (0 == SM2_do_decrypt(md, cval, NULL, &siz, evKey) || !(buf = OPENSSL_malloc(siz)))
    {
        BIO_free(bIn);
        SM2CiphertextValue_free(cval);
        EC_KEY_free(evKey);
		return GM_UTIL_CODE_SM2_DECRYPT_FAILED;
	}

    if (0 == SM2_do_decrypt(md, cval, (unsigned char*)buf, &siz, evKey))
    {
        BIO_free(bIn);
        SM2CiphertextValue_free(cval);
        OPENSSL_free(buf);
        EC_KEY_free(evKey);
        return GM_UTIL_CODE_SM2_DECRYPT_FAILED;
    }
    
    strOut.assign((char*)buf, siz);
    // 释放内存
    BIO_free(bIn);
    SM2CiphertextValue_free(cval);
	OPENSSL_free(buf);
    EC_KEY_free(evKey);
    return GM_UTIL_CODE_OK;
}

static streamsize Read(istream &stream, char *buffer, streamsize count)
{
    streamsize reads = stream.rdbuf()->sgetn(buffer, count);
    stream.rdstate();
    stream.peek();
    return reads;
}

string GmReadKeyFromFile(string strFileName)
{
    fstream myfile;
	myfile.open(strFileName, ifstream::in | ifstream::binary);
	if (!myfile.is_open())
    {
        return "";
    }

    char buff[1024];
    std::ostringstream oss;
    int len;
    while (!myfile.eof())
    {
        size_t read = Read(myfile, buff, sizeof(buff));
        oss << string(buff, read);
    }

    myfile.close();
    return oss.str();
}

static char sDigit1[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
static char sDigit2[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
string GmByte2HexStr(const string& data, bool bLowerCase)
{
    char *sDigit = sDigit1;
    if (!bLowerCase)
    {
        sDigit = sDigit2;
    }
    const char* pData = data.c_str();
    char cTemp;
    string strHex;
    for (unsigned int i = 0; i < data.size(); i++)
    {
        cTemp = *pData;
        pData++;
        strHex += sDigit[(cTemp >> 4) & 0x0F];
        strHex += sDigit[cTemp & 0x0F];
    }

    return strHex;
}

string GmHexStr2Byte(const string& hex, bool bLowerCase)
{
    if (hex.size() % 2 != 0)
    {
        // 十六进制字符串必须是偶数长度
        return "";
    }

    char chA = 'a';
    if (!bLowerCase)
    {
        chA = 'A';
    }

    std::ostringstream oss;
    for (int i = 0; i < hex.size(); i += 2)
    {
        unsigned int highBit;
        if (hex[i] >= '0' && hex[i] <= '9')
        {
            highBit = hex[i] - '0';
        }
        else
        {
            highBit = hex[i] - chA + 10;
        }
        unsigned int lowBit;
        if (hex[i + 1] >= '0' && hex[i + 1] <= '9')
        {
            lowBit = hex[i + 1] - '0';
        }
        else
        {
            lowBit = hex[i + 1] - chA + 10;
        }
        unsigned char ch = (highBit << 4) + lowBit; 
        oss << ch;
    }

    return oss.str();
}

下面是测试验证test.cpp:

#include "gmutil.h"
#include <iostream>
#include <sstream>

int main(int argc, char** argv)
{
    string strPriKey = GmReadKeyFromFile("sm2_server_private_key.key");
    string strPubKey = GmReadKeyFromFile("sm2_server_public_key.key");
    string strText = "hello world, this is a test";
    string strCipher;
    string strOut;
    std::cout << "plaintext:" << strText << std::endl;
    int nRet = GmSm2Encrypt(strPubKey, strText, strCipher);
    if (GM_UTIL_CODE_OK != nRet)
    {
        cout << "GmSm2Encrypt fail" << endl;
    }
    string strCipherTextHex = GmByte2HexStr(strCipher);
    cout << "hex ciper text:" << strCipherTextHex << endl;
    string strCipher1 = GmHexStr2Byte(strCipherTextHex);
    if (strCipher1 == strCipher)
    {
        cout << "conver hex str to byte sucess" << endl;
    }
    nRet = GmSm2Decrypt(strPriKey, strCipher1, strOut);
    std::cout << "after decrypt:" << strOut << std::endl;

    return 0;
}

编译的时候要加这几个关键的编译参数:-fvisibility=hidden -Wl,-Bsymbolic -Wl,--exclude-libs,ALL

用来屏蔽动态库中的符号的。

gmssl库相关函数的文档可以参考官网:国密SM2椭圆曲线密码标准 (gmssl.org)

还有可以直接参考gmssl源码中的sm2test.c以及sm2utl.c,里面有相关的代码

还有gmssl库用到openssl库的很多函数比如BIO等,可以参数openssl官网来使用

参考文章:

(20条消息) 基于GMSSL的SM2加解密测试_viqjeee的博客-CSDN博客_gmssl sm2

(20条消息) C语言SM2算法实现(基于GMSSL)_张志翔 ̮的博客-CSDN博客_sm2实现 --这个最详细,直接有完整代码

(25条消息) SM2算法全套(基于GMSSL)_wincent1的博客-CSDN博客_gmssl sm2算法

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值