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.21 安全属性组合实战:密钥安全的“最佳配方“

安全属性回顾

在深入实战之前,回顾四大安全属性:

四大安全属性:

CKA_SENSITIVE          密钥值是否可读取
CKA_EXTRACTABLE        密钥是否可导出
CKA_ALWAYS_SENSITIVE   创建时是否敏感(不可变为非敏感)
CKA_NEVER_EXTRACTABLE  创建时是否不可导出(不可变为可导出)

关键关系:
- CKA_ALWAYS_SENSITIVE ← 源于创建时的CKA_SENSITIVE
- CKA_NEVER_EXTRACTABLE ← 源于创建时的CKA_EXTRACTABLE
- 创建后无法"升级"安全级别
- 创建后无法"降级"CKA_ALWAYS_SENSITIVE/CKA_NEVER_EXTRACTABLE

安全属性组合矩阵

四种安全属性可以组合成不同的安全级别:

安全属性组合矩阵:

组合编号   SENSITIVE  EXTRACTABLE  ALWAYS_SENSITIVE  NEVER_EXTRACTABLE  安全级别
──────────────────────────────────────────────────────────────────────────────
组合1      TRUE       FALSE        TRUE              TRUE               最高安全
组合2      TRUE       TRUE         TRUE              FALSE              可备份安全
组合3      FALSE      FALSE        FALSE             TRUE               开发测试
组合4      FALSE      TRUE         FALSE             FALSE              开发测试
组合5      TRUE       FALSE        FALSE             TRUE               不可能!(C_CreateObject时NEVER_EXTRACTABLE不可为TRUE)
组合6      FALSE      TRUE         TRUE              FALSE              不可能!

注:
- 组合6不可能:CKA_ALWAYS_SENSITIVE=TRUE意味着创建时CKA_SENSITIVE=TRUE,但当前SENSITIVE=FALSE矛盾
- 组合5不可能:SENSITIVE=TRUE与ALWAYS_SENSITIVE=FALSE矛盾——创建时SENSITIVE=TRUE会触发ALWAYS_SENSITIVE自动锁定为TRUE

组合1:最高安全密钥

“锁在Token内,永不导出,永不暴露”

最高安全密钥配置:

密钥特点:
├─ CKA_SENSITIVE = TRUE        → 密钥值不可读取
├─ CKA_EXTRACTABLE = FALSE     → 密钥不可导出
├─ CKA_ALWAYS_SENSITIVE = TRUE → 永远保持敏感
├─ CKA_NEVER_EXTRACTABLE = TRUE → 永远不可导出
└─ CKA_LOCAL = TRUE            → 本地生成

应用场景:
├─ 核心签名密钥(如安全启动签名)
├─ 核心加密密钥(如主密钥)
├─ 证书签发密钥
└─ 不可恢复的关键密钥

生命周期:
┌─────────────────────────────────────┐
│ Token内生成                          │
│                                     │
│ 密钥生命周期:                       │
│ ├─ 签名/加密                        │
│ ├─ 签名/加密                        │
│ ├─ 签名/加密                        │
│ └─ ...                              │
│                                     │
│ 无法导出备份                        │
│ 无法读取密钥值                      │
│ Token损坏=密钥丢失                  │
│                                     │
│ 销毁时:Token内删除                  │
└─────────────────────────────────────┘
/* 创建最高安全密钥 */

CK_MECHANISM mechanism = {CKM_AES_KEY_GEN, NULL_PTR, 0};

CK_ULONG keySize = 32;
CK_BBOOL true = CK_TRUE;
CK_BBOOL false = CK_FALSE;

CK_ATTRIBUTE keyTemplate[] = {
    {CKA_TOKEN, &true, sizeof(true)},
    {CKA_VALUE_LEN, &keySize, sizeof(keySize)},
    {CKA_SENSITIVE, &true, sizeof(true)},
    {CKA_EXTRACTABLE, &false, sizeof(false)},
    {CKA_ENCRYPT, &true, sizeof(true)},
    {CKA_DECRYPT, &true, sizeof(true)}
};

CK_OBJECT_HANDLE hKey;
rv = C_GenerateKey(hSession, &mechanism, keyTemplate, 6, &hKey);

// 生成后:
// CKA_ALWAYS_SENSITIVE = TRUE(自动设置)
// CKA_NEVER_EXTRACTABLE = TRUE(自动设置)
// 密钥永不暴露,永不导出

组合2:可备份安全密钥

“安全但可导出备份”

可备份安全密钥配置:

