Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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系统。