4. 生成ECC密钥对并执行ECDSA签名
任务目标
在硬件安全模块(HSM)中生成基于NIST P-256曲线的ECC密钥对,并使用私钥对消息摘要执行ECDSA签名,最后通过公钥验证签名有效性。本案例聚焦于HSM内部密钥生成与签名操作的标准流程,不涉及密钥导出或明文私钥访问。
环境与依赖
本案例基于OpenSSL 3.x的PKCS#11接口实现,需要以下环境:
- OpenSSL 3.0+(支持PKCS#11 provider)
- SoftHSMv2作为HSM模拟器(或真实HSM设备)
- 操作系统:Linux(示例命令基于Ubuntu/Debian)
安装依赖:
sudo apt install softhsm2 openssl libengine-pkcs11-openssl
# 初始化HSM (若首次使用)
sudo softhsm2-util --init- --slot 0 --label "demo" --pin 1234 --so-pin 4321
实现代码
以下C程序使用PKCS#11标准接口完成密钥生成与ECDSA签名验证全流程:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pkcs11.h"
#define PIN "1234"
#define _LABEL "demo"
static CK_FUNCTION_LIST_PTR p11 = NULL;
static CK_SLOT_ID slot_id = 0;
static CK_SESSION_HANDLE session = 0;
static CK_OBJECT_HANDLE priv_key = 0, pub_key = 0;
static CK_RV init_pkcs11(void) {
CK_RV rv;
CK_C_GetFunctionList get_list = NULL;
void *handle = dlopen("libsofthsm2.so", RTLD_NOW);
if (!handle) return CKR_GENERAL_ERROR;
get_list = (CK_C_GetFunctionList)dlsym(handle, "C_GetFunctionList");
if (!get_list) return CKR_GENERAL_ERROR;
rv = get_list(&p11);
if (rv != CKR_OK) return rv;
rv = p11->C_Initialize(NULL);
if (rv != CKR_OK && rv != CKR_CRYPTOKI_ALREADY_INITIALIZED) return rv;
CK_ULONG count;
rv = p11->C_GetSlotList(CK_TRUE, NULL, &count);
if (rv != CKR_OK) return rv;
CK_SLOT_ID *slots = malloc(sizeof(CK_SLOT_ID) * count);
rv = p11->C_GetSlotList(CK_TRUE, slots, &count);
if (rv != CKR_OK) { free(slots); return rv; }
// 查找目标
for (CK_ULONG i = 0; i < count; i++) {
CK__INFO info;
if (p11->C_GetTokenInfo(slots[i], &info) == CKR_OK) {
if (strncmp((char*)info.label, _LABEL, strlen(_LABEL)) == 0) {
slot_id = slots[i];
break;
}
}
}
free(slots);
return (slot_id != 0) ? CKR_OK : CKR__NOT_RECOGNIZED;
}
static CK_RV generate_keypair(void) {
CK_RV rv;
CK_MECHANISM mech = {CKM_EC_KEY_PAIR_GEN, NULL, 0};
// 私钥模板
CK_ATTRIBUTE priv_tmpl[] = {
{CKA_, &(CK_BBOOL){CK_TRUE}, sizeof(CK_BBOOL)},
{CKA_PRIVATE, &(CK_BBOOL){CK_TRUE}, sizeof(CK_BBOOL)},
{CKA_SIGN, &(CK_BBOOL){CK_TRUE}, sizeof(CK_BBOOL)},
{CKA_EC_PARAMS, (CK_VOID_PTR)"\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07", 10} // P-256 OID
};
// 公钥模板
CK_ATTRIBUTE pub_tmpl[] = {
{CKA_, &(CK_BBOOL){CK_TRUE}, sizeof(CK_BBOOL)},
{CKA_PRIVATE, &(CK_BBOOL){CK_FALSE}, sizeof(CK_BBOOL)},
{CKA_VERIFY, &(CK_BBOOL){CK_TRUE}, sizeof(CK_BBOOL)},
{CKA_EC_PARAMS, (CK_VOID_PTR)"\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07", 10}
};
rv = p11->C_GenerateKeyPair(session, &mech,
pub_tmpl, sizeof(pub_tmpl)/sizeof(CK_ATTRIBUTE),
priv_tmpl, sizeof(priv_tmpl)/sizeof(CK_ATTRIBUTE),
&pub_key, &priv_key);
return rv;
}
static CK_RV sign_and_verify(void) {
CK_RV rv;
CK_MECHANISM sign_mech = {CKM_ECDSA, NULL, 0};
CK_MECHANISM verify_mech = {CKM_ECDSA, NULL, 0};
// 待签名数据(实际应用中为消息摘要)
CK_BYTE msg[] = "HSM ECDSA test message";
CK_BYTE signature[72];
CK_ULONG sig_len = sizeof(signature);
// 签名
rv = p11->C_SignInit(session, &sign_mech, priv_key);
if (rv != CKR_OK) return rv;
rv = p11->C_Sign(session, msg, sizeof(msg)-1, signature, &sig_len);
if (rv != CKR_OK) return rv;
printf("签名长度: %lu 字节\n", sig_len);
// 验证
rv = p11->C_VerifyInit(session, &verify_mech, pub_key);
if (rv != CKR_OK) return rv;
rv = p11->C_Verify(session, msg, sizeof(msg)-1, signature, sig_len);
return rv;
}
int main(void) {
CK_RV rv;
rv = init_pkcs11();
if (rv != CKR_OK) { printf("PKCS#11初始化失败: 0x%lx\n", rv); return 1; }
rv = p11->C_OpenSession(slot_id, CKF_SERIAL_SESSION | CKF_RW_SESSION,
NULL, NULL, &session);
if (rv != CKR_OK) { printf("会话打开失败: 0x%lx\n", rv); return 1; }
rv = p11->C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR)PIN, strlen(PIN));
if (rv != CKR_OK) { printf("登录失败: 0x%lx\n", rv); return 1; }
rv = generate_keypair();
if (rv != CKR_OK) { printf("密钥生成失败: 0x%lx\n", rv); return 1; }
printf("ECC P-256密钥对生成成功\n");
rv = sign_and_verify();
if (rv == CKR_OK) {
printf("ECDSA签名与验证成功\n");
} else {
printf("签名/验证失败: 0x%lx\n", rv);
}
p11->C_Logout(session);
p11->C_CloseSession(session);
p11->C_Finalize(NULL);
return (rv == CKR_OK) ? 0 : 1;
}
编译与运行
# 编译(需安装libsofthsm2-dev或相应PKCS#11头文件)
gcc -o ecdsa_demo ecdsa_demo.c -I/usr/include/softhsm2 -ldl -lcrypto
# 运行
./ecdsa_demo
验证方法
程序执行后,观察输出是否依次出现以下结果:
ECC P-256密钥对生成成功——表示C_GenerateKeyPair调用成功,密钥对已安全存储在HSM中签名长度: 70~72 字节——ECDSA签名长度在70-72字节之间(P-256曲线标准范围)ECDSA签名与验证成功——表示C_Verify返回CKR_OK,签名验证通过
若最后一行输出签名/验证失败,则说明签名验证未通过,需检查密钥模板参数或数据输入是否一致。
常见问题
- 找不到:确认SoftHSM初始化时使用的label与代码中_LABEL一致
- C_GenerateKeyPair返回CKR_ATTRIBUTE_VALUE_INVALID:检查EC_PARAMS的OID编码是否正确(P-256对应DER编码的OID 1.2.840.10045.3.1.7)
- 签名长度异常:ECDSA签名长度取决于r和s值,P-256曲线下通常在70-72字节之间,若超出72字节说明实现有误