基于miracl大数库
基于miracl大数库的SM2加密,通过修改GitHub - acherstyx/SM2-CPP-Implementation: SM2 C++ implementation 这个项目实现,但是通过gcc内置的AddressSanitizer(ASan)内存检测工具发现有内存泄漏的风险,但是我并没有在程序中找到确切的位置(已经发现问题,并有了解决方案)。
移植大数库
# clone sm2加密项目
git clone --depth=1 https://github.com/acherstyx/SM2-CPP-Implementation.gitmkdir miracl
cd SM2-CPP-Implementation
# 安装miracl大数库,可以直接执行这个项目中的install_miracl.sh
sudo ./install_miracl.sh
# 也可以自己手动安装,脚本会默认安装到系统目录
mkdir miracl
cd miracl
wget https://codeload.github.com/miracl/MIRACL/zip/master -O MIRACL-master.zip
unzip -j -aa -L MIRACL-master.zip
bash linux64
# 如果你想将miracl库安装在你的系统
sudo cp miracl.a /usr/lib/libmiracl.a
sudo mkdir -p /usr/include/miracl
sudo cp *.h /usr/include/miracl
# 如果只是想要在项目中使用,则只需要将其添加到你的项目文件中,如,我有下面所示的一个项目目录结构
# ├── include
# ├── lib
# └── src
# 只需将生成的miracl.a移动到lib文件夹下,将你项目中需要的的头文件*.h移动到include文件夹下,在编译时指定库文件路径和头文件路径即可
# 选择性删除项目中的不必要文件,将编译的静态库移植到项目中
cd ..
rm -rf test README.md LICENSE install_miracl.sh .git .gitignore
mkdir lib
cp miracl/miracl.a ./lib
cp miracl/{big.h,ecn.h,miracl.h,mirdef.h} ./include
mkdir src/miracl
cp miracl/{big.h,ecn.h,big.cpp,ecn.cpp} ./src/miracl
rm -rf miracl
编写test.cpp实现SM2加密解密
在当前的文件夹下,即SM2-CPP-Implementation目录下,创建test.cpp
#include "sm2.h"
#include "precision.h"
#include <iostream>
string keyGenator(int n)
{
char chr[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
'+', '-', '='};
srand(time(NULL));
string strResult;
char buf[10] = {0};
for (int i=0; i<n; i++)
{
int idx = rand()%(n+1);
sprintf(buf, "%c", chr[idx]);
strResult.append(buf);
}
return strResult;
}
int main(int argc, char const *argv[])
{
Big x, y, key;
std::string ori_key = keyGenator(64);
std::cout<<ori_key<<std::endl;
unsigned char msg[100];
unsigned char msg_dec[100] = {0};
int count = 0;
for(auto c : ori_key){
msg[count] = (unsigned char)c;
count++;
}
int klen = 64;
int enc_klen;
unsigned char out[1000];
for (int i = 0; i < klen; i++)
cout << msg[i];
cout << "\n";
sm2_key_gen(x, y, key);
std::cout << "key generation:" << "\n";
std::cout << "\t" << "x: " << x << " y: " << y << "\n";
std::cout << "\t" << "private key(d): " << key << "\n";
enc_klen = sm2_enc(msg, klen, x, y, out);
std::cout << "encrypt result:" << "\n";
for (int i = 0; i < enc_klen; i++) {
cout << out[i];
};
cout << "\n";
cout << enc_klen<<"\n";
int ret = sm2_dec(out, enc_klen, key, msg_dec);
cout << "decrypt result: ";
for (int i = 0; i < ret; i++)
cout << msg_dec[i];
cout << "\n";
return 0;
}
修改CMakeLists.txt文件
cmake_minimum_required(VERSION 3.13)
project(simple_example)
set(CMAKE_CXX_STANDARD 11)
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -O2 -g -fsanitize=address")
# -fsanitize=address参数用于内存检查
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -O2 -g")
#include
include_directories(include)
include_directories(src)
add_library(big src/miracl/big.cpp src/miracl/big.h)
set(LIB_SOURCE
src/miracl/big.cpp
src/miracl/ecn.cpp)
set(HEADER_SOURCE
include/sm2.h
include/sm3.h)
set(MAIN_SOURCE
src/sm2/inner_utils.cpp
src/sm3/sm3_miracl_wrapper.cpp
src/sm3/sm3_reference.cpp
src/sm2/sm2_enc.cpp
src/sm2/sm2_sign.cpp)
# sm2 lib
add_library(sm2 ${HEADER_SOURCE} ${MAIN_SOURCE})
add_executable(test main.cpp ${LIB_SOURCE})
target_link_libraries(test ${CMAKE_CURRENT_SOURCE_DIR}/lib/miracl.a big sm2)
这个时候应该已经可以成功编译了,但是生成的可执行程序在运行时会报错
malloc(): corrupted top size
[1] 21915 IOT instruction (core dumped) ./test
查询之后,发现是很隐晦的错误,这个错误通常表明在程序中涉及到内存分配(malloc)时出现了问题,很可能是由于内存越界、重复释放相同内存或其他内存错误所致。它可能是由于对相同内存区域重复释放、释放非法指针或者内存越界等问题引起的。在使用ASan内存检查之后,发现是sm2_enc.cpp中的sm2_enc()这个函数出现了错误
修改sm2_enc.cpp中的sm2_enc()函数
int sm2_enc(unsigned char *msg, int msg_len, Big x, Big y, unsigned char *msg_after_enc) {
Big a, b, p, n;
Big g_x, g_y; // g = <g_x, g_y>
Big k; // random number for encrypt
FPECC ecc_config = Ecc256; // default ecc
miracl *mip;
mip = mirsys(20, 0);
epoint *g, *pb; // public key
Big x1, y1;
Big x2, y2;
// unsigned char zl[32], zr[32];
unsigned char zl[msg_len + 32], zr[msg_len + 32];
memset(zl, 0, sizeof(zl));
memset(zr, 0, sizeof(zr));
mip->IOBASE = 16;
// get ecc parameter
cinstr(p.getbig(), ecc_config.p);
cinstr(a.getbig(), ecc_config.a);
cinstr(b.getbig(), ecc_config.b);
cinstr(n.getbig(), ecc_config.n);
ecurve_init(a.getbig(), b.getbig(), p.getbig(), MR_PROJECTIVE);
// read g
cinstr(g_x.getbig(), ecc_config.x);
cinstr(g_y.getbig(), ecc_config.y);
g = epoint_init();
epoint_set(g_x.getbig(), g_y.getbig(), 0, g);
// read pb
pb = epoint_init();
epoint_set(x.getbig(), y.getbig(), 0, pb);
restart_encrypt:
// random k
struct timespec tn;
clock_gettime(CLOCK_REALTIME, &tn);
irand(unsigned(tn.tv_nsec));
bigrand(n.getbig(), k.getbig());
#ifdef SM2_ENC_DEBUG
cout << "[enc] rand k: " << k << "\n";
#endif
// c1
ecurve_mult(k.getbig(), g, g);
epoint_get(g, x1.getbig(), y1.getbig());
big_to_bytes(32, x1.getbig(), (char *) msg_after_enc, TRUE);
big_to_bytes(32, y1.getbig(), (char *) msg_after_enc + 32, TRUE);
#ifdef SM2_ENC_DEBUG
cout << "[enc] x1: " << x1 << " y1: " << y1 << "\n";
#endif
// c2, c2 ^= msg
ecurve_mult(k.getbig(), pb, pb);
epoint_get(pb, x2.getbig(), y2.getbig());
#ifdef SM2_ENC_DEBUG
cout << "[enc] x2: " << x2 << " y2: " << y2 << '\n';
#endif
big_to_bytes(32, x2.getbig(), (char *) zl, TRUE);
big_to_bytes(32, y2.getbig(), (char *) zr, TRUE);
if (kdf(zl, zr, msg_len, msg_after_enc + 64) == 0)
goto restart_encrypt;
for (int i = 0; i < msg_len; i++) {
msg_after_enc[i + 64] ^= msg[i];
}
// c3
unsigned char *temp = (unsigned char *) malloc(sizeof(unsigned char) * (32 + 32 + msg_len));
memcpy(temp, zl, 32);
memcpy(temp + msg_len, msg, msg_len);
// memcpy(temp + msg_len + 32, zr, msg_len);
memcpy(temp + 64, zr, 32);
SM3Calc(temp, 63 + msg_len, msg_after_enc + 64 + msg_len);
mip->IOBASE = 10;
free(temp);
epoint_free(g);
epoint_free(pb);
return msg_len + 96; // [0:64] C1, [64:msg_len+64]: C2->KDF^msg, [msg_len+64:msg_len+96] C3(sm3_hash)
}
除了修改sm2_enc()函数以外,还需要在sm2_key_gen()函数和sm2_dec()的末尾添加epoint_free(g); 和epoint_free(pb);释放创建epoint分配的内存
编译运行
修改完所有的文件之后,返回到SM2-CPP-Implementation目录下,进行编译并运行测试程序
# 编译
mkdir build
cd build
cmake ..
make
# 运行
./test
发现运行后会出现内存泄漏,但是可以成功运行了。
解决内存泄露的问题(补)
通过使用ASan工具进行测试,发现问题出现在mirsys() 函数上,这个函数的作用是初始化 MIRACL 环境。它通常在使用 MIRACL 库之前调用,以设置底层数据结构和内存分配等。这个调用在程序中只能发生一次,并且在使用完 MIRACL 库后,通常会调用 mirexit()
来释放分配的资源。
重复调用 mirsys()
可能会引发内存泄漏或其他错误,因为它会尝试重新分配内存并且无法正确释放之前分配的资源。
而在这个项目中,sm2_key_gen(),sm2_enc(),sm2_dec()这几个函数都在内部调用了mirsys()函数,这是导致内存泄漏的原因,我的解决办法是在main()函数中调用mirsys()进行初始化,然后将其作为参数传入这几个函数中,删掉这几个函数中的mirsys()调用,在整个程序退出之前调用mirexit()释放资源。
修改后,就没有内存泄漏的错误了。
基于Botan密码学库
要使用botan库进行SM2加密时,我移植的版本为Botan_3.2.0版本,这个版本在实现公钥加密时使用到了std::span等c20的新特性,**要是你的项目用的是c20之前的标准,可能会不适用,要么考虑其他的加密库,要么使用botan库的其他版本。** Botan的官网为https://botan.randombit.net/
移植botan库
# 下载botan源码,也可以直接去官网下载压缩包自行解压
git clone --depth=1 https://github.com/randombit/botan.git
mkdir test1
cd botan
# --prefix=后面加你要移植安装的位置,其他参数可以参考https://botan.randombit.net/handbook/building.html
./configure.py --prefix=../test1 --minimized-build --enable-modules=sm2,auto_rng,sha2_64,system_rng --disable-shared-library --build-targets=static
make -j12
make install
# 如果想要重新编译,可以执行make distclean后重新配置编译
# 进入你的安装目录,删除无用文件
cd ../test1
rm -rf bin share
编写test.cpp实现SM2加密解密
test.cpp在test1/目录下
#include <botan/auto_rng.h>
#include <botan/ec_group.h>
#include <botan/sm2.h>
#include <botan/pubkey.h>
#include <botan/x509_key.h>
#include <botan/pkcs8.h>
#include <iostream>
#include <vector>
using namespace Botan;
std::string keyGenator(int n)
{
char chr[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
'+', '-', '='};
srand(time(NULL));
std::string strResult;
char buf[10] = {0};
for (int i=0; i<n; i++)
{
int idx = rand()%(n+1);
sprintf(buf, "%c", chr[idx]);
strResult.append(buf);
}
return strResult;
}
int main(int argc, char const *argv[])
{
Botan::AutoSeeded_RNG rng;
//生成64位随机数
std::string ori_key = keyGenator(64);
std::cout<<"Encrypted text: "<<ori_key<<std::endl;
std::vector<uint8_t> plaintext(ori_key.begin(), ori_key.end());
// 选择SM2曲线参数
Botan::EC_Group ec_group("sm2p256v1");
// 生成SM2密钥对
Botan::SM2_PrivateKey private_key(rng, ec_group);
Botan::SM2_PrivateKey public_key(private_key);
// 输出公钥
std::string public_key_pem = Botan::X509::PEM_encode(public_key);
std::cout << "SM2 Public Key:" << std::endl;
std::cout << public_key_pem << std::endl;
// 输出私钥
std::string private_key_pem = Botan::PKCS8::PEM_encode(private_key);
std::cout << "SM2 Private Key:" << std::endl;
std::cout << private_key_pem << std::endl;
// 加密
Botan::PK_Encryptor_EME encryptor(public_key, rng,"SHA-256");
std::vector<uint8_t> ciphertext = encryptor.encrypt(plaintext.data(), plaintext.size(),rng);
// 解密
Botan::PK_Decryptor_EME decryptor(private_key, rng,"SHA-256");
Botan::secure_vector<uint8_t> decrypted = decryptor.decrypt(ciphertext);
// 打印解密后的消息
std::string decrypted_text(decrypted.begin(), decrypted.end());
std::cout << "Decrypted text: " << decrypted_text << std::endl;
return 0;
}
编写Makefile
Makefile文件的位置在test1/目录下
LDFLAGS += -L./lib/
CPPFLAGS += -I./include/botan-3/
all:
g++ test.cpp -o test $(CPPFLAGS) $(LDFLAGS) -lbotan-3 -fstack-protector -m64 -pthread -std=c++20
clean:
rm test
编译并执行
# 编译
make
# 执行
./test
# 执行后输出
# Encrypted text: whsMltWFYdP3KgXZJ7G47YiAgy=B=J2v-uHlln=GNPJj2rHL+XR428Gk5Hv4bx-b
# SM2 Public Key:
# -----BEGIN PUBLIC KEY-----
# MFswFQYJKoEcz1UBgi0BBggqgRzPVQGCLQNCAAQz9r1rIsbwQu8ueItZcoJAzwBF
# /+Jy4mLD5EQZ5jfDwSrhqGLpj+i1vxNe2yW12mRoJ5I9a9XuyZEAT5WabG0Z
# -----END PUBLIC KEY-----
#
# SM2 Private Key:
# -----BEGIN PRIVATE KEY-----
# MIGJAgEAMBUGCSqBHM9VAYItAQYIKoEcz1UBgi0EbTBrAgEBBCCFZNTNC5nVshnO
# 89MeGNHIsGolx0NhGkaYDFEjJFy7oaFEA0IABDP2vWsixvBC7y54i1lygkDPAEX/
# 4nLiYsPkRBnmN8PBKuGoYumP6LW/E17bJbXaZGgnkj1r1e7JkQBPlZpsbRk=
# -----END PRIVATE KEY-----
#
# Decrypted text: whsMltWFYdP3KgXZJ7G47YiAgy=B=J2v-uHlln=GNPJj2rHL+XR428Gk5Hv4bx-b
评论区