5.3 安全存储与密钥管理:内存模拟设计
hsm-lite的存储方式
hsm-lite使用内存数组存储密钥,不涉及文件系统:
hsm-lite存储设计:
存储方式:
├── 内存数组存储(静态分配)
├── 不持久化(Session关闭后消失)
├── 固定容量限制(HSM_MAX_OBJECTS = 16)
└── 无文件系统依赖
简化特点:
├── 教学目的,展示概念
├── 单文件实现
├── 无安全属性检查(CKA_SENSITIVE等)
└── 密钥值可直接读取
密钥数据结构
实际hsm-lite中的密钥结构:
/* AES密钥对象(实际源码) */
typedef struct {
CK_OBJECT_HANDLE handle; // 对象句柄(1, 2, 3...)
CK_BYTE key[32]; // AES-256密钥值
CK_ULONG key_len; // 密钥长度
CK_BBOOL in_use; // 是否正在使用
} hsm_key_t;
/* 全局密钥池 */
static struct {
...
hsm_key_t keys[HSM_MAX_OBJECTS]; // 密钥数组(16个)
CK_ULONG key_count; // 当前密钥数量
CK_OBJECT_HANDLE next_key_handle; // 下一个句柄
} g_ctx;
简化之处:
与真实HSM对比:
真实HSM:
├── 密钥加密存储
├── CKA_SENSITIVE控制读取
├── Token持久化
├── 复杂属性管理
└── 多种密钥类型
hsm-lite(简化):
├── 密钥明文存储
├── 无属性控制
├── Session关闭后消失
├── 只支持AES密钥
└── 固定密钥长度
密钥查找机制
hsm-lite使用简单的数组遍历查找:
static hsm_key_t *find_key(CK_OBJECT_HANDLE hKey)
{
// 遍历密钥数组
for (CK_ULONG i = 0; i < HSM_MAX_OBJECTS; i++) {
// 检查是否在使用且句柄匹配
if (g_ctx.keys[i].in_use &&
g_ctx.keys[i].handle == hKey) {
return &g_ctx.keys[i]; // 找到
}
}
return NULL; // 未找到
}
查找效率:
查找效率分析:
hsm-lite:
├── O(n)线性查找
├── n = HSM_MAX_OBJECTS (16)
├── 教学目的,简单直观
真实HSM:
├── Hash表或Tree查找
├── O(1)或O(log n)
├── 性能优化
└── 大量密钥场景
Handle分配机制
句柄从1开始递增分配:
CK_RV C_GenerateKey(...)
{
...
for (CK_ULONG i = 0; i < HSM_MAX_OBJECTS; i++) {
if (!g_ctx.keys[i].in_use) {
// 找到空闲槽位
g_ctx.keys[i].in_use = CK_TRUE;
g_ctx.keys[i].handle = g_ctx.next_key_handle++; // 分配句柄
g_ctx.keys[i].key_len = HSM_AES_KEY_SIZE;
// 生成密钥值
get_random_bytes(g_ctx.keys[i].key, HSM_AES_KEY_SIZE);
g_ctx.key_count++;
*phKey = g_ctx.keys[i].handle;
return CKR_OK;
}
}
return CKR_GENERAL_ERROR; // 无空闲槽位
}
Handle分配规则:
Handle分配规则:
1. 从1开始(0保留为无效)
2. 递增分配(不回收)
3. 数组索引与Handle无关
4. 查找时遍历匹配
示例:
├── 第一次生成:handle = 1, 记录在keys[0]
├── 第二次生成:handle = 2, 存在keys[3]
├── 销毁handle=1后,keys[0].in_use = FALSE
├── 第三次生成:handle = 3(不是复用1)
└── 不回收Handle的原因:简化实现
Object管理
C_CreateObject:
CK_RV C_CreateObject(CK_SESSION_HANDLE hSession,
CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount,
CK_OBJECT_HANDLE_PTR phObject)
{
// 解析属性模板
CK_OBJECT_CLASS class = CKO_SECRET_KEY;
CK_KEY_TYPE key_type = CKK_AES;
CK_ULONG value_len = HSM_AES_KEY_SIZE;
CK_BYTE_PTR value = NULL;
for (CK_ULONG i = 0; i < ulCount; i++) {
switch (pTemplate[i].type) {
case CKA_CLASS:
class = *((CK_OBJECT_CLASS *)pTemplate[i].pValue);
break;
case CKA_KEY_TYPE:
key_type = *((CK_KEY_TYPE *)pTemplate[i].pValue);
break;
case CKA_VALUE:
value = (CK_BYTE_PTR)pTemplate[i].pValue;
value_len = pTemplate[i].ulValueLen;
break;
}
}
// 验证对象类型(只支持AES密钥)
if (class != CKO_SECRET_KEY || key_type != CKK_AES) {
return CKR_ATTRIBUTE_VALUE_INVALID;
}
// 创建密钥对象
// ...查找空闲槽位,复制密钥值
return CKR_OK;
}
C_DestroyObject:
CK_RV C_DestroyObject(CK_SESSION_HANDLE hSession,
CK_OBJECT_HANDLE hObject)
{
// 查找密钥
hsm_key_t *key = find_key(hObject);
if (!key) {
return CKR_OBJECT_HANDLE_INVALID;
}
// 清零密钥值(安全清理)
memset(key->key, 0, HSM_AES_KEY_SIZE);
// 标记为未使用
key->in_use = CK_FALSE;
g_ctx.key_count--;
printf("[hsm-lite] DestroyObject: handle=%lu\n", hObject);
return CKR_OK;
}
属性读取
C_GetAttributeValue:
CK_RV C_GetAttributeValue(CK_SESSION_HANDLE hSession,
CK_OBJECT_HANDLE hObject,
CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount)
{
if (!g_ctx.initialized) {
return CKR_FUNCTION_NOT_INITIALIZED;
}
hsm_session_t *sess = find_session(hSession);
if (!sess) {
return CKR_SESSION_HANDLE_INVALID;
}
hsm_key_t *key = find_key(hObject);
if (!key) {
return CKR_OBJECT_HANDLE_INVALID;
}
// 填充请求的属性
for (CK_ULONG i = 0; i < ulCount; i++) {
switch (pTemplate[i].type) {
case CKA_CLASS:
*((CK_OBJECT_CLASS *)pTemplate[i].pValue) = CKO_SECRET_KEY;
pTemplate[i].ulValueLen = sizeof(CK_OBJECT_CLASS);
break;
case CKA_KEY_TYPE:
*((CK_KEY_TYPE *)pTemplate[i].pValue) = CKK_AES;
pTemplate[i].ulValueLen = sizeof(CK_KEY_TYPE);
break;
case CKA_VALUE_LEN:
*((CK_ULONG *)pTemplate[i].pValue) = key->key_len;
pTemplate[i].ulValueLen = sizeof(CK_ULONG);
break;
case CKA_VALUE:
// 注意:hsm-lite允许读取密钥值(简化)
if (pTemplate[i].pValue) {
memcpy(pTemplate[i].pValue, key->key, key->key_len);
pTemplate[i].ulValueLen = key->key_len;
} else {
pTemplate[i].ulValueLen = key->key_len; // 只返回长度
}
break;
default:
// 不支持的属性
pTemplate[i].ulValueLen = CK_UNAVAILABLE_INFORMATION;
break;
}
}
return CKR_OK;
}
两步读取模式:
PKCS#11属性读取模式:
第一步:获取长度
├── pValue = NULL
├── 返回ulValueLen = 实际长度
└── 应用程序分配内存
第二步:获取值
├── pValue = 分配的缓冲区
├── 返回属性值
└── ulValueLen = 实际长度
示例:
CK_ATTRIBUTE template = {CKA_VALUE, NULL, 0};
C_GetAttributeValue(hSession, hKey, &template, 1); // 获取长度
CK_BYTE *value = malloc(template.ulValueLen);
template.pValue = value;
C_GetAttributeValue(hSession, hKey, &template, 1); // 获取值
安全属性简化
hsm-lite不支持真实的安全属性:
安全属性对比:
真实PKCS#11:
├── CKA_SENSITIVE:密钥值不可读取
├── CKA_EXTRACTABLE:密钥不可导出
├── CKA_ALWAYS_SENSITIVE:始终敏感
├── CKA_NEVER_EXTRACTABLE:从不可导出
├── 属性不可逆(安全等级不可降低)
└── C_WrapKey加密导出
hsm-lite(简化):
├── 无CKA_SENSITIVE检查
├── 无CKA_EXTRACTABLE检查
├── CKA_VALUE可直接读取
├── 密钥明文存储
└── 教学目的,展示概念
为什么不实现安全属性?
简化设计原因:
教学目的:
├── 展示PKCS#11接口流程
├── 不需要真实安全特性
├── 代码简洁易懂
└── 约615行实现
如需安全属性:
├── 需增加属性检查代码
├── 需实现属性不可逆逻辑
├── 需加密密钥存储
└── 代码量增加200+行
一个类比:教室储物柜
教室储物柜类比:
hsm-lite密钥存储(教室储物柜)
│
├── 存储方式
│ ├── 固定数量(16个格子)
│ ├── 内存存储(柜子在教室里)
│ ├── 无锁(任何人可查看)
│ └── 课后清空(Session关闭)
│
├── Handle(编号)
│ ├── 从1开始编号
│ ├── 递增分配
│ ├── 与格子位置无关
│ └── 查找时遍历匹配
│
├── 简化之处
│ ├── 无锁(真实HSM有加密)
│ ├── 无标签(真实HSM有CKA_LABEL)
│ ├── 无权限(真实HSM有CKA_PRIVATE)
│ └── 无持久化(真实HSM有Token对象)
│
└── 学习价值
├── 理解对象管理概念
├── 理解Handle机制
├── 理解属性读取流程
└── 为真实设计打基础
本篇小结
hsm-lite的存储设计非常简化:
存储方式:
- 内存数组存储
- 固定容量(16个)
- 无持久化
密钥结构:
- hsm_key_t:handle, key[32], key_len, in_use
- keys[]数组存储所有密钥
Handle机制:
- 从1递增分配
- 不回收复用
- 数组遍历查找
Object管理:
- C_CreateObject:解析属性模板创建
- C_DestroyObject:清零密钥值,标记未使用
- C_GetAttributeValue:支持CKA_CLASS/KEY_TYPE/VALUE
安全属性:
- 不支持CKA_SENSITIVE等
- 密钥值可直接读取
- 教学简化设计
简化之处:
- 无加密存储
- 无属性检查
- 无持久化
- 无多种密钥类型
下一节,我们将分析测试程序的设计和运行。
【下集预告】
测试程序如何设计?
四个测试案例详解?
如何编译运行?
多架构支持?
下一节,测试与编译。