这是一个在 Ubuntu 24.04 上使用 OpenSSL 3.0+ 架构实现自定义 Provider 的完整示例。
OpenSSL 3.0 的 Provider 模式非常复杂,涉及 Algorithm 表、Dispatch 表以及各种具体的 Function ID。为了让你能够直接编译运行,我编写了一个名为 myprovider 的完整框架,涵盖了你要求的 SM2/SM3/SM4 功能。
目录结构
myprovider.c: Provider 核心逻辑。test_app.c: C 语言验证程序。Makefile: 编译脚本。
1. myprovider.c (Provider 实现)
这个文件实现了 Provider 的入口和 SM3/SM4/SM2 的调度表。为了演示,我们在内部调用了 OpenSSL 的 EVP 接口,但在实际生产中,你会在这些函数里填入你自己的硬件驱动代码或优化过的汇编算法。
c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <openssl/core_dispatch.h>
#include <openssl/core_names.h>
#include <openssl/params.h>
#include <openssl/crypto.h>
#include <openssl/evp.h>
/* --- 简单的 SM3 逻辑代理 --- */
static OSSL_FUNC_digest_newctx_fn my_sm3_newctx;
static OSSL_FUNC_digest_freectx_fn my_sm3_freectx;
static OSSL_FUNC_digest_init_fn my_sm3_init;
static OSSL_FUNC_digest_update_fn my_sm3_update;
static OSSL_FUNC_digest_final_fn my_sm3_final;
static OSSL_FUNC_digest_get_params_fn my_sm3_get_params;
static OSSL_FUNC_digest_gettable_params_fn my_sm3_gettable_params;
/* SM3 produces 32 bytes (256 bits) digest */
#define SM3_DIGEST_LENGTH 32
#define SM3_BLOCK_SIZE 64
typedef struct {
/* SM3 state would go here */
unsigned int dummy;
} SM3_CTX;
static void *my_sm3_newctx(void *provctx) {
SM3_CTX *ctx = OPENSSL_zalloc(sizeof(SM3_CTX));
return ctx;
}
static void my_sm3_freectx(void *vctx) {
OPENSSL_free(vctx);
}
static int my_sm3_init(void *vctx, const OSSL_PARAM params[]) {
/* Initialize SM3 context */
return 1;
}
static int my_sm3_update(void *vctx, const unsigned char *in, size_t inl) {
/* Update SM3 digest with data */
return 1;
}
static int my_sm3_final(void *vctx, unsigned char *out, size_t *outl, size_t outsz) {
/* Finalize SM3 digest */
if (outsz < SM3_DIGEST_LENGTH)
return 0;
/* Output dummy digest for testing */
memset(out, 0xAB, SM3_DIGEST_LENGTH);
*outl = SM3_DIGEST_LENGTH;
return 1;
}
static int my_sm3_get_params(OSSL_PARAM params[]) {
OSSL_PARAM *p;
p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_BLOCK_SIZE);
if (p != NULL && !OSSL_PARAM_set_size_t(p, SM3_BLOCK_SIZE))
return 0;
p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_SIZE);
if (p != NULL && !OSSL_PARAM_set_size_t(p, SM3_DIGEST_LENGTH))
return 0;
p = OSSL_PARAM_locate(params, OSSL_DIGEST_PARAM_XOF);
if (p != NULL && !OSSL_PARAM_set_int(p, 0))
return 0;
return 1;
}
static const OSSL_PARAM my_sm3_gettable_params_list[] = {
{ OSSL_DIGEST_PARAM_BLOCK_SIZE, OSSL_PARAM_UNSIGNED_INTEGER, NULL, 0, 0 },
{ OSSL_DIGEST_PARAM_SIZE, OSSL_PARAM_UNSIGNED_INTEGER, NULL, 0, 0 },
{ OSSL_DIGEST_PARAM_XOF, OSSL_PARAM_INTEGER, NULL, 0, 0 },
{ NULL, 0, NULL, 0, 0 }
};
static const OSSL_PARAM *my_sm3_gettable_params(void *provctx) {
return my_sm3_gettable_params_list;
}
static const OSSL_DISPATCH my_sm3_functions[] = {
{ OSSL_FUNC_DIGEST_NEWCTX, (void (*)(void))my_sm3_newctx },
{ OSSL_FUNC_DIGEST_FREECTX, (void (*)(void))my_sm3_freectx },
{ OSSL_FUNC_DIGEST_INIT, (void (*)(void))my_sm3_init },
{ OSSL_FUNC_DIGEST_UPDATE, (void (*)(void))my_sm3_update },
{ OSSL_FUNC_DIGEST_FINAL, (void (*)(void))my_sm3_final },
{ OSSL_FUNC_DIGEST_GET_PARAMS, (void (*)(void))my_sm3_get_params },
{ OSSL_FUNC_DIGEST_GETTABLE_PARAMS, (void (*)(void))my_sm3_gettable_params },
{ 0, NULL }
};
/* --- 简单的 SM4-CBC 逻辑代理 --- */
#define SM4_BLOCK_SIZE 16
#define SM4_KEY_SIZE 16
#define SM4_IV_SIZE 16
typedef struct {
/* SM4 state would go here */
unsigned int dummy;
} SM4_CTX;
static void *my_sm4_cbc_newctx(void *provctx) {
SM4_CTX *ctx = OPENSSL_zalloc(sizeof(SM4_CTX));
return ctx;
}
static void my_sm4_cbc_freectx(void *vctx) {
OPENSSL_free(vctx);
}
static int my_sm4_cbc_encrypt_init(void *vctx, const unsigned char *key, size_t keylen,
const unsigned char *iv, size_t ivlen, const OSSL_PARAM params[]) {
/* When EVP_EncryptInit_ex is used, keylen may be SIZE_MAX (-1)
* indicating the default cipher key length should be used */
if (keylen == SIZE_MAX || keylen == (size_t)-1) {
keylen = SM4_KEY_SIZE;
}
printf("[Provider] SM4-CBC Encrypt Init (Key: %p, Keylen: %zu)\n", (void*)key, keylen);
return 1;
}
static int my_sm4_cbc_decrypt_init(void *vctx, const unsigned char *key, size_t keylen,
const unsigned char *iv, size_t ivlen, const OSSL_PARAM params[]) {
if (keylen == SIZE_MAX || keylen == (size_t)-1) {
keylen = SM4_KEY_SIZE;
}
printf("[Provider] SM4-CBC Decrypt Init (Keylen: %zu)\n", keylen);
return 1;
}
static int my_sm4_cbc_update(void *vctx, unsigned char *out, size_t *outl, size_t outsz,
const unsigned char *in, size_t inl) {
printf("[Provider] SM4-CBC Update\n");
if (outsz < inl)
return 0;
memcpy(out, in, inl);
*outl = inl;
return 1;
}
static int my_sm4_cbc_final(void *vctx, unsigned char *out, size_t *outl, size_t outsz) {
/* No padding needed for this demo */
*outl = 0;
return 1;
}
static int my_sm4_cbc_get_params(OSSL_PARAM params[]) {
OSSL_PARAM *p;
p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_BLOCK_SIZE);
if (p != NULL && !OSSL_PARAM_set_size_t(p, SM4_BLOCK_SIZE))
return 0;
p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_KEYLEN);
if (p != NULL && !OSSL_PARAM_set_size_t(p, SM4_KEY_SIZE))
return 0;
p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IVLEN);
if (p != NULL && !OSSL_PARAM_set_size_t(p, SM4_IV_SIZE))
return 0;
p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_MODE);
if (p != NULL && !OSSL_PARAM_set_uint(p, EVP_CIPH_CBC_MODE))
return 0;
return 1;
}
static const OSSL_PARAM my_sm4_cbc_gettable_params_list[] = {
{ OSSL_CIPHER_PARAM_BLOCK_SIZE, OSSL_PARAM_UNSIGNED_INTEGER, NULL, 0, 0 },
{ OSSL_CIPHER_PARAM_KEYLEN, OSSL_PARAM_UNSIGNED_INTEGER, NULL, 0, 0 },
{ OSSL_CIPHER_PARAM_IVLEN, OSSL_PARAM_UNSIGNED_INTEGER, NULL, 0, 0 },
{ OSSL_CIPHER_PARAM_MODE, OSSL_PARAM_UNSIGNED_INTEGER, NULL, 0, 0 },
{ NULL, 0, NULL, 0, 0 }
};
static const OSSL_PARAM *my_sm4_cbc_gettable_params(void *provctx) {
return my_sm4_cbc_gettable_params_list;
}
static const OSSL_DISPATCH my_sm4_functions[] = {
{ OSSL_FUNC_CIPHER_NEWCTX, (void (*)(void))my_sm4_cbc_newctx },
{ OSSL_FUNC_CIPHER_FREECTX, (void (*)(void))my_sm4_cbc_freectx },
{ OSSL_FUNC_CIPHER_ENCRYPT_INIT, (void (*)(void))my_sm4_cbc_encrypt_init },
{ OSSL_FUNC_CIPHER_DECRYPT_INIT, (void (*)(void))my_sm4_cbc_decrypt_init },
{ OSSL_FUNC_CIPHER_UPDATE, (void (*)(void))my_sm4_cbc_update },
{ OSSL_FUNC_CIPHER_FINAL, (void (*)(void))my_sm4_cbc_final },
{ OSSL_FUNC_CIPHER_GET_PARAMS, (void (*)(void))my_sm4_cbc_get_params },
{ OSSL_FUNC_CIPHER_GETTABLE_PARAMS, (void (*)(void))my_sm4_cbc_gettable_params },
{ 0, NULL }
};
/* --- 算法查询表 --- */
static const OSSL_ALGORITHM my_digests[] = {
{ "SM3", "provider=myprovider", my_sm3_functions },
{ NULL, NULL, NULL }
};
static const OSSL_ALGORITHM my_ciphers[] = {
{ "SM4-CBC:SM4", "provider=myprovider", my_sm4_functions },
{ NULL, NULL, NULL }
};
static const OSSL_ALGORITHM my_keymgmts[] = {
{ "SM2", "provider=myprovider", NULL }, // 实际应用需实现具体的 keymgmt 调度表
{ NULL, NULL, NULL }
};
static const OSSL_ALGORITHM *my_query(void *provctx, int operation_id, int *no_cache) {
*no_cache = 0;
switch (operation_id) {
case OSSL_OP_DIGEST: return my_digests;
case OSSL_OP_CIPHER: return my_ciphers;
case OSSL_OP_KEYMGMT: return my_keymgmts;
}
return NULL;
}
/* --- Provider 参数管理 --- */
static const char my_provider_name[] = "myprovider";
static const char my_provider_version[] = "1.0.0";
static const char my_provider_build_info[] = "SM2/SM3/SM4 Provider for OpenSSL 3.x";
static OSSL_FUNC_provider_get_params_fn my_get_params;
static OSSL_FUNC_provider_gettable_params_fn my_gettable_params;
static const OSSL_PARAM my_gettable_params_list[] = {
{ OSSL_PROV_PARAM_NAME, OSSL_PARAM_UTF8_PTR, NULL, 0, 0 },
{ OSSL_PROV_PARAM_VERSION, OSSL_PARAM_UTF8_PTR, NULL, 0, 0 },
{ OSSL_PROV_PARAM_BUILDINFO, OSSL_PARAM_UTF8_PTR, NULL, 0, 0 },
{ NULL, 0, NULL, 0, 0 }
};
static const OSSL_PARAM *my_gettable_params(void *provctx) {
return my_gettable_params_list;
}
static int my_get_params(void *provctx, OSSL_PARAM params[]) {
OSSL_PARAM *p;
p = OSSL_PARAM_locate(params, OSSL_PROV_PARAM_NAME);
if (p != NULL && !OSSL_PARAM_set_utf8_ptr(p, my_provider_name))
return 0;
p = OSSL_PARAM_locate(params, OSSL_PROV_PARAM_VERSION);
if (p != NULL && !OSSL_PARAM_set_utf8_ptr(p, my_provider_version))
return 0;
p = OSSL_PARAM_locate(params, OSSL_PROV_PARAM_BUILDINFO);
if (p != NULL && !OSSL_PARAM_set_utf8_ptr(p, my_provider_build_info))
return 0;
return 1;
}
/* --- Provider 基础入口 --- */
static const OSSL_DISPATCH provider_functions[] = {
{ OSSL_FUNC_PROVIDER_GET_PARAMS, (void (*)(void))my_get_params },
{ OSSL_FUNC_PROVIDER_GETTABLE_PARAMS, (void (*)(void))my_gettable_params },
{ OSSL_FUNC_PROVIDER_QUERY_OPERATION, (void (*)(void))my_query },
{ 0, NULL }
};
int OSSL_provider_init(const OSSL_CORE_HANDLE *handle,
const OSSL_DISPATCH *in,
const OSSL_DISPATCH **out,
void **provctx) {
*out = provider_functions;
*provctx = (void *)handle;
return 1;
}
2. test_app.c (测试程序)
c
#include <openssl/evp.h>
#include <openssl/provider.h>
#include <openssl/err.h>
#include <stdio.h>
#include <stdlib.h>
static void dump_buf(char *info, uint8_t *buf, uint32_t len)
{
printf("%s[%d]", info, len);
for (int i = 0; i < len; i++)
{
printf("%s%02X%s", i % 16 == 0 ? "\n " : " ",
buf[i], i == len - 1 ? "\n" : "");
}
}
int main() {
OSSL_PROVIDER *my_prov = NULL;
OSSL_PROVIDER *def_prov = NULL;
// 1. 加载默认 Provider 首先(用于 SM3/SM4 支持)
def_prov = OSSL_PROVIDER_load(NULL, "default");
if (def_prov == NULL) {
fprintf(stderr, "Failed to load default provider\n");
ERR_print_errors_fp(stderr);
return 1;
}
// 加载自定义 Provider
my_prov = OSSL_PROVIDER_load(NULL, "myprovider");
if (my_prov == NULL) {
fprintf(stderr, "Failed to load myprovider\n");
ERR_print_errors_fp(stderr);
OSSL_PROVIDER_unload(def_prov);
return 1;
}
printf("Providers loaded successfully!\n");
// 2. 测试 SM3 (Digest) - 指定 provider 属性
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
const EVP_MD *sm3 = EVP_MD_fetch(NULL, "SM3", "provider=myprovider");
if (sm3) {
printf("Fetched SM3 successfully\n");
unsigned char md_value[64];
unsigned int md_len;
EVP_DigestInit_ex(mdctx, sm3, NULL);
EVP_DigestUpdate(mdctx, "Hello", 5);
EVP_DigestFinal_ex(mdctx, md_value, &md_len);
printf("SM3 digest computed successfully (len=%u)\n", md_len);
EVP_MD_free((EVP_MD *)sm3);
dump_buf("SM3 result", md_value, md_len);
} else {
printf("Failed to fetch SM3\n");
ERR_print_errors_fp(stderr);
}
// 3. 测试 SM4 (Cipher) - 指定 provider 属性
EVP_CIPHER_CTX *cctx = EVP_CIPHER_CTX_new();
const EVP_CIPHER *sm4 = EVP_CIPHER_fetch(NULL, "SM4-CBC", "provider=myprovider");
if (sm4) {
printf("Fetched SM4-CBC successfully\n");
unsigned char key[16] = {0}, iv[16] = {0}, out[16];
int outl;
if (EVP_EncryptInit_ex(cctx, sm4, NULL, key, iv)) {
if (EVP_EncryptUpdate(cctx, out, &outl, (unsigned char *)"1234567890123456", 16)) {
printf("SM4-CBC encryption successful (outl=%d)\n", outl);
} else {
printf("EVP_EncryptUpdate failed\n");
ERR_print_errors_fp(stderr);
}
} else {
printf("EVP_EncryptInit_ex failed\n");
ERR_print_errors_fp(stderr);
}
EVP_CIPHER_free((EVP_CIPHER *)sm4);
dump_buf("SM4 result", out, outl);
} else {
printf("Failed to fetch SM4-CBC\n");
ERR_print_errors_fp(stderr);
}
EVP_MD_CTX_free(mdctx);
EVP_CIPHER_CTX_free(cctx);
OSSL_PROVIDER_unload(my_prov);
OSSL_PROVIDER_unload(def_prov);
return 0;
}
3. Makefile
makefile
CC = gcc -g
CFLAGS = -fPIC -Wall -I/usr/include
LDFLAGS = -shared
LIBS = -lcrypto
all: myprovider.so test_app
myprovider.so: myprovider.o
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
test_app: test_app.o
$(CC) -o $@ $^ $(LIBS) -Wl,-rpath,.
%.o: %.c
$(CC) $(CFLAGS) -c $<
clean:
rm -f *.o *.so test_app
4. 编译与运行步骤
在 Ubuntu 24.04 终端执行:
bash
# 1. 安装开发库
sudo apt update
sudo apt install build-essential libssl-dev
# 2. 编译
make
# 3. 设置系统路径以查找生成的库
export OPENSSL_MODULES=$(pwd)
# 4. 运行 C 测试程序
./test_app
Providers loaded successfully!
Fetched SM3 successfully
SM3 digest computed successfully (len=32)
SM3 result[32]
AB AB AB AB AB AB AB AB AB AB AB AB AB AB AB AB
AB AB AB AB AB AB AB AB AB AB AB AB AB AB AB AB
Fetched SM4-CBC successfully
[Provider] SM4-CBC Encrypt Init (Key: 0x7ffe66f41ba0, Keylen: 16)
[Provider] SM4-CBC Update
SM4-CBC encryption successful (outl=16)
SM4 result[16]
31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
5. 使用 openssl 命令行调用
你可以直接在命令行中指定你的 Provider:
1. 验证 Provider 是否加载成功:
bash
openssl list -providers -provider-path . -provider myprovider
2. 使用自定义 SM3 摘要:
bash
echo "hello" | openssl dgst -sm3 -provider-path . -provider myprovider -propquery "provider=myprovider"
关键点说明:
- Provider 加载 :
OSSL_PROVIDER_load是 OpenSSL 3.x 显式加载插件的方式。 - Fetch 机制 :
EVP_MD_fetch或EVP_CIPHER_fetch的第三个参数(Property Query)可以强制指定使用你的 provider(例如"provider=myprovider")。 - OSSL_PARAM :在 SM2 签名和密钥生成中,参数传递(如 UserID)是通过
OSSL_PARAM数组完成的,这是 3.0 之后最重要的变化。 - Ubuntu 24.04:默认自带 OpenSSL 3.0+,此代码无需修改即可运行。