一、背景
因为密码学有很多较快的算法是基于c或c++纂修,而工程上主要以go语言为主,所以在此梳理一些go调用c常见问题和用例。
有很多奇特的方式进行传输,但是想要性能最优还是以指针传输作为主要传输方式。
一些简单的计算可以直接使用c编写成.h进行引用,但在工程部署常常拥有大量依赖库,若在服务器上部署时间太慢,还可能存在网络问题。所以最佳方式是将所有依赖库编译成动态库.so和.dylib供部署方使用。
二、cgo调用
1.cgo依赖库调用需要建设环境:
CGO_LDFLAGS | -L/Users/admin/Desktop/pir/pir_arm64/pir_cpp/cmake_arm -lpir | -L{path} -l{动态库}:比如libpir.so |
DYLD_LIBRARY_PATH | /Users/admin/Desktop/pir/pir_arm64/pir_cpp/cmake_arm | 动态库位置 |
CGO_CFLAGS | -I/Users/admin/Desktop/pir/pir_arm64/pir_cpp/thirdparty/pir | .h头文件位置 |
可以在环境中export全局设置,也可以启用之前单次引入:
CGO_CFLAGS="-I${PIR_HOME}/pir_cpp/thirdparty/pir"
DYLD_LIBRARY_PATH="${PIR_HOME}/pir_cpp/build"
CGO_LDFLAGS="-L${PIR_HOME}/pir_cpp/build -lpir" go run main.go
注意:同时饮用两个依赖库:
CGO_ENABLED=1 CGO_CFLAGS="-I${PIR_HOME}/pir_cpp/thirdparty/pir -I${GMSSL_HOME}/include"
CGO_LDFLAGS="-L${PIR_HOME}/pir_cpp/build -lpir2
-L${GMSSL_HOME}/lib -lgmcrypto -lgmssl"
go build -tags=jsoniter -ldflags "$(API_LINK_OPTIONS)" -o ${BIN_PATH}/${API_APP_NAME} ${API_CMD_PATH}
在Goland中配置全景变量CGO_LDFLAGS时不要加双引号。
2.go如何调用c函数并获得计算结果(方法很多,介绍最为稳定,速度最快的方案)
(1)数据结构简单:
func OprfBlind(id []byte, key []byte) ([]byte, error) {
if len(key_receiver) != 32 {
return nil, ErrInvalidKey
}
if id_receiver == nil {
return nil, ErrEmptyParameter
}
size := C.size_t(32)
array := C.malloc(size)
defer C.free(array)
C.oprf_mul_value((*C.char)(unsafe.Pointer(&id[0])), (*C.char)(unsafe.Pointer(&key[0])), (*C.char)(array), (C.int(len(id)))) //可以返回整数作为密文的长度
ciphertext := C.GoBytes(unsafe.Pointer(array), C.int(32)) //知晓返回的数据长度为32
return ciphertext, nil
}
(2)结构较为复杂,借助结构体传输。
func Oprf2Key(ciphertexts_r []byte, key_sender []byte) ([]byte, error) {
if len(ciphertexts_r) == 0 {
return nil, ErrInvalidPoint
}
if len(key_sender) != 32 {
return nil, ErrInvalidKey
}
key_sender = string2Key(key_sender, 32)
cgodata := (*C.cgoData)(C.malloc(C.size_t(unsafe.Sizeof(C.cgoData{}))))
defer C.free(unsafe.Pointer(cgodata))
C.oprf_mul_A((*C.char)(unsafe.Pointer(&ciphertexts_r[0])), (*C.char)(unsafe.Pointer(&key_sender[0])), cgodata, (C.int)(len(ciphertexts_r)))
ciphertext := C.GoBytes(unsafe.Pointer(cgodata.data), C.int(32))
return ciphertext, nil
}
typedef struct {
char *data;
int data_len;
}cgoData;
#cgodata.data在go中没有给它分配内存,所以我们需要在c++中分配:
int oprf_div_A(const char *ids, const char *A, cgoData *plaintext, int size) {
myECC ecc;
plaintext->data = new char [32];
int flag = ecc.point_div(A, ids, plaintext->data);
return flag;
}
func SealInit(NumberItems int, MaxSizeItem int) ([]unsafe.Pointer, error) {
if NumberItems <= 0 || MaxSizeItem <= 0 {
return nil, errors.New("sealGenerateQuery Error: NumberItems,SizePerItem should be >0")
}
cSealData := (*C.SealData)(C.malloc(C.size_t(unsafe.Sizeof(C.SealData{}))))
sealParams := make([]unsafe.Pointer, 2)
defer C.free(unsafe.Pointer(cSealData))
cSealData.poly_degree = C.int(4096)
cSealData.logt = C.int(20)
cSealData.number_of_items = C.int(NumberItems)
cSealData.size_per_item = C.int(MaxSizeItem % 10000)
sealParams[0] = C.seal_init(cSealData)
if MaxSizeItem > 10000 {
cSealData.size_per_item = C.int(10000)
sealParams[1] = C.seal_init(cSealData)
}
return sealParams, nil
}
(3)指针传递多个参数:
三、bug修复:
1.内存如何分配:
___go_build_data_create_go(90164,0x16f6f3000) malloc: Heap corruption detected, free list is damaged at 0x6000002fc000
*** Incorrect guard value: 15577313418704380084
___go_build_data_create_go(90164,0x16f6f3000) malloc: *** set a breakpoint in malloc_error_break to debug
func EncryptElement(values []byte, key []byte) ([]byte, error) {
if len(values) == 0 || len(values) == 0 {
return nil, ErrEmptyParameter
}
if values == nil || key == nil {
return nil, ErrEmptyParameter
}
size := C.size_t(16 * int((len(values)+31)/16)) //在内存一定多分配一些!!!
array := C.malloc(size) //分配内存和释放\也可以在内部生成一定要大一些。
defer C.free(array)
C.encrypt_element((*C.char)(unsafe.Pointer(&values[0])), (*C.char)(unsafe.Pointer(&key[0])), (*C.char)(array), (C.int)(len(values)))
ciphertext := C.GoBytes(unsafe.Pointer(array), C.int(size-16))
return ciphertext, nil
}
//无论外部还是内部一定记住内存分配。
2.for循环容易丢失
func SealDecrypt(sealParams []unsafe.Pointer, reply [][]byte, position int, keys SealKeys) ([]byte, error) {
// 分配 MyData 结构体的内存
if len(keys.SecretKey) <= 0 || len(keys.PublicKey) <= 0 || len(reply) <= 0 {
return nil, errors.New("sealDecrypt Error: SecretKey , PublicKey and Ciphertext is NULL")
}
var plaintexts []byte
for i := 0; i < len(reply); i++ {
cSealData := (*C.SealData)(C.malloc(C.size_t(unsafe.Sizeof(C.SealData{}))))
cSealData.ele_index = C.int(position) //放在外面容易丢失,建议每次赋值一次
cSealData.secret_key = (*C.char)(unsafe.Pointer(&keys.SecretKey[0]))
cSealData.public_key = (*C.char)(unsafe.Pointer(&keys.PublicKey[0]))
if i == len(reply)-1 {
cSealData.seal_params = sealParams[0]
} else {
cSealData.seal_params = sealParams[1]
}
cSealData.reply = (*C.char)(unsafe.Pointer(&reply[i][0]))
// 调用 C 函数,并传递 MyData 结构体的指针
C.seal_decrypt(cSealData)
// 将 C 返回的数据转换为 Go 的切片类型
Plaintext := C.GoBytes(unsafe.Pointer(cSealData.plaintext), C.int(cSealData.plaintext_len))
plaintexts = append(plaintexts, Plaintext...)
C.free(unsafe.Pointer(cSealData)) //即用即清!当数据量一大会内存报错!
}
return plaintexts, nil
}
四、用例
simple.h
//
// Created by ybx on 2023/7/21.
//
#ifndef CGO_EXEMPLE_SIMPLE_H
#define CGO_EXEMPLE_SIMPLE_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
char *data;
int data_len;
}cgo_simple_struct;
int swap_value(char *value1, char *value2);
int swap_value_struct(cgo_simple_struct *struct1, cgo_simple_struct *struct2);
int get_value(char *data, cgo_simple_struct *cgo);
#ifdef __cplusplus
}
#endif
#endif //CGO_EXEMPLE_SIMPLE_H
simple.cpp
//
// Created by admin on 2023/7/21.
//
#include <cstring>
#include "simple.h"
int swap_value(char *value1, char *value2){
// 交换两个字符数组的内容
char temp[strlen(value1) + 1];
strcpy(temp, value1);
strcpy(value1, value2);
strcpy(value2, temp);
return 0; // 返回一个标志,表示交换成功
}
int swap_value_struct(cgo_simple_struct *struct1, cgo_simple_struct *struct2) {
// 交换两个结构体中的指针和长度
char *temp_data = struct1->data;
int temp_data_len = struct1->data_len;
struct1->data = struct2->data;
struct1->data_len = struct2->data_len;
struct2->data = temp_data;
struct2->data_len = temp_data_len;
return 0; // 返回一个标志,表示交换成功
}
int get_value(char *data, cgo_simple_struct *cgo) {
// 为 cgo->data 分配内存,并复制 ids 的内容到其中
cgo->data = new char[len + 1];
strncpy(cgo->data, data, cgo->data_len);
// 保存 ids 的长度
cgo->data_len = len;
return 0; // 返回一个标志,表示复制成功
}
cmakelist:
cmake_minimum_required(VERSION 3.25)
project(c++)
set(CMAKE_CXX_STANDARD 17)
add_executable(c++ main.cpp src/simple.cpp src/simple.h)
add_library(cgo SHARED src/simple.cpp src/simple.h)
cgo.go
package main
// #cgo LDFLAGS: -L./c++/build -lcgo
// #cgo CFLAGS: -I./c++/src
/*
#include "simple.h"
#include <stdlib.h>
#include <string.h>
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
// 调用 swap_value 函数
str1 := []byte("Hello")
str2 := []byte("World")
fmt.Printf("Before swap: struct1 = %s, struct2 = %s\n", string(str1), string(str2))
C.swap_value((*C.char)(unsafe.Pointer(&str1[0])), (*C.char)(unsafe.Pointer(&str2[0])))
fmt.Printf("After swap: struct1 = %s, struct2 = %s\n", string(str1), string(str2))
// 调用 get_value 函数
cgodata1 := (*C.cgo_simple_struct)(C.malloc(C.size_t(unsafe.Sizeof(C.cgo_simple_struct{}))))
defer C.free(unsafe.Pointer(cgodata1))
cgodata1.data_len = C.int(len(str1))
C.get_value((*C.char)(unsafe.Pointer(&str1[0])), cgodata1)
getData := C.GoBytes(unsafe.Pointer(cgodata1.data), C.int(cgodata1.data_len))
fmt.Printf("After get: struct->data = %s, struct->data_len = %d\n", string(getData), cgodata1.data_len)
cgodata2 := (*C.cgo_simple_struct)(C.malloc(C.size_t(unsafe.Sizeof(C.cgo_simple_struct{}))))
defer C.free(unsafe.Pointer(cgodata2))
cgodata2.data = (*C.char)(unsafe.Pointer(&str2[0]))
cgodata2.data_len = C.int(len(str2))
fmt.Printf("After swap: struct1->data = %s, struct2->data = %s\n", string(getData), string(str2))
C.swap_value_struct(cgodata1, cgodata2)
getData1 := C.GoBytes(unsafe.Pointer(cgodata1.data), C.int(cgodata1.data_len))
getData2 := C.GoBytes(unsafe.Pointer(cgodata1.data), C.int(cgodata1.data_len))
// 打印交换后的结果
fmt.Printf("After swap: struct1->data = %s, struct2->data = %s\n", string(getData1), string(getData2))
}
结果: