Slot、、Session、Object 模型
PKCS 11 的架构,说白了就四个抽象层:Slot、、Session、Object。我第一次接触时也觉得概念有点绕,后来调了个硬件 HSM 才真正理解——这些不是理论玩具,是实打实的接口契约。
我把它们比作「银行柜台系统」:Slot 是柜员窗口, 是柜员手里的印章,Session 是你跟柜员的对话,Object 就是你存的现金或支票。嗯,这样是不是好记多了?
2.1 Slot(槽位)—— 你插卡的那扇门
Slot 是 PKCS 11 对物理或逻辑设备插槽的抽象。每张加密卡、每个 USB 、甚至软件模拟的 ,都对应一个 Slot。
- 槽位号:从 0 开始编号,通过
C_GetSlotList获取当前可用槽位。 - 槽位状态:分为「有 插入」和「无 」两种。我遇到过客户机没插卡就调用
C_GetSlotInfo,结果返回空 信息——这其实是标准行为,但新手容易忽略。 - 多槽位场景:一台机器可挂多个加密设备,每个设备对应一个独立 Slot。
2.2 (令牌)—— 真正干活的硬件
是插在 Slot 里的实际设备。它负责存储密钥、执行加解密,是个带 CPU 和存储的安全芯片。说白了, 是你整个安全体系的根基。
| 属性 | 说明 | 我碰到的坑 |
|---|---|---|
| 标签(Label) | 用户可设置的名称 | 不同 标签重复,导致应用选错 |
| 序列号 | 出厂固化,唯一标识 | 用序列号区分设备最靠谱 |
| 制造商 ID | 设备厂商标识 | 兼容性测试时很有用 |
| 总存储空间 | 可用的 Flash/EEPROM | 密钥写满时不报错,返回 CKR_DEVICE_MEMORY |
| 可用空间 | 剩余可存储容量 | 建议每次写入前检查 |
有两大登录角色:安全官员(SO) 和 普通用户(User)。SO 负责初始化 、设置用户 PIN;User 做实际密钥操作。我建议你严格分离这两个角色,别偷懒用同一个 PIN。
2.3 Session(会话)—— 你跟 的专属通道
Session 是应用程序与 之间的逻辑连接。每次调用 C_OpenSession 就开了一条通道。它管理着你的操作上下文,比如当前登录状态、正在进行的加解密流程。
- Read-Only 模式:只能查,不能写。适合只做验签的场景。
- Read-Write 模式:可以创建、修改、删除密钥。
- 串行 vs 并行:大部分 只支持串行会话——你做完一个操作,下一个才能开始。并行会话多用于多线程环境下给不同线程分配独立通道。
// 一个典型的 Session 生命周期
CK_SESSION_HANDLE hSession;
CK_RV rv;
// 1. 打开会话
rv = C_OpenSession(slotID, CKF_SERIAL_SESSION | CKF_RW_SESSION,
NULL, NULL, &hSession);
// 2. 登录
rv = C_Login(hSession, CKU_USER, pin, pinLen);
// 3. 干活(加解密、签名、密钥生成...)
rv = C_SignInit(hSession, &mechanism, hPrivateKey);
rv = C_Sign(hSession, data, dataLen, signature, &sigLen);
// 4. 登出
rv = C_Logout(hSession);
// 5. 关闭会话
rv = C_CloseSession(hSession);
C_OpenSession 后忘记 C_CloseSession。 的并发会话数有限(看到过最多 10 个的),开满后新会话直接返回 CKR_SESSION_COUNT。
2.4 Object(对象)—— 你真正关心的资产
Object 是 上存储的数据单元。PKCS 11 把所有东西都看成对象——密钥、证书、数据、甚至公钥模板。我习惯把 Object 分为三大类:
- 数据对象(CKO_DATA):存公共信息,比如证书、CRL、自定义数据。嗯,这些不是密钥,不涉及加密。
- 密钥对象:这才是核心。包括 CKO_SECRET_KEY(对称密钥)、CKO_PUBLIC_KEY(公钥)、CKO_PRIVATE_KEY(私钥)。
- 域对象(CKO_DOMAIN):定义密钥生成参数,比如 RSA 的模长、ECC 的曲线。
| 对象类型 | 典型用途 | 生命周期 |
|---|---|---|
| CKO_DATA | 存储 CA 证书 | 可导入、可删除 |
| CKO_SECRET_KEY | AES 密钥用于加解密 | 生成后只标识句柄 |
| CKO_PUBLIC_KEY | 验签、加密 | 私钥删除后公钥仍可存在 |
| CKO_PRIVATE_KEY | 签名、解密 | 永远不可导出明文 |
| CKO_DOMAIN | 定义密钥生成参数 | 通常预先存储 |
每个 Object 都有属性(Attributes),通过 C_CreateObject 或 C_GenerateKey 传入模板来定义。比如创建 AES 密钥:
CK_OBJECT_CLASS class = CKO_SECRET_KEY;
CK_KEY_TYPE keyType = CKK_AES;
CK_ULONG valueLen = 16; // 128 位
CK_BBOOL true = CK_TRUE;
CK_ATTRIBUTE template[] = {
{CKA_CLASS, &class, sizeof(class)},
{CKA_KEY_TYPE, &keyType, sizeof(keyType)},
{CKA_VALUE_LEN,&valueLen, sizeof(valueLen)},
{CKA_ENCRYPT, &true, sizeof(true)},
{CKA_DECRYPT, &true, sizeof(true)},
{CKA_, &true, sizeof(true)},
{CKA_PRIVATE, &true, sizeof(true)}
};
CK_OBJECT_HANDLE hKey;
C_GenerateKey(hSession, &mech, template, 7, &hKey);
CKA_ 属性,结果密钥只在 Session 内存中存在,Session 关闭就丢了。那是真金白银的教训——所有持久密钥都该显式指定 CKA_ = CK_TRUE。
2.5 四层模型关系图
我把这四层的关系画成一张图。你从上往下看:应用层通过 Cryptoki 接口访问 Slot,Slot 管理着 , 里运行着 Session,Session 里挂着 Object。简单直接。
2.6 四个概念的关系一表清
最后我整理这张对比表。你写代码时反复对照它,基本不会混淆:
| 概念 | 定义 | 核心作用 | 生命周期 | 代码关键点 |
|---|---|---|---|---|
| Slot | 物理/逻辑插槽 | 发现设备、获取状态 | 应用启动时枚举 | C_GetSlotList |
| 加密令牌设备 | 安全存储、执行密码运算 | 通电即存在 | C_GetTokenInfo |
|
| Session | 操作会话通道 | 管理上下文、登录态 | 打开 → 关闭 | C_OpenSession / C_CloseSession |
| Object | 密钥/证书/数据 | 执行密码学操作 | 创建 → 销毁 | C_CreateObject / C_DestroyObject |