PKCS#11 对象模型:数据对象、密钥对象、证书对象
说实话,搞 PKCS#11 开发,头一件事就是要弄明白它的对象模型。你想想看,HSM 里存的无非就是各种“东西”——密钥、证书、敏感数据。PKCS#11 把这堆东西抽象成了统一的对象体系。我刚开始学的时候也觉得这模型有点绕,但用久了就会发现,这套设计确实很巧妙。
所有对象都被分为两大类:存储对象 和 密钥对象。存储对象是那些可以被读出来的东西,密钥对象则是专门干密码活儿的。每个对象都有一堆属性,就像人的身份证信息一样,用属性来描述这个对象是什么、能干什么。
对象的通用属性
每个 PKCS#11 对象,不管是什么类型,都有一组公共属性。就像每个人都有姓名和身份证号一样。我列几个你一定会打交道的:
| 属性名称 | 作用 | 值的类型 |
|---|---|---|
| CKA_CLASS | 对象的大类 | CK_OBJECT_CLASS 枚举 |
| CKA_ | 是令牌对象还是会话对象 | CK_BBOOL (TRUE/FALSE) |
| CKA_PRIVATE | 是否需要登录才能访问 | CK_BBOOL |
| CKA_MODIFIABLE | 创建后能不能改属性 | CK_BBOOL |
| CKA_LABEL | 给对象取个名字 | UTF-8 字符串 |
| CKA_ID | 关联密钥和证书的标识 | 字节数组 |
重点提醒:CKA_ 决定对象的生命周期。TRUE 表示永久保存在 HSM 里,会话结束也不丢。FALSE 表示会话级临时对象,一关会话就没了。我见过有人把临时密钥当成永久的来用,结果重启后全丢了——教训啊。
数据对象——最底层的存储单元
数据对象,说白了就是个“盒子”,里面装的就是一段纯字节流。它没有密码学属性,不能用来签名、加密。它唯一能干的事就是存和取。
它的核心属性就两个:
- CKA_VALUE:真正存的数据内容
- CKA_APPLICATION:给数据打个标签,说明是哪个应用存的
我见过不少实现,把私钥也当成普通数据对象存储——这是很危险的做法。数据对象没有密码学保护,直接能被读出来。私钥必须用密钥对象来存,HSM 硬件会保证私钥永远不暴露在芯片外部。
避坑: 不要用 CKA_CLASS 为 CKO_DATA 的对象来存密钥材料。虽然技术上能存,但安全级别完全不同。我曾经在客户现场看到一套遗留系统,用数据对象存 RSA 私钥,然后调 C_SignInit 的时候发现不行——数据对象没有密钥操作的接口。
密钥对象——密码操作的核心
这才是 PKCS#11 的灵魂。密钥对象分成三种:公钥、私钥、秘密密钥。每种都有自己的专属属性和操作。
公钥对象 (CKO_PUBLIC_KEY)
公钥是可以公开的,所以它的 CKA_PRIVATE 属性通常是 FALSE。公钥对象包含算法类型、密钥材料(比如 RSA 的模数 N 和公钥指数 E),还有一组 CKA_VERIFY、CKA_ENCRYPT、CKA_WRAP 这样的属性——这些表示这把公钥能用来干什么。
私钥对象 (CKO_PRIVATE_KEY)
私钥是机密。它支持的操作是签名、解密、拆封。私钥对象有一个特殊属性 CKA_SENSITIVE,如果设置为 TRUE,那么 C_GetAttributeValue 是拿不到 CKA_VALUE 的。硬件压根就不会把私钥材料吐出来。
秘密密钥对象 (CKO_SECRET_KEY)
秘密密钥就是对称密钥——AES、DES、HMAC 这类。它的核心属性是 CKA_KEY_TYPE、CKA_VALUE_LEN、CKA_VALUE(如果 CKA_SENSITIVE=TRUE 就不可读)。秘密密钥能干的事很多:加密、解密、签名(HMAC 模式)、验证、派生其他密钥。
个人习惯: 每次创建密钥对象时,我必设三个属性:CKA_=TRUE、CKA_PRIVATE=TRUE、CKA_SENSITIVE=TRUE。这样才能保证密钥持久化、登录保护、材料不透传。三件套齐了才靠谱。
证书对象——绑定身份
证书对象用来存 X.509 数字证书。它本质上是只读的——你创建完证书对象后,基本不会去改它。
重要的属性:
- CKA_VALUE:完整的 DER 编码证书
- CKA_CERTIFICATE_TYPE:一般是 CKC_X_509
- CKA_TRUSTED:是否为受信任的根证书
- CKA_CERTIFICATE_CATEGORY:分类(用户/CA/其他)
很多 HSM 厂商会把证书对象跟密钥对象做关联——通过 CKA_ID 属性,把同属一个实体的证书和私钥配成一对。你想想看,签名验证时,用私钥签名,用证书里的公钥验证,CKA_ID 一匹配,非常方便。
对象句柄的生命周期
PKCS#11 里你操作对象不是直接拿属性访问,而是通过 对象句柄(CK_OBJECT_HANDLE)。这个句柄是一个数字 ID,你在会话里用它来操作对象。
我建议注意几点:
- C_FindObjects 才能拿到句柄——你不能自己猜一个数字当成句柄。
- 会话对象只有当前会话能用——换一个会话,句柄就失效了。
- 令牌对象句柄全局有效——只要令牌不拔,任何会话都能用这个句柄。
- 句柄本身没有安全含义——它就是个引用,真正的安全由 HSM 内核保障。
我曾经翻过一个大跟头: 在一款国产 HSM 上创建了一个令牌级 AES 密钥,然后没关闭会话就直接拔 了。重新插回去,老句柄还能用?结果 C_EncryptInit 返回 CKR_DEVICE_ERROR。后来才明白,某些厂商的句柄缓存在会话里,拔 后要重新查找。所以我的经验是——每次建立新会话,都老老实实重新查找对象,别偷懒用老的句柄。
好了,PKCS#11 的对象模型就这些核心东西。记住三个要点:第一,所有对象都是属性驱动的;第二,密钥对象分公钥、私钥、秘密密钥三种,各有各的用途;第三,证书对象本质上是只读容器。搞清楚了这三层,后面的签名、加密操作才有根基。