密钥特点:
├─ CKA_SENSITIVE = TRUE        → 密钥值不可读取
├─ CKA_EXTRACTABLE = TRUE      → 密钥可导出
├─ CKA_ALWAYS_SENSITIVE = TRUE → 永远保持敏感
├─ CKA_NEVER_EXTRACTABLE = FALSE → 可以导出
└─ CKA_LOCAL = TRUE            → 本地生成

应用场景:
├─ 需要备份的密钥
├─ 需要迁移到其他Token的密钥
├─ 密钥生命周期管理
└─ 多Token部署

生命周期:
┌─────────────────────────────────────┐
│ Token内生成                          │
│                                     │
│ 正常使用:                           │
│ ├─ 签名/加密                        │
│ ├─ 签名/加密                        │
│                                     │
│ 需要备份时:                         │
│ C_WrapKey → wrappedKey              │
│ 存储到安全备份服务器                 │
│                                     │
│ Token损坏时:                        │
│ 从备份服务器恢复                     │
│ C_UnwrapKey → 恢复密钥              │
│                                     │
│ 恢复的密钥:                         │
│ CKA_LOCAL = FALSE                   │
│ CKA_SENSITIVE = TRUE(保持)        │
│ CKA_ALWAYS_SENSITIVE = FALSE(变化)│
└─────────────────────────────────────┘
/* 创建可备份安全密钥 */

CK_ATTRIBUTE keyTemplate[] = {
    {CKA_TOKEN, &true, sizeof(true)},
    {CKA_VALUE_LEN, &keySize, sizeof(keySize)},
    {CKA_SENSITIVE, &true, sizeof(true)},
    {CKA_EXTRACTABLE, &true, sizeof(true)},  // 可导出
    {CKA_ENCRYPT, &true, sizeof(true)},
    {CKA_DECRYPT, &true, sizeof(true)}
};

CK_OBJECT_HANDLE hKey;
rv = C_GenerateKey(hSession, &mechanism, keyTemplate, 6, &hKey);

// 生成后:
// CKA_ALWAYS_SENSITIVE = TRUE
// CKA_NEVER_EXTRACTABLE = FALSE
// 可以通过C_WrapKey导出

备份流程

/* 密钥备份流程 */

// 1. 生成包装密钥(用于备份)
CK_OBJECT_HANDLE hBackupKey;
rv = C_GenerateKey(hSession, &backupMech, backupTemplate, ..., &hBackupKey);

// 2. 包装需要备份的密钥
CK_MECHANISM wrapMech = {CKM_AES_KEY_WRAP, NULL_PTR, 0};
CK_BYTE wrappedKey[256];
CK_ULONG wrappedKeyLen;

rv = C_WrapKey(hSession, &wrapMech, hBackupKey, hKey,
               wrappedKey, &wrappedKeyLen);

// 3. 存储wrappedKey到安全备份服务器
store_to_backup_server(wrappedKey, wrappedKeyLen);

// 4. 销毁原始密钥(可选)
rv = C_DestroyObject(hSession, hKey);

恢复流程

/* 密钥恢复流程 */

// 1. 从备份服务器获取wrappedKey
CK_BYTE wrappedKey[256];
CK_ULONG wrappedKeyLen;
retrieve_from_backup_server(wrappedKey, &wrappedKeyLen);

// 2. 解包恢复密钥
CK_MECHANISM unwrapMech = {CKM_AES_KEY_WRAP, NULL_PTR, 0};

CK_ATTRIBUTE keyTemplate[] = {
    {CKA_TOKEN, &true, sizeof(true)},
    {CKA_SENSITIVE, &true, sizeof(true)},
    {CKA_ENCRYPT, &true, sizeof(true)},
    {CKA_DECRYPT, &true, sizeof(true)}
};

CK_OBJECT_HANDLE hRestoredKey;
rv = C_UnwrapKey(hSession, &unwrapMech, hBackupKey,
                 wrappedKey, wrappedKeyLen,
                 keyTemplate, 4, &hRestoredKey);

// 恢复后的密钥:
// CKA_LOCAL = FALSE(非本地生成)
// CKA_ALWAYS_SENSITIVE = FALSE(恢复时设置的)

组合3:开发测试密钥

“可读取、可导出、仅供开发调试”

开发测试密钥配置:

密钥特点:
├─ CKA_SENSITIVE = FALSE       → 密钥值可读取
├─ CKA_EXTRACTABLE = FALSE     → 密钥不可导出(可选)
├─ CKA_ALWAYS_SENSITIVE = FALSE → 可以变为敏感
├─ CKA_NEVER_EXTRACTABLE = TRUE → 永远不可导出
└─ CKA_LOCAL = TRUE            → 本地生成

应用场景:
├─ 开发调试阶段
├─ 单元测试
├─ 密钥值验证
└─ 安全分析(测试环境)

