3. 使用HSM进行RSA签名与验签
任务目标
在硬件安全模块(HSM)中生成RSA密钥对,使用私钥对消息摘要进行签名,并使用公钥验证签名。通过本案例,掌握HSM中RSA操作的典型调用流程:密钥生成→签名→验签,并理解HSM私钥不可导出的安全特性。
环境与依赖
- HSM模拟器:使用
SoftHSM v2(开源PKCS#11实现) - 工具库:
OpenSC(提供pkcs11-tool命令行工具) - 操作系统:Linux(Ubuntu/Debian)
安装命令:
sudo apt-get install softhsm2 opensc
实现方案
本案例使用PKCS#11标准接口与HSM交互。核心流程为:初始化HSM令牌→生成RSA-2048密钥对→使用CKM_SHA256_RSA_PKCS机制签名→使用CKM_RSA_PKCS机制验签。私钥始终保存在HSM内部,仅返回密钥句柄,外部无法读取私钥明文。
关键完整代码
// rsa_sign_verify.c
// 编译: gcc -o rsa_sign_verify rsa_sign_verify.c -lsofthsm2 -lcrypto
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <softhsm2.h>
#define _LABEL "MyToken"
#define SO_PIN "12345678"
#define USER_PIN "12345678"
static CK_RV init_(CK_SLOT_ID slot) {
return C_InitToken(slot, (CK_UTF8CHAR*)SO_PIN, strlen(SO_PIN),
(CK_UTF8CHAR*)_LABEL, strlen(_LABEL));
}
int main() {
CK_RV rv;
CK_SLOT_ID slot = 0;
CK_SESSION_HANDLE session;
CK_OBJECT_HANDLE pub_key, priv_key;
CK_ULONG sig_len = 0;
CK_BYTE sig[256];
CK_BYTE message[] = "HSM RSA signature test message";
CK_BYTE hash[32];
CK_BBOOL true_val = CK_TRUE;
// 初始化
rv = C_Initialize(NULL);
if (rv != CKR_OK) { printf("C_Initialize failed: 0x%lX\n", rv); return 1; }
// 初始化令牌(首次运行时需要)
rv = init_(slot);
if (rv != CKR_OK && rv != CKR_CRYPTOKI_ALREADY_INITIALIZED) {
printf("InitToken failed: 0x%lX\n", rv); return 1;
}
// 打开会话
rv = C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION,
NULL, NULL, &session);
if (rv != CKR_OK) { printf("OpenSession failed: 0x%lX\n", rv); return 1; }
// 登录
rv = C_Login(session, CKU_USER, (CK_UTF8CHAR*)USER_PIN, strlen(USER_PIN));
if (rv != CKR_OK) { printf("Login failed: 0x%lX\n", rv); return 1; }
// 生成RSA-2048密钥对
CK_MECHANISM mech = {CKM_RSA_PKCS_KEY_PAIR_GEN, NULL, 0};
CK_ULONG modulus_bits = 2048;
CK_BYTE pub_exp[] = {0x01, 0x00, 0x01}; // 65537
CK_ATTRIBUTE pub_template[] = {
{CKA_, &true_val, sizeof(true_val)},
{CKA_MODULUS_BITS, &modulus_bits, sizeof(modulus_bits)},
{CKA_PUBLIC_EXPONENT, pub_exp, sizeof(pub_exp)}
};
CK_ATTRIBUTE priv_template[] = {
{CKA_, &true_val, sizeof(true_val)},
{CKA_PRIVATE, &true_val, sizeof(true_val)},
{CKA_SIGN, &true_val, sizeof(true_val)}
};
rv = C_GenerateKeyPair(session, &mech,
pub_template, 3,
priv_template, 3,
&pub_key, &priv_key);
if (rv != CKR_OK) { printf("GenerateKeyPair failed: 0x%lX\n", rv); return 1; }
// 计算SHA-256哈希
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL);
EVP_DigestUpdate(mdctx, message, strlen((char*)message));
EVP_DigestFinal_ex(mdctx, hash, NULL);
EVP_MD_CTX_free(mdctx);
// 签名(CKM_SHA256_RSA_PKCS内部会再次哈希,此处传入原始消息)
CK_MECHANISM sig_mech = {CKM_SHA256_RSA_PKCS, NULL, 0};
rv = C_SignInit(session, &sig_mech, priv_key);
if (rv != CKR_OK) { printf("SignInit failed: 0x%lX\n", rv); return 1; }
rv = C_Sign(session, message, strlen((char*)message), NULL, &sig_len);
if (rv != CKR_OK) { printf("Sign size query failed: 0x%lX\n", rv); return 1; }
rv = C_Sign(session, message, strlen((char*)message), sig, &sig_len);
if (rv != CKR_OK) { printf("Sign failed: 0x%lX\n", rv); return 1; }
printf("签名成功,签名长度: %lu 字节\n", sig_len);
// 验签(使用CKM_RSA_PKCS机制,传入原始哈希)
CK_MECHANISM ver_mech = {CKM_RSA_PKCS, NULL, 0};
rv = C_VerifyInit(session, &ver_mech, pub_key);
if (rv != CKR_OK) { printf("VerifyInit failed: 0x%lX\n", rv); return 1; }
rv = C_Verify(session, hash, 32, sig, sig_len);
if (rv == CKR_OK) {
printf("验签成功:签名有效\n");
} else {
printf("验签失败: 0x%lX\n", rv);
}
// 清理
C_Logout(session);
C_CloseSession(session);
C_Finalize(NULL);
return (rv == CKR_OK) ? 0 : 1;
}
运行与验证
- 初始化HSM令牌(首次运行前执行一次):
softhsm2-util --init- --slot 0 --label MyToken --so-pin 12345678 --pin 12345678 - 编译:
gcc -o rsa_sign_verify rsa_sign_verify.c -lsofthsm2 -lcrypto - 运行:
./rsa_sign_verify
通过条件:程序输出包含“签名成功”和“验签成功:签名有效”,退出码为0。若验签失败或输出错误码,则说明密钥生成、签名或验签流程存在问题。
验证要点
- 签名机制使用
CKM_SHA256_RSA_PKCS,HSM内部自动完成哈希计算,因此C_Sign传入原始消息而非哈希值。 - 验签机制使用
CKM_RSA_PKCS,此时需要显式传入SHA-256哈希值(32字节)。 - 私钥句柄仅在当前会话有效,且私钥对象设置了
CKA_PRIVATE属性,外部无法通过C_GetAttributeValue导出私钥材料。