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

此文章基于https://blog.csdn.net/hacker_lpy/article/details/124211114提供的方法,经过测试修正代码后,与https://the-x.cn/zh-cn/cryptography/Sm2.aspx提供的工具的加密解密数据一致。
大概做了如下变更:
1、修正SM2加密解密使用C1C3C2的顺序(手动调整的)。
2、修正SM2结果不正确的问题。
3、增加错误码信息的获取GmGetErrorMessage。
4、增加将公钥、私钥PEM字符串转十六进制字符串的方法。
5、增加将公钥十六进制转PEM字符串的方法。
但是私钥十六进制转字符串始终未能成功,如果有通过测试的朋友,请留言告知,不胜感激!

修改后的gmutil.cpp如下:

#include "../include/gmutil.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/sm2.h>
#include <openssl/bio.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/ec.h>
#include <openssl/x509.h>

using namespace std;

/**
 * @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("[BIO_new_mem_buf]->key len=%lu,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;
}

/**
 * @brief 加密
 *
 * @param strPubKey 公钥内容
 * @param strIn 原始字符串
 * @param strCiphertext 加密后内容
 * @return int 成功返回0,失败返回非零值
 */
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字节
    SM2CiphertextValue *cval = NULL;
    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;
    }

    const EC_GROUP *group = EC_KEY_get0_group(evKey);
    unsigned char *cipher_buf = (unsigned char *)malloc(strIn.size() + 97);
    unsigned char *cipher_text = cipher_buf;                                 // 因为i2o_SM2CiphertextValue会改掉指针(这是一个BUG)
    int cipher_text_len = i2o_SM2CiphertextValue(group, cval, &cipher_text); // 将标准密文cv转化为字符形式,存在cipher_buf中
    // C1 为随机产生的公钥,C2 为密文,与明文长度等长, C3 为 SM3 算法对明文数计算得到消息摘要,长度固定为 256 位
    // 转换成C1|C3|C2
    int c1Len = 65;
    int c2Len = strIn.size();
    int c3Len = 32;
    const char *buffer = (char *)cipher_buf;
    strCiphertext.assign(buffer, c1Len);
    strCiphertext.append(buffer + c1Len + c2Len, c3Len);
    strCiphertext.append(buffer + c1Len, c2Len);
    // 释放内存
    SM2CiphertextValue_free(cval);
    EC_KEY_free(evKey);
    free(cipher_buf);
    return GM_UTIL_CODE_OK;
}

/**
 * @brief SM2解密
 *
 * @param strPriKey 私钥字符串
 * @param strCiphertext 已加密的内容,顺序为C1|C3|C2
 * @param strOut 解密后的原始字符串
 * @return int 成功返回0,失败返回非零值
 */
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;
    }
    // 只支持默认的sm2p256v1椭圆曲线参数
    if (!EC_KEY_is_sm2p256v1(evKey))
    {
        EC_KEY_free(evKey);
        return GM_UTIL_CODE_NOT_SM2P256V1;
    }
    const EVP_MD *md = EVP_sm3();
    const EC_GROUP *group = EC_KEY_get0_group(evKey);
    // 顺序从C1|C3|C2改回C1|C2|C3
    int c1Len = 65;
    int c3Len = 32;
    int c2Len = strCiphertext.size() - c1Len - c3Len;
    if (c2Len < 0)
    {
        EC_KEY_free(evKey);
        return GM_UTIL_CODE_SM2_NOT_VALID_CIPHER;
    }
    const char *buffer = strCiphertext.c_str();
    string c1c2c3Cipher;
    if (buffer[0] != 0x04)
    {
        c1Len--;
        c2Len++;
        c1c2c3Cipher.append(1, 0x04);
    }
    c1c2c3Cipher.append(buffer, c1Len);                 // C1
    c1c2c3Cipher.append(buffer + c1Len + c3Len, c2Len); // C2
    c1c2c3Cipher.append(buffer + c1Len, c3Len);         // C3
    // cout << c1Len << " " << c2Len << " " << c3Len << endl;
    const unsigned char *data = (const unsigned char *)c1c2c3Cipher.c_str();
    SM2CiphertextValue *cval = o2i_SM2CiphertextValue(group, md, NULL, &data, c1c2c3Cipher.size());
    unsigned char *outbuff = (unsigned char *)malloc(c1c2c3Cipher.size() - 97);
    size_t outlen = 0;
    if (0 == SM2_do_decrypt(md, cval, outbuff, &outlen, evKey))
    {
        SM2CiphertextValue_free(cval);
        EC_KEY_free(evKey);
        return GM_UTIL_CODE_SM2_DECRYPT_FAILED;
    }
    strOut.assign((char *)outbuff, outlen);
    // 释放内存
    SM2CiphertextValue_free(cval);
    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];
    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';
    }

    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();
}

// 根据错误码获取错误信息
string GmGetErrorMessage(int code)
{
    const char *msgs[] = {
        "成功",
        "密钥解析失败",
        "SM2加密失败",
        "SM2解密失败",
        "不是SM2加密结果",
        "不是默认的sm2p256v1椭圆曲线参数",
        "初始化BIO失败",
        "加密数据存储到BIO失败",
        "BIO数据转存到缓冲区失败",
        "BIO数据转成Ciphertext结构失败",
    };
    int size = sizeof msgs / sizeof(char *);
    string msgstr = (code < size && code >= 0) ? msgs[code] : "未知错误";
    return string("(") + to_string(code) + ")" + msgstr;
}

