2. 生成RSA密钥对并导出公钥

任务目标

在硬件安全模块(HSM)中生成RSA-2048密钥对,并将公钥以标准格式导出。密钥对必须安全存储在HSM内部,私钥永不出设备,仅公钥可导出用于外部加密或验签。

环境依赖

本案例基于OpenSC/OpenSSL的PKCS#11接口实现,需要以下环境:

  • 支持PKCS#11的HSM设备或模拟器(如SoftHSMv2)
  • OpenSC(提供pkcs11-tool)或OpenSSL 3.x(支持PKCS#11 provider)
  • Linux环境(示例命令基于Ubuntu/Debian)

实现方案

使用PKCS#11标准接口完成RSA密钥对生成与公钥导出。核心流程为:初始化会话 → 设置公钥模板与私钥模板 → 调用C_GenerateKeyPair → 使用C_GetAttributeValue读取公钥模数与指数 → 组装为DER编码的SubjectPublicKeyInfo结构。

关键完整代码


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "pkcs11.h"

#define MODULUS_BYTES 256  // RSA-2048

static CK_RV generate_and_export_key(CK_SLOT_ID slot_id,
                                     CK_BYTE_PTR *out_der, CK_ULONG *out_len) {
    CK_RV rv;
    CK_FUNCTION_LIST_PTR funcs;
    CK_SESSION_HANDLE session;
    CK_OBJECT_HANDLE pub_key, priv_key;
    CK_ULONG mod_len, exp_len;
    CK_BYTE modulus[MODULUS_BYTES];
    CK_BYTE exponent[3] = {0x01, 0x00, 0x01}; // 65537

    // 获取函数列表
    rv = C_GetFunctionList(&funcs);
    if (rv != CKR_OK) return rv;

    // 初始化
    rv = funcs->C_Initialize(NULL);
    if (rv != CKR_OK && rv != CKR_CRYPTOKI_ALREADY_INITIALIZED) return rv;

    // 打开会话
    rv = funcs->C_OpenSession(slot_id, CKF_SERIAL_SESSION | CKF_RW_SESSION,
                              NULL, NULL, &session);
    if (rv != CKR_OK) return rv;

    // 公钥模板
    CK_OBJECT_CLASS pub_class = CKO_PUBLIC_KEY;
    CK_KEY_TYPE key_type = CKK_RSA;
    CK_BBOOL true_val = CK_TRUE;
    CK_BYTE subject[] = "RSA-2048 Key";
    CK_ATTRIBUTE pub_template[] = {
        {CKA_CLASS, &pub_class, sizeof(pub_class)},
        {CKA_KEY_TYPE, &key_type, sizeof(key_type)},
        {CKA_, &true_val, sizeof(true_val)},
        {CKA_ENCRYPT, &true_val, sizeof(true_val)},
        {CKA_VERIFY, &true_val, sizeof(true_val)},
        {CKA_MODULUS_BITS, (CK_VOID_PTR)2048, sizeof(CK_ULONG)},
        {CKA_PUBLIC_EXPONENT, exponent, sizeof(exponent)},
        {CKA_LABEL, subject, sizeof(subject)-1}
    };

    // 私钥模板
    CK_BBOOL false_val = CK_FALSE;
    CK_ATTRIBUTE priv_template[] = {
        {CKA_CLASS, &priv_class, sizeof(priv_class)},
        {CKA_KEY_TYPE, &key_type, sizeof(key_type)},
        {CKA_, &true_val, sizeof(true_val)},
        {CKA_PRIVATE, &true_val, sizeof(true_val)},
        {CKA_SIGN, &true_val, sizeof(true_val)},
        {CKA_DECRYPT, &true_val, sizeof(true_val)},
        {CKA_LABEL, subject, sizeof(subject)-1}
    };
    CK_OBJECT_CLASS priv_class = CKO_PRIVATE_KEY;

    // 生成密钥对
    rv = funcs->C_GenerateKeyPair(session,
                                  CKF_GENERATE_KEY_PAIR,
                                  pub_template, 7,
                                  priv_template, 6,
                                  &pub_key, &priv_key);
    if (rv != CKR_OK) goto cleanup;

    // 读取公钥模数
    CK_ATTRIBUTE mod_attr = {CKA_MODULUS, NULL, 0};
    rv = funcs->C_GetAttributeValue(session, pub_key, &mod_attr, 1);
    if (rv != CKR_OK) goto cleanup;
    mod_len = mod_attr.ulValueLen;
    mod_attr.pValue = modulus;
    rv = funcs->C_GetAttributeValue(session, pub_key, &mod_attr, 1);
    if (rv != CKR_OK) goto cleanup;

    // 读取公钥指数
    CK_ATTRIBUTE exp_attr = {CKA_PUBLIC_EXPONENT, NULL, 0};
    rv = funcs->C_GetAttributeValue(session, pub_key, &exp_attr, 1);
    if (rv != CKR_OK) goto cleanup;
    exp_len = exp_attr.ulValueLen;
    CK_BYTE *exp_buf = malloc(exp_len);
    exp_attr.pValue = exp_buf;
    rv = funcs->C_GetAttributeValue(session, pub_key, &exp_attr, 1);
    if (rv != CKR_OK) { free(exp_buf); goto cleanup; }

    // 组装DER编码的SubjectPublicKeyInfo
    // 使用OpenSSL辅助构造(简化示例,实际需完整DER编码)
    // 此处省略DER编码细节,实际工程中调用i2d_RSA_PUBKEY或手动构造
    *out_der = NULL;
    *out_len = 0;

    free(exp_buf);
cleanup:
    funcs->C_CloseSession(session);
    funcs->C_Finalize(NULL);
    return rv;
}

int main() {
    CK_BYTE_PTR der = NULL;
    CK_ULONG der_len = 0;
    CK_RV rv = generate_and_export_key(0, &der, &der_len);
    if (rv == CKR_OK) {
        printf("RSA key pair generated successfully\n");
        // 实际使用时将der写入文件或输出
    } else {
        printf("Failed: 0x%lX\n", rv);
    }
    return rv == CKR_OK ? 0 : 1;
}

运行与验证

使用SoftHSM作为PKCS#11实现进行验证:


# 安装并初始化SoftHSM
sudo apt install softhsm2 opensc
sudo mkdir -p /var/lib/softhsm/tokens
sudo softhsm2-util --init- --slot 0 --label test --pin 1234 --so-pin 1234

# 编译(需链接PKCS#11库)
gcc -o rsa_gen rsa_gen.c -I/usr/include/pkcs11 -L/usr/lib/softhsm -lsofthsm2

# 运行
./rsa_gen

验证通过条件:

  • 程序返回0且输出"RSA key pair generated successfully"
  • 使用pkcs11-tool --list-objects --pin 1234可看到生成的RSA公钥与私钥对象
  • 私钥对象标记为private且无法通过pkcs11-tool --read-object导出

常见问题

  • CKR_SLOT_ID_INVALID:确认SoftHSM 已初始化且slot编号正确
  • CKR_USER_NOT_LOGGED_IN:需在生成密钥前调用C_Login(PIN为1234)
  • DER导出:完整实现需使用OpenSSL的i2d_RSA_PUBKEY函数将模数与指数编码为标准SPKI格式,本示例聚焦PKCS#11核心流程