如何为基于CFFI的PQClean McEliece库实现运行时依赖注入?
实现方案
1. 解决编译链接错误:给缺失函数添加转发实现
首先在CFFI定义中补充shake256和PQCLEAN_randombytes的转发实现,让MSVC编译时能找到函数符号,解决LNK1120错误。
1.1 声明C层函数指针与全局变量
在你的CFFI cdef块中添加以下声明:
// PQClean依赖的函数签名 void shake256(unsigned char *out, size_t outlen, const unsigned char *in, size_t inlen); void PQCLEAN_randombytes(unsigned char *out, size_t outlen); // 定义对应函数指针类型 typedef void (*shake256_func)(unsigned char*, size_t, const unsigned char*, size_t); typedef void (*randombytes_func)(unsigned char*, size_t); // 全局变量,用于存储用户提供的函数指针 extern shake256_func custom_shake256; extern randombytes_func custom_randombytes;
1.2 实现转发函数
在CFFI的source代码块中实现这两个缺失的函数,让它们调用全局指针指向的逻辑:
// 初始化全局指针为NULL shake256_func custom_shake256 = NULL; randombytes_func custom_randombytes = NULL; void shake256(unsigned char *out, size_t outlen, const unsigned char *in, size_t inlen) { if (!custom_shake256) { fputs("Error: custom_shake256 not initialized\n", stderr); abort(); } custom_shake256(out, outlen, in, inlen); } void PQCLEAN_randombytes(unsigned char *out, size_t outlen) { if (!custom_randombytes) { fputs("Error: custom_randombytes not initialized\n", stderr); abort(); } custom_randombytes(out, outlen); }
2. 运行时绑定用户提供的Python函数
通过CFFI的callback机制,将用户传入的Python函数转换为C函数指针,赋值给全局变量,让C代码可以调用Python逻辑。
2.1 封装Python类,强制依赖注入
在你的Python库中定义核心类,要求用户在初始化时传入符合要求的函数:
from typing import Callable from cffi import FFI # 假设你已经完成CFFI的基础初始化(ffi.set_source、ffi.cdef等) ffi = FFI() class ClassicMcEliece: def __init__(self, shake256: Callable[[bytes], bytes], randombytes: Callable[[int], bytes]): # 提前校验输入函数的可调用性 if not callable(shake256): raise ValueError("shake256 must be a callable function") if not callable(randombytes): raise ValueError("randombytes must be a callable function") # 适配C函数签名的回调包装 @ffi.callback("void(unsigned char*, size_t, const unsigned char*, size_t)") def _c_shake256(out, outlen, in_buf, inlen): # 将C缓冲区转换为Python bytes input_data = ffi.buffer(in_buf, inlen)[:] # 调用用户提供的函数 output_data = shake256(input_data) # 校验输出长度,避免缓冲区溢出 if len(output_data) != outlen: raise ValueError(f"shake256 returned {len(output_data)} bytes, expected {outlen}") # 将结果拷贝到C输出缓冲区 ffi.memmove(out, output_data, outlen) @ffi.callback("void(unsigned char*, size_t)") def _c_randombytes(out, outlen): output_data = randombytes(outlen) if len(output_data) != outlen: raise ValueError(f"randombytes returned {len(output_data)} bytes, expected {outlen}") ffi.memmove(out, output_data, outlen) # 将回调函数赋值给C全局指针 ffi.lib.custom_shake256 = _c_shake256 ffi.lib.custom_randombytes = _c_randombytes # 保存回调引用,防止被Python垃圾回收 self._shake256_cb = _c_shake256 self._randombytes_cb = _c_randombytes # 初始化PQClean的KEM实例(根据实际代码补充) # ... # 添加密钥生成、加密、解密等方法 def keygen(self): pk = ffi.new("unsigned char[]", ffi.lib.PQCLEAN_CLASSICMCELEICE6960119F_CRYPTO_PUBLICKEYBYTES) sk = ffi.new("unsigned char[]", ffi.lib.PQCLEAN_CLASSICMCELEICE6960119F_CRYPTO_SECRETKEYBYTES) ffi.lib.PQCLEAN_CLASSICMCELEICE6960119F_crypto_kem_keypair(pk, sk) return ffi.buffer(pk)[:], ffi.buffer(sk)[:]
3. 用户使用示例
用户只需传入符合类型要求的函数即可使用你的库:
import hashlib import os from your_library import ClassicMcEliece # 自定义shake256实现(输入bytes,输出指定长度的哈希) def my_shake256(data: bytes) -> bytes: # 根据PQClean的需求调整输出长度,比如Classic McEliece 6960119f可能需要64字节 return hashlib.shake_256(data).digest(64) # 自定义随机字节生成函数(输入长度,返回对应长度的随机bytes) def my_randombytes(length: int) -> bytes: return os.urandom(length) # 初始化库实例 kem = ClassicMcEliece(my_shake256, my_randombytes) # 使用KEM功能 public_key, secret_key = kem.keygen() # ... 后续加密解密逻辑
关键注意事项
- 回调引用保留:必须将
ffi.callback创建的函数对象保存为类属性,否则Python垃圾回收会回收这些回调,导致C代码调用时崩溃。 - 长度校验:在回调包装中严格校验用户函数的输出长度,避免C层缓冲区溢出。
- 编译配置:在
ffi.set_source中要正确包含PQClean的源文件和头文件路径,确保编译时能找到Classic McEliece的实现代码。
内容的提问来源于stack exchange,提问作者JamesTheAwesomeDude
相关产品推荐
相关产品推荐

