欢迎光临
我们一直在努力

HSM技术精讲(3.2):核心对象模型——Slot、Token、Session、Object的“四重奏“

3.2 核心对象模型:Slot、Token、Session、Object的"四重奏"

📚 本文内容摘自本人的开源书《HSM技术书 – 从思想实验到安全基石》

🔗 在线阅读/下载:hsm-book

git clone https://github.com/Lularible/hsm-book.git

⭐ 如果对您有帮助,欢迎 Star 支持,也欢迎通过 GitHub Issues 交流讨论。

银行保险箱系统的"四重奏"

让我们继续用银行保险箱的比喻。

一个完整的银行保险箱系统,有四个关键概念:

  • 服务窗口:客户与保险箱系统交互的入口。窗口可以有多个,每个窗口可以服务不同的保险箱库。
  • 保险箱库:存储客户物品的物理空间。一个窗口对应一个库。
  • 访问记录:客户每次访问库时,建立一条记录。记录包含访问时间、访问权限、操作类型。
  • 保险箱内容:库里存储的具体物品(文件、珠宝、证书等)。
  • 这四个概念,对应了PKCS#11的核心对象:

    银行概念PKCS#11概念含义
    服务窗口 Slot HSM的物理插槽/逻辑接口
    保险箱库 Token HSM的逻辑实例/密钥存储区
    访问记录 Session 与Token的交互会话
    保险箱内容 Object 密钥、证书等密码对象

    Slot:密码世界的"服务窗口"

    Slot是PKCS#11的第一个概念。

    Slot的定义:

    Slot(插槽)是一个逻辑概念,代表一个密码令牌可以插入的位置。

    Slot可以是:

    • 物理Slot:HSM硬件的实际插槽(如智能卡读卡器的插槽)
    • 逻辑Slot:HSM软件模拟的逻辑接口(如SoftHSM的虚拟Slot)

    Slot的特点:

  • Slot有编号:Slot ID,通常是0、1、2…的整数
  • Slot可以包含Token:一个Slot可以插入一个Token(也可以没有Token)
  • Slot有状态:Token存在或Token不存在
  • Slot有信息:厂商信息、Slot描述、硬件版本等
  • Slot的结构:

    Slot信息结构(CK_SLOT_INFO):

    typedef struct CK_SLOT_INFO {
    CK_UTF8CHAR manufacturerID[32]; // 厂商ID
    CK_UTF8CHAR slotDescription[64]; // Slot描述
    CK_FLAGS flags; // Slot标志
    CK_VERSION hardwareVersion; // 硬件版本
    CK_VERSION firmwareVersion; // 固件版本
    } CK_SLOT_INFO;

    flags可能的值:
    – CKF_TOKEN_PRESENT:Token存在
    – CKF_REMOVABLE_DEVICE:可移除设备
    – CKF_HW_SLOT:硬件Slot

    Slot的典型场景:

    Slot的典型场景:

    场景一:独立HSM(车规级安全芯片)

    Slot 0 → Token存在(独立HSM)
    Slot 1 → Token不存在
    Slot 2 → Token不存在

    场景二:智能卡读卡器

    Slot 0 → Token存在(插入的智能卡)
    Slot 1 → Token不存在(空插槽)

    场景三:软件HSM(SoftHSM2)

    Slot 0 → Token存在(虚拟Token 0)
    Slot 1 → Token存在(虚拟Token 1)
    Slot 2 → Token不存在

    获取Slot列表的代码:

    CK_RV rv;
    CK_SLOT_ID_PTR pSlotList;
    CK_ULONG ulCount;

    // 获取Slot数量
    rv = C_GetSlotList(FALSE, NULL_PTR, &ulCount);
    if (rv != CKR_OK) {
    printf("获取Slot数量失败:%08x\\n", rv);
    return;
    }

    // 分配内存
    pSlotList = (CK_SLOT_ID_PTR)malloc(ulCount * sizeof(CK_SLOT_ID));

    // 获取Slot列表
    rv = C_GetSlotList(FALSE, pSlotList, &ulCount);
    if (rv != CKR_OK) {
    printf("获取Slot列表失败:%08x\\n", rv);
    free(pSlotList);
    return;
    }

    // 打印Slot信息
    for (CK_ULONG i = 0; i < ulCount; i++) {
    CK_SLOT_INFO slotInfo;
    rv = C_GetSlotInfo(pSlotList[i], &slotInfo);
    printf("Slot %lu: %s\\n", pSlotList[i], slotInfo.slotDescription);
    }

    free(pSlotList);


    Token:密码世界的"保险箱库"

    Token是PKCS#11的第二个概念。

    Token的定义:

    Token(令牌)是存储密码对象的逻辑实体。

    Token代表:

    • 一个HSM实例(如车规级独立安全芯片)
    • 一个智能卡(如插入读卡器的卡片)
    • 一个虚拟Token(如SoftHSM2的虚拟Token)

    Token的特点:

  • Token存储密码对象:密钥、证书、数据对象都存储在Token中
  • Token有访问控制:需要PIN码登录才能访问私有对象
  • Token有状态:初始化状态、正常状态、锁定状态等
  • Token有信息:厂商信息、Token描述、序列号等
  • Token的结构:

    Token信息结构(CK_TOKEN_INFO):

    typedef struct CK_TOKEN_INFO {
    CK_UTF8CHAR label[32]; // Token标签
    CK_UTF8CHAR manufacturerID[32]; // 厂商ID
    CK_UTF8CHAR model[16]; // 型号
    CK_UTF8CHAR serialNumber[16]; // 序列号
    CK_FLAGS flags; // Token标志
    CK_ULONG ulMaxSessionCount; // 最大会话数
    CK_ULONG ulSessionCount; // 当前会话数
    CK_ULONG ulMaxRwSessionCount; // 最大读写会话数
    CK_ULONG ulRwSessionCount; // 当前读写会话数
    CK_ULONG ulMaxPinLen; // 最大PIN长度
    CK_ULONG ulMinPinLen; // 最小PIN长度
    CK_ULONG ulTotalPublicMemory; // 公共内存总量
    CK_ULONG ulFreePublicMemory; // 公共内存空闲量
    CK_ULONG ulTotalPrivateMemory; // 私有内存总量
    CK_ULONG ulFreePrivateMemory; // 私有内存空闲量
    CK_VERSION hardwareVersion; // 硬件版本
    CK_VERSION firmwareVersion; // 固件版本
    CK_CHAR utcTime[16]; // UTC时间(可选)
    } CK_TOKEN_INFO;

    flags可能的值:
    – CKF_RNG:有随机数生成器
    – CKF_WRITE_PROTECTED:写保护
    – CKF_LOGIN_REQUIRED:需要登录
    – CKF_USER_PIN_INITIALIZED:用户PIN已初始化
    – CKF_TOKEN_INITIALIZED:Token已初始化

    Token的用户角色:

    Token有两个用户角色:

    角色描述权限
    Security Officer (SO) 安全管理员 初始化Token、设置用户PIN、解锁Token
    User 普通用户 使用密钥、执行密码操作

    Token的初始化流程:

    Token初始化流程:

    1. 获取Slot列表
    C_GetSlotList()

    2. 选择一个Slot
    slotID = pSlotList[0]

    3. 初始化Token(需要SO PIN)
    C_InitToken(slotID, soPin, soPinLen, label)

    4. 打开会话
    C_OpenSession(slotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, …)

    5. 登录为SO
    C_Login(hSession, CKU_SO, soPin, soPinLen)

    6. 初始化用户PIN
    C_InitPIN(hSession, userPin, userPinLen)

    7. 注销
    C_Logout(hSession)

    8. 关闭会话
    C_CloseSession(hSession)


    Session:密码世界的"访问记录"

    Session是PKCS#11的第三个概念。

    Session的定义:

    Session(会话)是应用程序与Token的一次交互过程。

    每次应用程序与Token交互时,需要:

  • 打开一个Session
  • 在Session中执行操作
  • 关闭Session
  • Session的特点:

  • Session绑定Token:Session与特定的Token绑定
  • Session有状态:读/写、登录/未登录
  • Session有权限:基于登录状态决定可执行的操作
  • Session可以并发:一个Token可以有多个Session
  • Session的状态:

    Session状态:

    ┌─────────────────────────────────────────────────────────┐
    │ │
    │ ┌─────────────────────────────────────────────┐ │
    │ │读写会话 │ │
    │ │ │ │
    │ │ ┌─────────────────┐ ┌─────────────────┐ │ │
    │ │ │R/W Public │ │R/W User │ │ │
    │ │ │Session │ │Functions │ │ │
    │ │ │(未登录) │ │(登录为User) │ │ │
    │ │ └─────────────────┘ └─────────────────┘ │ │
    │ │ │ │
    │ │ ┌─────────────────┐ ┌─────────────────┐ │ │
    │ │ │R/W SO │ │R/W SO │ │ │
    │ │ │Functions │ │Functions │ │ │
    │ │ │(登录为SO) │ │(登录为SO) │ │ │
    │ │ └─────────────────┘ └─────────────────┘ │ │
    │ │ │ │
    │ └─────────────────────────────────────────────┘ │
    │ │
    │ ┌─────────────────────────────────────────────┐ │
    │ │只读会话 │ │
    │ │ │ │
    │ │ ┌─────────────────┐ ┌─────────────────┐ │ │
    │ │ │R/O Public │ │R/O User │ │ │
    │ │ │Session │ │Functions │ │ │
    │ │ │(未登录) │ │(登录为User) │ │ │
    │ │ └─────────────────┘ └─────────────────┘ │ │
    │ │ │ │
    │ └─────────────────────────────────────────────┘ │
    │ │
    └─────────────────────────────────────────────────────────┘

    Session的打开方式:

    CK_SESSION_HANDLE hSession;
    CK_FLAGS flags = CKF_SERIAL_SESSION | CKF_RW_SESSION;

    // 打开读写会话
    rv = C_OpenSession(slotID, flags, NULL_PTR, NULL_PTR, &hSession);

    // 打开只读会话
    flags = CKF_SERIAL_SESSION;
    rv = C_OpenSession(slotID, flags, NULL_PTR, NULL_PTR, &hSession);

    Session状态转换:

    Session状态转换:

    未登录

    │ C_Login(CKU_USER, pin, pinLen)

    已登录User

    │ C_Login(CKU_SO, soPin, soPinLen) ← 只有R/W会话可以

    已登录SO

    │ C_Logout()

    未登录


    Object:密码世界的"保险箱内容"

    Object是PKCS#11的第四个概念。

    Object的定义:

    Object(对象)是存储在Token中的密码资源。

    Object可以是:

    • 密钥:公钥、私钥、对称密钥
    • 证书:X.509证书
    • 数据:用户数据对象

    Object的类型:

    Object类型常量描述
    CKO_PUBLIC_KEY 公钥对象 RSA/ECC公钥
    CKO_PRIVATE_KEY 私钥对象 RSA/ECC私钥
    CKO_SECRET_KEY 对称密钥对象 AES/DES密钥
    CKO_CERTIFICATE 证书对象 X.509证书
    CKO_DATA 数据对象 用户数据
    CKO_MECHANISM 机制对象 密码机制描述

    Object的属性:

    每个Object有一组属性。属性定义了Object的特征和行为。

    常见的属性:

    属性常量描述
    CKA_CLASS Object类型 公钥/私钥/对称密钥等
    CKA_KEY_TYPE 密钥类型 RSA/ECC/AES等
    CKA_LABEL 标签 用户定义的标签
    CKA_ID ID 用户定义的ID
    CKA_TOKEN 是否存储在Token中 TRUE=持久存储
    CKA_PRIVATE 是否私有 TRUE=需要登录才能访问
    CKA_SENSITIVE 是否敏感 TRUE=不可导出
    CKA_EXTRACTABLE 是否可导出 FALSE=不能导出
    CKA_MODIFIABLE 是否可修改 TRUE=属性可修改
    CKA_DESTROYABLE 是否可销毁 TRUE=可以销毁
    CKA_VALUE 密钥值 密钥的实际值
    CKA_MODULUS RSA模数 RSA密钥的模数
    CKA_PUBLIC_EXPONENT RSA公钥指数 RSA公钥的指数
    CKA_PRIVATE_EXPONENT RSA私钥指数 RSA私钥的指数

    Object的安全属性组合:

    这些属性的组合决定了Object的安全行为:

    Object安全属性组合示例:

    组合一:不可导出的私钥(最安全)
    – CKA_SENSITIVE = TRUE(敏感,不可导出)
    – CKA_EXTRACTABLE = FALSE(不可导出)
    – CKA_PRIVATE = TRUE(私有,需要登录)

    组合二:可导出的对称密钥(可备份)
    – CKA_SENSITIVE = FALSE(不敏感)
    – CKA_EXTRACTABLE = TRUE(可导出)
    – CKA_PRIVATE = TRUE(私有)

    组合三:公开的公钥(可自由访问)
    – CKA_SENSITIVE = FALSE(不敏感)
    – CKA_EXTRACTABLE = TRUE(可导出)
    – CKA_PRIVATE = FALSE(公开)

    创建Object的代码:

    CK_OBJECT_HANDLE hKey;
    CK_ATTRIBUTE template[] = {
    {CKA_CLASS, &keyClass, sizeof(keyClass)},
    {CKA_KEY_TYPE, &keyType, sizeof(keyType)},
    {CKA_TOKEN, &bTrue, sizeof(bTrue)},
    {CKA_PRIVATE, &bTrue, sizeof(bTrue)},
    {CKA_SENSITIVE, &bTrue, sizeof(bTrue)},
    {CKA_EXTRACTABLE, &bFalse, sizeof(bFalse)},
    {CKA_LABEL, "MyKey", 5},
    };

    rv = C_CreateObject(hSession, template, 6, &hKey);


    四重奏的完整关系

    现在让我们把这四个概念组合起来。

    完整的层次结构:

    PKCS#11对象模型层次结构:

    Library(PKCS#11库)

    ├── Slot 0
    │ ├── Token(存在)
    │ │ ├── Session 0
    │ │ │ ├── Object 0(公钥)
    │ │ │ ├── Object 1(私钥)
    │ │ │ ├── Object 2(对称密钥)
    │ │ │ └── Object 3(证书)
    │ │ └── Session 1
    │ │ ├── Object 4(数据)
    │ │ └── Object 5(证书)
    │ └── Token(不存在)

    ├── Slot 1
    │ └── Token(存在)
    │ └── Session 2
    │ ├── Object 0
    │ └── Object 1

    └── Slot 2
    └── Token(不存在)

    关系的要点:

  • Slot与Token:Slot是位置,Token是内容。一个Slot可以有一个Token,也可以没有。
  • Token与Session:Session是Token的交互。一个Token可以有多个Session。
  • Session与Object:Object通过Session访问。Session决定了Object的访问权限。
  • Object与Token:Object存储在Token中。Token是Object的容器。
  • 访问流程:

    访问Object的完整流程:

    应用程序

    │ C_Initialize()

    Library初始化

    │ C_GetSlotList()

    获取Slot列表 → 选择Slot 0

    │ C_OpenSession(slotID, …)

    打开Session → Session绑定到Slot 0的Token

    │ C_Login(hSession, CKU_USER, …)

    登录 → Session变为已登录状态

    │ C_FindObjectsInit(hSession, …)
    │ C_FindObjects(hSession, …)
    │ C_FindObjectsFinal(hSession)

    查找Object → 找到Object(私钥)

    │ C_SignInit(hSession, mechanism, hKey)
    │ C_Sign(hSession, …)

    使用Object → 执行签名操作

    │ C_Logout(hSession)
    │ C_CloseSession(hSession)
    │ C_Finalize()

    清理 → 结束会话


    一个类比:完整的银行流程

    让我用一个完整的银行类比来理解这个四重奏。

    银行保险箱系统流程:

    银行保险箱系统流程:

    1. 客户进入银行大厅
    = 应用程序初始化 C_Initialize()

    2. 客户查看窗口列表
    = 获取Slot列表 C_GetSlotList()

    3. 客户选择一个窗口
    = 选择Slot

    4. 客户通过窗口请求访问保险箱库
    = 打开Session C_OpenSession()

    5. 客户出示身份证明,工作人员核验
    = 登录 C_Login()

    6. 工作人员带客户进入库房
    = Session绑定到Token

    7. 客户查找自己的保险箱
    = 查找Object C_FindObjects()

    8. 客户打开保险箱,取出/存入物品
    = 使用Object C_Sign/C_Encrypt等

    9. 客户离开库房,工作人员注销访问记录
    = 注销 C_Logout()

    10. 客户离开窗口
    = 关闭Session C_CloseSession()

    11. 客户离开银行大厅
    = 清理 C_Finalize()

    这个类比清晰地对应了PKCS#11的每个概念和流程。


    实际代码示例:完整的签名流程

    现在,让我们看一个完整的PKCS#11签名流程代码:

    CK_RV rv;
    CK_C_INITIALIZE_ARGS initArgs = {NULL_PTR, NULL_PTR, NULL_PTR, NULL_PTR, CKF_OS_LOCKING_OK, NULL_PTR};
    CK_SLOT_ID slotID;
    CK_SLOT_ID_PTR pSlotList;
    CK_ULONG ulSlotCount;
    CK_SESSION_HANDLE hSession;
    CK_OBJECT_HANDLE hKey;
    CK_BYTE data[] = "Hello, PKCS#11!";
    CK_ULONG ulDataLen = sizeof(data);
    CK_BYTE signature[256];
    CK_ULONG ulSigLen = 256;
    CK_CHAR userPin[] = "12345678";
    CK_ULONG ulUserPinLen = sizeof(userPin);

    // 1. 初始化
    rv = C_Initialize(&initArgs);
    if (rv != CKR_OK) {
    printf("初始化失败:0x%08x\\n", rv);
    return rv;
    }

    // 2. 获取Slot列表
    rv = C_GetSlotList(CK_TRUE, NULL_PTR, &ulSlotCount);
    if (rv != CKR_OK) {
    C_Finalize(NULL_PTR);
    return rv;
    }

    pSlotList = malloc(ulSlotCount * sizeof(CK_SLOT_ID));
    rv = C_GetSlotList(CK_TRUE, pSlotList, &ulSlotCount);
    if (rv != CKR_OK) {
    free(pSlotList);
    C_Finalize(NULL_PTR);
    return rv;
    }

    slotID = pSlotList[0]; // 选择第一个Slot
    free(pSlotList);

    // 3. 打开Session
    rv = C_OpenSession(slotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSession);
    if (rv != CKR_OK) {
    C_Finalize(NULL_PTR);
    return rv;
    }

    // 4. 登录
    rv = C_Login(hSession, CKU_USER, userPin, ulUserPinLen);
    if (rv != CKR_OK) {
    C_CloseSession(hSession);
    C_Finalize(NULL_PTR);
    return rv;
    }

    // 5. 查找私钥
    CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
    CK_KEY_TYPE keyType = CKK_RSA;
    CK_ATTRIBUTE findTemplate[] = {
    {CKA_CLASS, &keyClass, sizeof(keyClass)},
    {CKA_KEY_TYPE, &keyType, sizeof(keyType)},
    };

    rv = C_FindObjectsInit(hSession, findTemplate, 2);
    if (rv != CKR_OK) {
    C_Logout(hSession);
    C_CloseSession(hSession);
    C_Finalize(NULL_PTR);
    return rv;
    }

    CK_ULONG ulCount;
    rv = C_FindObjects(hSession, &hKey, 1, &ulCount);
    if (rv != CKR_OK || ulCount == 0) {
    C_FindObjectsFinal(hSession);
    C_Logout(hSession);
    C_CloseSession(hSession);
    C_Finalize(NULL_PTR);
    return rv;
    }

    rv = C_FindObjectsFinal(hSession);
    if (rv != CKR_OK) {
    C_Logout(hSession);
    C_CloseSession(hSession);
    C_Finalize(NULL_PTR);
    return rv;
    }

    // 6. 签名
    CK_MECHANISM mechanism = {CKM_RSA_PKCS, NULL_PTR, 0};
    rv = C_SignInit(hSession, &mechanism, hKey);
    if (rv != CKR_OK) {
    C_Logout(hSession);
    C_CloseSession(hSession);
    C_Finalize(NULL_PTR);
    return rv;
    }

    rv = C_Sign(hSession, data, ulDataLen, signature, &ulSigLen);
    if (rv != CKR_OK) {
    C_Logout(hSession);
    C_CloseSession(hSession);
    C_Finalize(NULL_PTR);
    return rv;
    }

    printf("签名成功,长度:%lu字节\\n", ulSigLen);

    // 7. 注销
    rv = C_Logout(hSession);
    if (rv != CKR_OK) {
    C_CloseSession(hSession);
    C_Finalize(NULL_PTR);
    return rv;
    }

    // 8. 关闭Session
    rv = C_CloseSession(hSession);
    if (rv != CKR_OK) {
    C_Finalize(NULL_PTR);
    return rv;
    }

    // 9. 清理
    rv = C_Finalize(NULL_PTR);

    return rv;

    这个代码完整展示了PKCS#11的使用流程:

    • 初始化 → 获取Slot → 打开Session → 登录 → 查找密钥 → 签名 → 注销 → 关闭Session → 清理

    本篇小结

    今天我们分析了PKCS#11的核心对象模型——四重奏。

    Slot(服务窗口):

    • 密码令牌的插入位置
    • 可以包含Token,也可以不包含
    • 有编号、状态、信息

    Token(保险箱库):

    • 存储密码对象的逻辑实体
    • 有访问控制(SO和User)
    • 有状态(初始化、正常、锁定)

    Session(访问记录):

    • 与Token的交互会话
    • 有状态(读/写、登录/未登录)
    • 有权限(基于登录状态)

    Object(保险箱内容):

    • 存储在Token中的密码资源
    • 有类型(公钥、私钥、对称密钥、证书、数据)
    • 有属性(安全属性定义行为)

    这四个概念形成完整的层次结构:Library → Slot → Token → Session → Object。

    下一节,我们将深入PKCS#11的安全哲学——属性如何定义安全边界,密钥如何被保护。

    【下集预告】

    PKCS#11的安全哲学是什么?

    “提供机制而非策略”——这是PKCS#11的核心设计原则。

    但机制如何转化为安全?属性如何定义边界?

    CKA_SENSITIVE、CKA_EXTRACTABLE、CKA_PRIVATE这些属性如何工作?

    下一节,安全哲学。

    赞(0)
    未经允许不得转载:171主机测评 » HSM技术精讲(3.2):核心对象模型——Slot、Token、Session、Object的“四重奏“
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址