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核心流程