此文章基于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也很重要。