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源码分析。