侧边栏壁纸
博主头像
Into The Abyss 博主等级

My Life is a Death Race

  • 累计撰写 34 篇文章
  • 累计创建 7 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

c/c++移植第三方库实现sm2算法

Administrator
2024-01-02 / 0 评论 / 0 点赞 / 454 阅读 / 0 字

基于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
0
c++

评论区