3.16 密钥管理函数:密钥的“生与死“
密钥管理函数概览
PKCS#11规范第5.18节定义了密钥管理函数,涵盖密钥的生成、包装、解包和派生。
密钥管理函数列表:
函数名 作用
──────────────────────────────────────
C_GenerateKey 生成对称密钥
C_GenerateKeyPair 生成公私钥对
C_WrapKey 包装(加密)密钥
C_UnwrapKey 解包(解密)密钥
C_DeriveKey 从基密钥派生新密钥
密钥管理函数是PKCS#11的“密钥工厂“——所有密钥的创建、备份、恢复都通过这些函数完成。
C_GenerateKey:生成对称密钥
函数原型:
CK_RV C_GenerateKey(
CK_SESSION_HANDLE hSession,
CK_MECHANISM_PTR pMechanism,
CK_ATTRIBUTE_PTR pTemplate,
CK_ULONG ulCount,
CK_OBJECT_HANDLE_PTR phKey
);
密钥生成机制:
对称密钥生成机制:
CKM_AES_KEY_GEN AES密钥(128/192/256位)
CKM_DES_KEY_GEN DES密钥(56位,不推荐)
CKM_DES2_KEY_GEN 2密钥3DES(112位)
CKM_DES3_KEY_GEN 3密钥3DES(168位)
CKM_GENERIC_SECRET_KEY_GEN 通用密钥
国密密钥(厂商扩展,非PKCS#11 v3.1标准):
CKM_SM4_KEY_GEN SM4密钥(128位)
注意:SM4等国密机制是厂商对PKCS#11的扩展,并非OASIS PKCS#11 v3.1标准规范的一部分。
AES密钥生成示例
/* 生成256位AES密钥 */
CK_MECHANISM mechanism = {CKM_AES_KEY_GEN, NULL_PTR, 0};
CK_ULONG keySize = 32; // 32字节 = 256位
CK_BYTE keyId[] = {0x01};
CK_BBOOL true = CK_TRUE;
CK_BBOOL sensitive = CK_TRUE;
CK_BBOOL extractable = CK_FALSE;
CK_ATTRIBUTE keyTemplate[] = {
{CKA_TOKEN, &true, sizeof(true)},
{CKA_VALUE_LEN, &keySize, sizeof(keySize)},
{CKA_ID, keyId, sizeof(keyId)},
{CKA_SENSITIVE, &sensitive, sizeof(sensitive)},
{CKA_EXTRACTABLE, &extractable, sizeof(extractable)},
{CKA_ENCRYPT, &true, sizeof(true)},
{CKA_DECRYPT, &true, sizeof(true)}
};
CK_OBJECT_HANDLE hAesKey;
rv = C_GenerateKey(hSession, &mechanism, keyTemplate, 7, &hAesKey);
if (rv == CKR_OK) {
printf("AES密钥生成成功\n");
// CKA_LOCAL = TRUE(本地生成)
// CKA_ALWAYS_SENSITIVE = TRUE
// CKA_NEVER_EXTRACTABLE = TRUE(因为设置EXTRACTABLE=FALSE)
}
CKA_LOCAL属性的意义
通过C_GenerateKey生成的密钥有特殊属性:
C_GenerateKey生成的密钥属性:
CKA_LOCAL = TRUE
→ 密钥在Token内生成,不是外部导入
CKA_KEY_GEN_MECHANISM = CKM_AES_KEY_GEN
→ 记录密钥的生成机制
CKA_ALWAYS_SENSITIVE = TRUE(如果CKA_SENSITIVE = TRUE)
→ 从创建开始就是敏感的,永不改变
CKA_NEVER_EXTRACTABLE = TRUE(如果CKA_EXTRACTABLE = FALSE)
→ 从创建开始就不可导出,永不改变
与C_CreateObject对比:
密钥来源对比:
C_GenerateKey生成:
- CKA_LOCAL = TRUE
- CKA_ALWAYS_SENSITIVE继承CKA_SENSITIVE
- 密钥值在Token内部产生,更安全
C_CreateObject创建:
- CKA_LOCAL = FALSE
- CKA_ALWAYS_SENSITIVE = FALSE
- 密钥值由应用程序提供,存在泄露风险
C_GenerateKeyPair:生成公私钥对
函数原型:
CK_RV C_GenerateKeyPair(
CK_SESSION_HANDLE hSession,
CK_MECHANISM_PTR pMechanism,
CK_ATTRIBUTE_PTR pPublicKeyTemplate,
CK_ULONG ulPublicKeyAttributeCount,
CK_ATTRIBUTE_PTR pPrivateKeyTemplate,
CK_ULONG ulPrivateKeyAttributeCount,
CK_OBJECT_HANDLE_PTR phPublicKey,
CK_OBJECT_HANDLE_PTR phPrivateKey
);
公私钥对生成机制:
公私钥对生成机制:
CKM_RSA_PKCS_KEY_PAIR_GEN RSA密钥对
CKM_DH_PKCS_KEY_PAIR_GEN DH密钥对
CKM_EC_KEY_PAIR_GEN EC密钥对(通用,通过曲线参数区分ECDSA/ECDH)
国密密钥:
CKM_SM2_KEY_PAIR_GEN SM2密钥对(厂商扩展)
RSA密钥对生成示例
/* 生成2048位RSA密钥对 */
CK_MECHANISM mechanism = {CKM_RSA_PKCS_KEY_PAIR_GEN, NULL_PTR, 0};
CK_ULONG modulusBits = 2048;
CK_BYTE publicExponent[] = {0x01, 0x00, 0x01}; // 65537
CK_BYTE keyId[] = {0x01};
CK_BBOOL true = CK_TRUE;
CK_ATTRIBUTE publicKeyTemplate[] = {
{CKA_TOKEN, &true, sizeof(true)},
{CKA_MODULUS_BITS, &modulusBits, sizeof(modulusBits)},
{CKA_PUBLIC_EXPONENT, publicExponent, sizeof(publicExponent)},
{CKA_ID, keyId, sizeof(keyId)},
{CKA_ENCRYPT, &true, sizeof(true)},
{CKA_VERIFY, &true, sizeof(true)}
};
CK_ATTRIBUTE privateKeyTemplate[] = {
{CKA_TOKEN, &true, sizeof(true)},
{CKA_ID, keyId, sizeof(keyId)},
{CKA_SENSITIVE, &true, sizeof(true)},
{CKA_EXTRACTABLE, &true, sizeof(true)}, // 可导出备份
{CKA_DECRYPT, &true, sizeof(true)},
{CKA_SIGN, &true, sizeof(true)}
};
CK_OBJECT_HANDLE hPublicKey, hPrivateKey;
rv = C_GenerateKeyPair(hSession, &mechanism,
publicKeyTemplate, 6,
privateKeyTemplate, 6,
&hPublicKey, &hPrivateKey);
if (rv == CKR_OK) {
printf("RSA密钥对生成成功\n");
printf("公钥句柄=%lu, 私钥句柄=%lu\n", hPublicKey, hPrivateKey);
}
EC密钥对生成示例
/* 生成ECDSA密钥对(P-256曲线) */
CK_MECHANISM mechanism = {CKM_EC_KEY_PAIR_GEN, NULL_PTR, 0};
// P-256曲线OID:1.2.840.10045.3.1.7
CK_BYTE ecParams[] = {0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07};
CK_BYTE keyId[] = {0x02};
CK_BBOOL true = CK_TRUE;
CK_ATTRIBUTE publicKeyTemplate[] = {
{CKA_TOKEN, &true, sizeof(true)},
{CKA_EC_PARAMS, ecParams, sizeof(ecParams)},
{CKA_ID, keyId, sizeof(keyId)},
{CKA_VERIFY, &true, sizeof(true)}
};
CK_ATTRIBUTE privateKeyTemplate[] = {
{CKA_TOKEN, &true, sizeof(true)},
{CKA_ID, keyId, sizeof(keyId)},
{CKA_SENSITIVE, &true, sizeof(true)},
{CKA_EXTRACTABLE, &true, sizeof(true)},
{CKA_SIGN, &true, sizeof(true)}
};
CK_OBJECT_HANDLE hPublicKey, hPrivateKey;
rv = C_GenerateKeyPair(hSession, &mechanism,
publicKeyTemplate, 4,
privateKeyTemplate, 5,
&hPublicKey, &hPrivateKey);
密钥对的原子性保证
规范明确保证:密钥对生成是原子操作。
C_GenerateKeyPair原子性:
可能结果:
1. 成功 → 创建匹配的公私钥对
2. 失败 → 不创建任何密钥
不可能结果:
- 只创建公钥
- 只创建私钥
- 公私钥不匹配
这是PKCS#11的重要设计原则:
要么全部成功,要么全部失败,保证密钥对的完整性。
C_WrapKey:包装密钥
函数原型:
CK_RV C_WrapKey(
CK_SESSION_HANDLE hSession,
CK_MECHANISM_PTR pMechanism,
CK_OBJECT_HANDLE hWrappingKey,
CK_OBJECT_HANDLE hKey,
CK_BYTE_PTR pWrappedKey,
CK_ULONG_PTR pulWrappedKeyLen
);
用途:将密钥加密导出,用于备份或传输。
密钥包装场景:
场景1:密钥备份
┌─────────────────────────────────────┐
│ Token A │
│ │
│ AES密钥(hKey) │
│ CKA_EXTRACTABLE = TRUE │
│ │
│ C_WrapKey(hWrappingKey, hKey) │
│ → wrappedKey │
│ │
│ 存储wrappedKey到安全备份服务器 │
└─────────────────────────────────────┘
场景2:密钥传输
┌─────────────────────────────────────┐
│ Token A → Token B │
│ │
│ AES密钥 │
│ C_WrapKey → wrappedKey │
│ │
│ 通过安全通道传输wrappedKey │
│ │
│ Token B │
│ C_UnwrapKey → 新密钥 │
└─────────────────────────────────────┘
AES密钥包装示例
/* AES密钥包装(使用AES密钥加密) */
CK_MECHANISM mechanism = {CKM_AES_KEY_WRAP, NULL_PTR, 0};
CK_OBJECT_HANDLE hWrappingKey; // 包装密钥(AES)
CK_OBJECT_HANDLE hKeyToWrap; // 待包装密钥(AES或私钥)
CK_BYTE wrappedKey[256];
CK_ULONG wrappedKeyLen = sizeof(wrappedKey);
rv = C_WrapKey(hSession, &mechanism, hWrappingKey, hKeyToWrap,
wrappedKey, &wrappedKeyLen);
if (rv == CKR_OK) {
printf("密钥包装成功,长度=%lu\n", wrappedKeyLen);
// 可以安全存储wrappedKey
}
密钥包装约束
密钥包装受到多个属性约束:
包装约束:
包装密钥(hWrappingKey):
- CKA_WRAP = TRUE(必须支持包装)
被包装密钥(hKey):
- CKA_EXTRACTABLE = TRUE(必须可导出)
失败情况:
CKR_KEY_UNEXTRACTABLE → CKA_EXTRACTABLE = FALSE
CKR_KEY_NOT_WRAPPABLE → Token特定原因无法包装
CKR_KEY_SIZE_RANGE → 密钥长度不匹配机制
CKR_WRAPPING_KEY_TYPE_INCONSISTENT → 包装密钥类型不匹配
RSA密钥包装
使用RSA公钥包装对称密钥:
/* RSA公钥包装AES密钥 */
CK_MECHANISM mechanism = {CKM_RSA_PKCS, NULL_PTR, 0};
CK_OBJECT_HANDLE hPublicKey; // RSA公钥
CK_OBJECT_HANDLE hAesKey; // AES密钥
CK_BYTE wrappedKey[256]; // RSA密钥长度
CK_ULONG wrappedKeyLen = sizeof(wrappedKey);
rv = C_WrapKey(hSession, &mechanism, hPublicKey, hAesKey,
wrappedKey, &wrappedKeyLen);
if (rv == CKR_OK) {
// wrappedKey是用RSA公钥加密的AES密钥
// 只有对应的RSA私钥才能解包
}
C_UnwrapKey:解包密钥
函数原型:
CK_RV C_UnwrapKey(
CK_SESSION_HANDLE hSession,
CK_MECHANISM_PTR pMechanism,
CK_OBJECT_HANDLE hUnwrappingKey,
CK_BYTE_PTR pWrappedKey,
CK_ULONG ulWrappedKeyLen,
CK_ATTRIBUTE_PTR pTemplate,
CK_ULONG ulAttributeCount,
CK_OBJECT_HANDLE_PTR phKey
);
/* AES密钥解包 */
CK_MECHANISM mechanism = {CKM_AES_KEY_WRAP, NULL_PTR, 0};
CK_OBJECT_HANDLE hUnwrappingKey; // 解包密钥
CK_BYTE wrappedKey[] = {...}; // 包装后的密钥数据
CK_ULONG wrappedKeyLen = sizeof(wrappedKey);
CK_BYTE keyId[] = {0x03};
CK_BBOOL true = CK_TRUE;
CK_BBOOL sensitive = CK_TRUE;
CK_BBOOL extractable = CK_FALSE;
CK_ATTRIBUTE keyTemplate[] = {
{CKA_TOKEN, &true, sizeof(true)},
{CKA_ID, keyId, sizeof(keyId)},
{CKA_SENSITIVE, &sensitive, sizeof(sensitive)},
{CKA_EXTRACTABLE, &extractable, sizeof(extractable)}
};
CK_OBJECT_HANDLE hNewKey;
rv = C_UnwrapKey(hSession, &mechanism, hUnwrappingKey,
wrappedKey, wrappedKeyLen,
keyTemplate, 4, &hNewKey);
if (rv == CKR_OK) {
printf("密钥解包成功\n");
// CKA_LOCAL = FALSE(不是本地生成)
}
C_DeriveKey:密钥派生
函数原型:
CK_RV C_DeriveKey(
CK_SESSION_HANDLE hSession,
CK_MECHANISM_PTR pMechanism,
CK_OBJECT_HANDLE hBaseKey,
CK_ATTRIBUTE_PTR pTemplate,
CK_ULONG ulAttributeCount,
CK_OBJECT_HANDLE_PTR phKey
);
用途:从基密钥派生新密钥,用于密钥协商、密钥分层等场景。
密钥派生场景:
场景1:ECDH密钥协商
┌─────────────────────────────────────┐
│ Alice │
│ │
│ EC私钥(hPrivateKeyA) │
│ │
│ 收到Bob的EC公钥 │
│ │
│ C_DeriveKey(hPrivateKeyA, │
│ mechanism={ECDH, │
│ BobPublicKey}) │
│ → 共享密钥 │
└─────────────────────────────────────┘
场景2:HKDF密钥派生
┌─────────────────────────────────────┐
│ 密钥分层 │
│ │
│ 主密钥(Master Key) │
│ │
│ C_DeriveKey → 加密密钥 │
│ C_DeriveKey → HMAC密钥 │
│ C_DeriveKey → 会话密钥 │
└─────────────────────────────────────┘
ECDH密钥派生示例
/* ECDH密钥协商 */
CK_ECDH1_DERIVE_PARAMS ecdhParams = {
.kdf = CKD_NULL, // 密钥派生函数
.ulSharedDataLen = 0,
.pSharedData = NULL_PTR,
.ulPublicDataLen = sizeof(bobPublicKey),
.pPublicData = bobPublicKey // Bob的公钥数据
};
CK_MECHANISM mechanism = {
CKM_ECDH1_DERIVE,
&ecdhParams,
sizeof(ecdhParams)
};
CK_BYTE keyId[] = {0x05};
CK_BBOOL true = CK_TRUE;
CK_ATTRIBUTE keyTemplate[] = {
{CKA_TOKEN, &true, sizeof(true)},
{CKA_ID, keyId, sizeof(keyId)},
{CKA_KEY_TYPE, &(CK_KEY_TYPE){CKK_AES}, sizeof(CK_KEY_TYPE)},
{CKA_VALUE_LEN, &(CK_ULONG){32}, sizeof(CK_ULONG)},
{CKA_SENSITIVE, &true, sizeof(true)}
};
CK_OBJECT_HANDLE hSharedKey;
rv = C_DeriveKey(hSession, &mechanism, hPrivateKeyA,
keyTemplate, 5, &hSharedKey);
if (rv == CKR_OK) {
printf("共享密钥派生成功\n");
// hSharedKey与Bob派生的密钥相同
}
HKDF密钥派生
HKDF(HMAC-based Key Derivation Function)是常用的密钥派生方法:
/* HKDF密钥派生 */
CK_HKDF_PARAMS hkdfParams = {
TRUE, /* bExtract */
TRUE, /* bExpand */
CKM_SHA256, /* prfHashMechanism */
CKZ_SALT_SPECIFIED, /* ulSaltType */
salt, sizeof(salt), /* pSalt, ulSaltLen */
CK_INVALID_HANDLE, /* hSaltKey */
info, sizeof(info) /* pInfo, ulInfoLen */
};
CK_MECHANISM mechanism = {
CKM_HKDF_DERIVE,
&hkdfParams,
sizeof(hkdfParams)
};
CK_ULONG keyLen = 32; /* 派生32字节密钥 */
CK_ATTRIBUTE keyTemplate[] = {
{CKA_TOKEN, &true, sizeof(CK_BBOOL)},
{CKA_VALUE_LEN, &keyLen, sizeof(CK_ULONG)},
{CKA_KEY_TYPE, &(CK_KEY_TYPE){CKK_AES}, sizeof(CK_KEY_TYPE)}
};
CK_OBJECT_HANDLE hDerivedKey;
rv = C_DeriveKey(hSession, &mechanism, hMasterKey,
keyTemplate, 3, &hDerivedKey);
密钥派生与安全属性
派生密钥的安全属性受到基密钥属性的影响:
派生密钥安全属性继承:
基密钥属性 派生密钥属性约束
───────────────────────────────────────
CKA_SENSITIVE = TRUE → 派生密钥可以设置SENSITIVE=TRUE
CKA_SENSITIVE = FALSE → 派生密钥必须SENSITIVE=FALSE
CKA_EXTRACTABLE = TRUE → 派生密钥可以设置EXTRACTABLE=TRUE
CKA_EXTRACTABLE = FALSE → 派生密钥必须EXTRACTABLE=FALSE
CKA_ALWAYS_SENSITIVE → 继承自基密钥
CKA_NEVER_EXTRACTABLE → 继承自基密钥
密钥生命周期管理
密钥完整生命周期:
1. 生成
┌─────────────────────────────────────┐
│ C_GenerateKey / C_GenerateKeyPair │
│ │
│ Token内生成 │
│ CKA_LOCAL = TRUE │
│ 安全属性完整 │
└─────────────────────────────────────┘
2. 使用
┌─────────────────────────────────────┐
│ C_Encrypt / C_Decrypt │
│ C_Sign / C_Verify │
│ │
│ 密钥生命周期内反复使用 │
└─────────────────────────────────────┘
3. 备份(可选)
┌─────────────────────────────────────┐
│ C_WrapKey │
│ │
│ 加密导出到备份介质 │
│ CKA_EXTRACTABLE = TRUE │
└─────────────────────────────────────┘
4. 恢复(可选)
┌─────────────────────────────────────┐
│ C_UnwrapKey │
│ │
│ 从备份导入到Token │
│ CKA_LOCAL = FALSE │
└─────────────────────────────────────┘
5. 派生(可选)
┌─────────────────────────────────────┐
│ C_DeriveKey │
│ │
│ 从基密钥派生子密钥 │
│ 密钥分层管理 │
└─────────────────────────────────────┘
6. 销毁
┌─────────────────────────────────────┐
│ C_DestroyObject │
│ │
│ 密钥从Token删除 │
│ CKA_DESTROYABLE = TRUE │
└─────────────────────────────────────┘
小结:密钥管理函数的设计智慧
密钥管理要点回顾:
1. 生成密钥的安全优势
- CKA_LOCAL = TRUE(本地生成)
- 密钥值不暴露给应用程序
- CKA_ALWAYS_SENSITIVE保护敏感密钥
2. 密钥对的原子性
- 公私钥要么同时生成,要么都不生成
- 保证密钥对的匹配性
3. 包装导出的可控性
- CKA_EXTRACTABLE控制导出权限
- CKA_WRAP控制包装权限
- 包装后密钥安全传输
4. 派生密钥的层次管理
- ECDH用于密钥协商
- HKDF用于密钥分层
- 派生密钥继承安全属性
5. 密钥来源区分
- 本地生成:最安全,CKA_LOCAL=TRUE
- 外部导入:需谨慎,CKA_LOCAL=FALSE
- 密钥派生:层次管理,继承基密钥属性
密钥管理函数为PKCS#11提供了完整的密钥生命周期管理能力,从生成、使用到备份、销毁,构成密钥安全的“闭环“。 下一节,我们将学习Mechanism系统——看算法机制的定义。
【下集预告】
Mechanism是什么?
CK_MECHANISM结构是什么?
Mechanism怎么查询和选择?
下一节,Mechanism系统。