警告:
⚠️ 生产环境严禁使用此配置!
⚠️ 密钥值可读取,存在泄露风险!
⚠️ 仅用于开发环境调试!
/* 开发测试密钥(仅用于调试) */

CK_ATTRIBUTE keyTemplate[] = {
    {CKA_TOKEN, &true, sizeof(true)},
    {CKA_VALUE_LEN, &keySize, sizeof(keySize)},
    {CKA_SENSITIVE, &false, sizeof(false)},  // 可读取!
    {CKA_EXTRACTABLE, &false, sizeof(false)},
    {CKA_ENCRYPT, &true, sizeof(true)},
    {CKA_DECRYPT, &true, sizeof(true)}
};

CK_OBJECT_HANDLE hKey;
rv = C_GenerateKey(hSession, &mechanism, keyTemplate, 6, &hKey);

// 可以读取密钥值(仅调试用!)
CK_BYTE keyValue[32];
CK_ATTRIBUTE getValue = {CKA_VALUE, keyValue, 32};
rv = C_GetAttributeValue(hSession, hKey, &getValue, 1);
// 成功读取密钥值(不安全!)

RSA密钥的安全配置

RSA密钥对的安全配置示例:

/* 最高安全RSA密钥对 */

CK_ATTRIBUTE publicKeyTemplate[] = {
    {CKA_TOKEN, &true, sizeof(true)},
    {CKA_MODULUS_BITS, &modulusBits, sizeof(modulusBits)},
    {CKA_ENCRYPT, &true, sizeof(true)},
    {CKA_VERIFY, &true, sizeof(true)}
};

CK_ATTRIBUTE privateKeyTemplate[] = {
    {CKA_TOKEN, &true, sizeof(true)},
    {CKA_SENSITIVE, &true, sizeof(true)},
    {CKA_EXTRACTABLE, &false, sizeof(false)},  // 不可导出
    {CKA_DECRYPT, &true, sizeof(true)},
    {CKA_SIGN, &true, sizeof(true)}
};

CK_OBJECT_HANDLE hPublicKey, hPrivateKey;
rv = C_GenerateKeyPair(hSession, &mechanism,
                       publicKeyTemplate, 4,
                       privateKeyTemplate, 5,
                       &hPublicKey, &hPrivateKey);

// 私钥:
// CKA_SENSITIVE = TRUE → 私钥值不可读取
// CKA_EXTRACTABLE = FALSE → 私钥不可导出
// CKA_ALWAYS_SENSITIVE = TRUE → 永远敏感
// CKA_NEVER_EXTRACTABLE = TRUE → 永远不可导出

// 公钥:
// 公钥默认不敏感(CKA_SENSITIVE = FALSE)
// 公钥可以读取(公钥本来就是公开的)

车载密钥配置最佳实践

车载密钥安全配置矩阵:

密钥类型              安全配置              备份策略
───────────────────────────────────────────────────
安全启动签名密钥      最高安全              不备份
                      SENSITIVE=TRUE        Token损坏需重新生成
                      EXTRACTABLE=FALSE     新密钥需更新证书链

诊断认证签名密钥      可备份安全            定期备份
                      SENSITIVE=TRUE        备份到安全服务器
                      EXTRACTABLE=TRUE      多Token部署

V2X通信签名密钥       可备份安全            定期备份
                      SENSITIVE=TRUE        密钥轮换支持
                      EXTRACTABLE=TRUE

会话加密密钥          最高安全              不备份
(运行时生成)        SENSITIVE=TRUE        运行时生成
                      EXTRACTABLE=FALSE     使用后销毁

日志加密密钥          可备份安全            定期备份
                      SENSITIVE=TRUE        防止日志解密困难
                      EXTRACTABLE=TRUE

HMAC密钥              可备份安全            定期备份
                      SENSITIVE=TRUE
                      EXTRACTABLE=TRUE

密钥轮换与生命周期管理

密钥轮换流程:

1. 当前密钥即将过期
   ┌─────────────────────────────────────┐
   │ 当前密钥Key_A                        │
   │ CKA_END_DATE即将到达                 │
   │                                     │
   │ 需要生成新密钥Key_B                  │
   └─────────────────────────────────────┘

2. 生成新密钥
   ┌─────────────────────────────────────┐
   │ C_GenerateKey → Key_B               │
   │                                     │
   │ Key_B配置与Key_A相同                 │
   │ CKA_START_DATE = 现在               │
   │ CKA_END_DATE = 一年后               │
   └─────────────────────────────────────┘