string Sm2PubKeyPem2Hex(const string &pemPubKeyStr)
{
    EC_KEY *eckey = CreateEC((unsigned char *)pemPubKeyStr.c_str(), 1);
    if (!eckey)
        return "";

    EC_POINT *pub_key;
    unsigned char pubbuf[1024] = {0};
    pub_key = (EC_POINT *)EC_KEY_get0_public_key(eckey);
    EC_GROUP *group = (EC_GROUP *)EC_KEY_get0_group(eckey);
    int buflen = EC_POINT_point2oct(group, pub_key, EC_KEY_get_conv_form(eckey), pubbuf, sizeof(pubbuf), NULL);

    BIGNUM *pub_key_BIGNUM;
    pub_key_BIGNUM = BN_new();
    BN_bin2bn(pubbuf, buflen, pub_key_BIGNUM);
    string hexKey = BN_bn2hex(pub_key_BIGNUM);
    BN_free(pub_key_BIGNUM);
    return hexKey;
}

string Sm2PriKeyPem2Hex(const string &pemPriKeyStr)
{
    EC_KEY *eckey = CreateEC((unsigned char *)pemPriKeyStr.c_str(), 0);
    if (!eckey)
        return "";
    BIGNUM *private_key;
    private_key = BN_new();
    private_key = (BIGNUM *)EC_KEY_get0_private_key(eckey);
    string hexKey = BN_bn2hex(private_key);
    BN_free(private_key);

    return hexKey;
}

string Sm2PubKeyHex2Pem(const string &hex)
{
    ERR_load_crypto_strings();
    EC_KEY *eckey = EC_KEY_new_by_curve_name(NID_sm2p256v1);
    if (!eckey)
    {
        return "";
    }
    EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
    string bytes = GmHexStr2Byte(hex, false);
    const unsigned char *ptr = (const unsigned char *)bytes.c_str();
    if (!o2i_ECPublicKey(&eckey, &ptr, bytes.size()))
    {
        return "";
    }
    EVP_PKEY *pkey = EVP_PKEY_new();
    if (!EVP_PKEY_assign_EC_KEY(pkey, eckey))
    {
        return "";
    }

    BIO *bio = BIO_new(BIO_s_mem());
    if (!PEM_write_bio_PUBKEY(bio, pkey))
    {
        return "";
    }
    char *pemKey = NULL;
    long len = BIO_get_mem_data(bio, &pemKey);
    string str(pemKey, len);
    return str;
}

修改后的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_SM2_NOT_VALID_CIPHER,           // 不是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);

    UNIX_EXPORT string GmGetErrorMessage(int code); // 根据错误码获取错误信息

    UNIX_EXPORT string Sm2PubKeyPem2Hex(const string &pemPubKeyStr);
    UNIX_EXPORT string Sm2PriKeyPem2Hex(const string &pemPriKeyStr);
    UNIX_EXPORT string Sm2PubKeyHex2Pem(const string &hex);
}

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

测试代码如下:

#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");
    cout << "HexPubKey: " << Sm2PubKeyPem2Hex(strPubKey) << endl;
    cout << "HexPriKey: " << Sm2PriKeyPem2Hex(strPriKey) << endl;
    cout << "PemPubKey: " << endl << Sm2PubKeyHex2Pem(Sm2PubKeyPem2Hex(strPubKey)) << endl;
    string strText = "登录密码";
    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;
        return -1;
    }
	cout << "Chipher size: " << strCipher.size() << endl;
    string strCipherTextHex = GmByte2HexStr(strCipher);
    cout << "hex ciper Hex: " << strCipherTextHex << endl;

    strCipherTextHex = "049dc20e2fc3fa7ae2d325a4dc23f9e400f1a945f3dad3b62822d176ed8a9f204af9f585ef36b8fdfd6f91eb1dfb515fcff8c5a18cd0f94d38a3e2b5566e2cf8eeadaa20d608955522f2b09ea525ddd7fb68e6a18df585dea1080be35c3638992ca7d91372bcfd56f48d98991b53e22b3b78f010ae42d6195976ef7bd9914fb79d";
    string strCipher1 = GmHexStr2Byte(strCipherTextHex);
    nRet = GmSm2Decrypt(strPriKey, strCipher1, strOut);
    if (GM_UTIL_CODE_OK != nRet)
    {
        cout << "GmSm2Decrypt fail" << endl;
        return -1;
    }
    std::cout << "after decrypt: " << strOut << std::endl;

    return 0;
}

将上述三个文件替换掉该项目中的相应文件,make即可得到libgmutil.so和test文件。

附录:
GmSSL-master的编译:
如果是x86环境,只要执行./config,make即可得到libssl.a和libcrypto.a;
如果是ARM交叉编译:执行./config --cross-compile-prefix=aarch64-linux-gnu- shared no-asm,然后替换:sed -i “s/ -m64//g” Makefile,再make,注意no-asm很重要,替换-m64也很重要。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值