Mystery0の小站

Mystery0の小站

将 go-jasypt 编译成静态库给 C++ 调用

使用的 go-jasypt

https://github.com/Mystery00/go-jasypt

首先写一个go项目,引入 go-jasypt

先写 go.mod

module c-jasypt

go 1.21

require github.com/Mystery00/go-jasypt v1.0.3

require golang.org/x/crypto v0.22.0 // indirect

截止到现在, go-jasypt 的版本是 1.0.3 ,里面有一个 crypto 的依赖有漏洞,因此这里强制指定了它的版本为 0.22.0

然后再写一个 main.go 文件,我们在这个文件里面暴露出来加密解密函数

package main

import (
	"C"
	"fmt"
	"github.com/Mystery00/go-jasypt"
	"github.com/Mystery00/go-jasypt/iv"
	"github.com/Mystery00/go-jasypt/salt"
)

const (
	// specify the algorithm
	algorithm = "PBEWithHMACSHA512AndAES_256"
)

//export Encrypt
func Encrypt(password, message *C.char) *C.char {
	// create a new instance of jasypt
	encryptor := jasypt.New(algorithm, jasypt.NewConfig(
		jasypt.SetPassword(C.GoString(password)),
		jasypt.SetSaltGenerator(salt.RandomSaltGenerator{}),
		jasypt.SetIvGenerator(iv.RandomIvGenerator{}),
	))
	// encrypt the message
	encrypted, err := encryptor.Encrypt(C.GoString(message))
	if err != nil {
		return C.CString(fmt.Sprintf("Error: %s", err.Error()))
	}
	return C.CString(encrypted)
}

//export Decrypt
func Decrypt(password, encode *C.char) *C.char {
	// create a new instance of jasypt
	encryptor := jasypt.New(algorithm, jasypt.NewConfig(
		jasypt.SetPassword(C.GoString(password)),
		jasypt.SetSaltGenerator(salt.RandomSaltGenerator{}),
		jasypt.SetIvGenerator(iv.RandomIvGenerator{}),
	))
	// decrypt the message
	decrypted, err := encryptor.Decrypt(C.GoString(encode))
	if err != nil {
		return C.CString(fmt.Sprintf("Error: %s", err.Error()))
	}
	return C.CString(decrypted)
}

func main() {
}

有几个地方需要注意一下:

  • package 必须是main,因为我们这里需要编译成静态库,不是main的话会报错 -buildmode=c-archive requires exactly one main package

  • 同上,需要一个main方法,没有逻辑无所谓,必须要存在

  • 在需要暴露的方法上增加 //export 标识

  • import中也要带上 import "C"

  • 这个库接收的都是 go 的string类型参数,但是直接使用string类型似乎会导致go的内存管理出现问题,运行时会报错 oom,所以使用 *C.char 来接收

  • 返回数据也是同理,使用 *C.char

编译静态库

编写好源码之后,执行以下命令进行编译

go build -buildmode=c-archive -o jasypt.a

我没有指定 GOOSGOARCH 这些,需要交叉编译的话请自行指定

编译通过之后会生成两个文件,一个是 jasypt.a ,也就是编译命令中我们指定的输出文件名,这个就是静态库,同时还有一个 jasypt.h 头文件,这个是给C++引用的。

编写 C++ 代码

代码如下,仅仅是调用一下,里面的输入都是测试数据

#include <iostream>

extern "C" {
    const char* Encrypt(const char* password, const char* message);
    const char* Decrypt(const char* password, const char* encode);
}

int main() {
    const char* result = Decrypt("password", "yBC5Tt8PFp+mIoxY69zYfW2f/wxV6ofYMuHGIxd8fxMra/riH78DyMz4zNTCcQ9z");
    std::cout << result << std::endl;
    const char* encode = Encrypt("password", result);
    std::cout << encode << std::endl;
    return 0;
}

按照我上面的代码是可以正常运行的,或许在大型项目里面需要单独去引入头文件啥的

但是我的C++是10年前学的了,已经全部忘记完了,这里就留给C++大佬去自行修改吧

不行了,要长脑子了

编译 C++ 代码

执行一下命令

g++ -o test _test.cpp jasypt.a

我的 cpp 代码与go代码放到了一起,为了避免go拿去编译静态库了,就加了_前缀

参数没啥好说的,照着写就编译出来一个 test 文件

直接运行这个 test 文件,就得到输出内容了

./test

1234567890
A4l0hahtfc4S4q586c6xEAVvzButDaXrwNyQ5NhhC1A64IxBbkE7NHU2gDYvHvwN

其中第一行是我们用测试数据解密的结果,是正确的

第二行是我们用解密结果再做了一次加密之后的密文

因为jasypt的效果,每一次加密我们得到的密文都是不同的,因此如果你的第二行输出和我不一样并不是代码原因,只要输出的内容不带 Error: 前缀,都是正确的