3. 备份新密钥
   ┌─────────────────────────────────────┐
   │ 如果EXTRACTABLE=TRUE:              │
   │ C_WrapKey → backupKey_B             │
   │ 存储到备份服务器                     │
   └─────────────────────────────────────┘

4. 更新引用
   ┌─────────────────────────────────────┐
   │ 应用程序切换到新密钥                 │
   │ C_FindObjects(CKA_ID=newId)         │
   │ 使用Key_B进行签名/加密               │
   └─────────────────────────────────────┘

5. 销毁旧密钥
   ┌─────────────────────────────────────┐
   │ 确保Key_A不再使用                    │
   │ C_DestroyObject(Key_A)              │
   └─────────────────────────────────────┘

密钥导入的安全考虑

导入外部密钥时,安全属性会被“降级“:

密钥导入vs本地生成的安全差异:

本地生成(C_GenerateKey):
├─ CKA_LOCAL = TRUE
├─ CKA_ALWAYS_SENSITIVE继承创建时的SENSITIVE
└─ 密钥值在Token内产生,从未暴露

外部导入(C_CreateObject):
├─ CKA_LOCAL = FALSE
├─ CKA_ALWAYS_SENSITIVE = FALSE(即使SENSITIVE=TRUE)
├─ 密钥值由应用程序提供
└─ 存在传输泄露风险

C_UnwrapKey恢复:
├─ CKA_LOCAL = FALSE
├─ CKA_ALWAYS_SENSITIVE = FALSE
└─ 但密钥值通过加密传输,更安全

结论:
本地生成最安全
C_UnwrapKey次安全(加密传输)
C_CreateObject最不安全(明文传输)

密钥安全检查清单

部署前安全检查:

□ 密钥生成
  ├─ 使用C_GenerateKey/C_GenerateKeyPair
  ├─ 避免C_CreateObject导入明文密钥
  └─ CKA_SENSITIVE = TRUE

□ 密钥导出控制
  ├─ EXTRACTABLE = FALSE → 不导出
  ├─ EXTRACTABLE = TRUE → 需备份机制
  └─ 导出使用C_WrapKey加密

□ 密钥读取控制
  ├─ CKA_SENSITIVE = TRUE → 不可读取
  ├─ 验证C_GetAttributeValue返回CKR_ATTRIBUTE_SENSITIVE
  └─ 开发调试密钥仅用于测试环境

□ 密钥备份
  ├─ 可备份密钥需要包装密钥
  ├─ 包装密钥存储安全
  └─ 备份密钥定期验证

□ 密钥生命周期
  ├─ CKA_START_DATE/CKA_END_DATE设置
  ├─ 密钥轮换计划
  └─ 过期密钥销毁

□ 权限控制
  ├─ CKA_ENCRYPT/DECRYPT/SIGN/VERIFY
  ├─ CKA_DERIVE(密钥派生)
  └─ 最小权限原则

小结:安全属性组合的核心原则

安全属性组合要点回顾:

1. 四种组合的安全级别
   - 最高安全:SENSITIVE=T, EXTRACTABLE=F
   - 可备份安全:SENSITIVE=T, EXTRACTABLE=T
   - 开发测试:SENSITIVE=F(仅测试)
   - 不稳定配置:可以修改SENSITIVE(降级风险)

2. 创建时决定安全等级
   - CKA_ALWAYS_SENSITIVE源于创建时
   - CKA_NEVER_EXTRACTABLE源于创建时
   - 无法事后"升级"安全级别

3. 本地生成最安全
   - CKA_LOCAL = TRUE
   - 密钥值在Token内产生
   - 从未暴露给应用程序

4. 备份与恢复
   - C_WrapKey加密导出
   - C_UnwrapKey加密恢复
   - C_CreateObject明文导入(不安全)

5. 车载最佳实践
   - 核心密钥:最高安全,不备份
   - 需备份密钥:可备份安全,加密备份
   - 开发密钥:仅测试环境,不用于生产

6. 安全检查清单
   - 部署前逐项检查
   - 定期审计密钥配置
   - 密钥轮换机制完备

安全属性组合是密钥安全的核心,理解并正确使用这些属性,是实现车载安全系统的关键技能。 下一章,我们将深入开源实现——看PKCS#11如何被SoftHSM2落地。

【第三章总结】

第三章结束了。我们学习了PKCS#11的完整规范:

  • 设计哲学:接口与实现分离,机制而非策略
  • 对象模型:Slot、Token、Session、Object
  • 函数接口:初始化、管理、密码运算
  • 机制系统:AES、RSA、ECC、哈希
  • 安全属性:SENSITIVE、EXTRACTABLE等组合

理论学习完成了,接下来看实践——开源实现。

第四章,SoftHSM2源码分析。