3. 使用HSM进行RSA签名与验签

使用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;
}

运行与验证

  1. 初始化HSM令牌(首次运行前执行一次):
    softhsm2-util --init- --slot 0 --label MyToken --so-pin 12345678 --pin 12345678
  2. 编译:
    gcc -o rsa_sign_verify rsa_sign_verify.c -lsofthsm2 -lcrypto
  3. 运行:
    ./rsa_sign_verify

通过条件:程序输出包含“签名成功”和“验签成功:签名有效”,退出码为0。若验签失败或输出错误码,则说明密钥生成、签名或验签流程存在问题。

验证要点

  • 签名机制使用CKM_SHA256_RSA_PKCS,HSM内部自动完成哈希计算,因此C_Sign传入原始消息而非哈希值。
  • 验签机制使用CKM_RSA_PKCS,此时需要显式传入SHA-256哈希值(32字节)。
  • 私钥句柄仅在当前会话有效,且私钥对象设置了CKA_PRIVATE属性,外部无法通过C_GetAttributeValue导出私钥材料。