6.5 安全机制实现:PIN与密钥管理
一个场景:你成功连接了HSM,准备导入一个重要的密钥。调用hsm_import_key,却收到错误码“安全条件不满足”。你查看文档,发现需要先“解锁”。解锁是什么?怎么解锁?
一个问题浮出水面:HSM如何保证只有授权用户才能执行敏感操作?PIN验证、传输密钥、密钥管理——这些安全机制是如何协同工作的?
一个隐喻:这像银行的保险箱——你需要出示身份证(PIN验证)、使用专用钥匙(传输密钥)、在银行工作人员监督下才能存取贵重物品。每一层保护都不可或缺。
HSM的安全层级
HSM的安全机制分为多个层级:
安全层级结构
┌─────────────────────────────────────┐
│ 物理安全:防篡改、防探测 │ ← 硬件层
├─────────────────────────────────────┤
│ 访问控制:PIN验证、角色权限 │ ← 认证层
├─────────────────────────────────────┤
│ 数据保护:传输密钥加密 │ ← 传输层
├─────────────────────────────────────┤
│ 密钥管理:生成/导入/导出/删除 │ ← 应用层
└─────────────────────────────────────┘
每一层都有对应的API和操作流程。
PIN验证机制
PIN的角色分类
HSM支持两种PIN角色:
| 角色 | 名称 | 权限范围 |
|---|---|---|
| ADMIN_PIN | 管理员PIN | 文件读写、密钥管理、安全配置 |
| USER_PIN | 用户PIN | 使用密钥进行加密/签名操作 |
管理员PIN权限更高,可以管理HSM的文件系统和密钥库。用户PIN只能使用已有的密钥,不能修改配置。
PIN验证流程
调用hsm_verify_pin进行PIN验证:
typedef struct {
uint8_t owner; // PIN角色:ADMIN_PIN或USER_PIN
uint8_t pin_value[16]; // PIN值(最长16字节)
uint8_t pin_len; // PIN长度
uint8_t limit; // 重试次数限制
} pin_t;
// 示例代码
uint8_t pin_buf[16] = {0x12, 0x34, 0x56, 0x78,
0x9A, 0xBC, 0xDE, 0xF0,
0x12, 0x34, 0x56, 0x78,
0x9A, 0xBC, 0xDE, 0xF0};
pin_t pin = {0};
pin.owner = ADMIN_PIN;
pin.pin_len = 16;
memcpy(pin.pin_value, pin_buf, 16);
ret = hsm_verify_pin(&pin);
if (ret != HSM_OK) {
LOGE("PIN验证失败");
return ret;
}
PIN验证的内部实现
SDK不会直接发送PIN值到HSM,而是采用挑战-响应机制:
PIN验证流程
Host HSM
│ │
│──── 获取随机数请求 ───│
│ │
│←── 返回随机数 ────────│
│ │
│ 计算PIN的哈希值 │
│ 用哈希值加密随机数 │
│ │
│──── 发送加密结果 ─────│
│ │
│←── 验证结果 ──────────│
具体实现:
se_error_t apdu_verify_pin(pin_t *pin) {
// 步骤1:获取随机数
uint8_t random[8];
ret = hsm_get_random(random, 8);
// 步骤2:计算PIN哈希
uint8_t pin_hash[32];
sm3_hash(pin->pin_value, pin->pin_len, pin_hash);
// 步骤3:用PIN哈希加密随机数
uint8_t encrypted[16];
sm4_encrypt(pin_hash, 16, random, 8, encrypted);
// 注意:此为厂商特定方案,标准做法应使用KDF(如PBKDF2/HKDF)从PIN派生密钥
// 步骤4:发送加密结果到HSM验证
ret = send_verify_command(pin->owner, encrypted);
return ret;
}
这种机制的优势:
- PIN不直接传输:即使通信被截获,也无法获取PIN值
- 随机数挑战:每次验证使用不同的随机数,防止重放攻击
- 哈希+加密:双重处理,安全性更高
HSM内部会执行相同的计算:用存储的PIN哈希解密接收到的数据,如果解密结果与之前发送的随机数一致,说明PIN正确。
验证失败处理
PIN验证失败后,HSM会记录失败次数:
响应:63 C0
解析:
SW1 = 0x63 // 认证失败
SW2 = 0xC0 // 低4位表示剩余重试次数(0x0表示0次,密码已锁)
达到重试次数上限后,HSM可能进入锁定状态,需要管理员介入解锁。
传输密钥机制
什么是传输密钥?
传输密钥(Transport Key)用于加密Host与HSM之间传输的敏感数据,如密钥导入时的密钥值。
传输密钥用途
密钥导入场景:
原始密钥值 → 用传输密钥加密 → 发送到HSM → HSM解密 → 存储密钥
密钥导出场景:
HSM读取密钥 → 用传输密钥加密 → 发送到Host → Host解密 → 获得密钥值
传输密钥ID固定为0x02,这是HSM协议的规定。
设置传输密钥
typedef struct {
uint8_t alg; // 算法类型
uint8_t id; // 密钥ID
uint32_t val_len; // 密钥长度
uint8_t val[MAX_KEY_LEN]; // 密钥值
uint8_t type; // 密钥类型
} sym_key_t;
// 设置传输密钥(ID必须为0x02)
uint8_t trankey_val[16] = {0x40, 0x41, 0x42, 0x43,
0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4A, 0x4B,
0x4C, 0x4D, 0x4E, 0x4F};
sym_key_t trankey = {0};
trankey.val_len = 16;
trankey.id = 0x02; // 固定为传输密钥ID
memcpy(trankey.val, trankey_val, 16);
// 先验证管理员PIN,才能设置传输密钥
ret = hsm_verify_pin(&admin_pin);
if (ret != HSM_OK) {
return ret;
}
ret = hsm_set_transport_key(&trankey);
if (ret != HSM_OK) {
LOGE("设置传输密钥失败");
return ret;
}
使用传输密钥导入密钥
// 导入密钥时指定使用传输密钥加密
sym_key_t key = {0};
key.val_len = 16;
key.id = 0x11; // 新密钥的ID
memcpy(key.val, original_key_value, 16);
// encrypt_flag为1表示使用传输密钥加密传输
ret = hsm_import_key(&key, 1); // encrypt_flag=1
SDK内部会自动用传输密钥加密密钥值后再发送。
密钥管理操作
密钥ID范围
HSM中的密钥通过ID标识,ID范围决定密钥类型:
| ID范围 | 密钥类型 | 说明 |
|---|---|---|
| 0x00-0xEF | 固定密钥 | 持久存储,掉电不丢失 |
| 0xF0-0xFF | 临时密钥 | 会话期间有效,掉电后清除 |
| 0x02 | 传输密钥 | 固定用途,用于加密传输 |
密钥生成
// 生成非对称密钥对(RSA)
uint16_t pubkey_id = 0x90;
uint16_t privkey_id = 0x91;
ret = hsm_verify_pin(&admin_pin); // 需要先解锁
if (ret != HSM_OK) {
return ret;
}
ret = hsm_generate_keypair(ALG_RSA_1024, pubkey_id, privkey_id);
if (ret == HSM_OK) {
// 密钥对生成成功
// 公钥可以导出,私钥不可导出
}
// 生成对称密钥(AES)
sym_key_t symkey = {0};
symkey.id = 0x93;
symkey.val_len = 16; // AES-128
ret = hsm_generate_symkey(ALG_AES_128, &symkey);
密钥导入
导入密钥有多种方式:
// 方式1:明文导入(不推荐)
ret = hsm_import_key(&key, 0); // encrypt_flag=0
// 方式2:使用传输密钥加密导入(推荐)
ret = hsm_import_key(&key, 1); // encrypt_flag=1
// 方式3:使用其他密钥加密导入
// 例如:用公钥加密,HSM用对应私钥解密
ret = hsm_import_key_encrypted(&key, 0x0B); // encrypt_key_id=0x0B
密钥导出
// 导出公钥(私钥不可导出)
uint8_t pubkey_buf[256];
uint32_t pubkey_len;
ret = hsm_export_key(pubkey_id, pubkey_buf, &pubkey_len);
尝试导出私钥会返回错误:
响应:69 82
解析:
SW1-SW2 = 0x6982 // 安全条件不满足
原因:私钥被标记为不可导出(CKA_EXTRACTABLE=false)
密钥删除
ret = hsm_delete_key(key_id);
if (ret == HSM_OK) {
// 密钥已删除
}
密钥信息查询
key_info_t info;
ret = hsm_get_key_info(key_id, &info);
if (ret == HSM_OK) {
// info包含密钥类型、长度、算法等信息
// 但不包含密钥值本身
}
密钥的安全属性
HSM中的密钥有多个安全属性,控制其行为:
| 属性 | 说明 |
|---|---|
| CKA_SENSITIVE | 密钥是否敏感(敏感密钥值不可读取) |
| CKA_EXTRACTABLE | 密钥是否可导出 |
| CKA_ALWAYS_SENSITIVE | 密钥创建时是否就是敏感的 |
| CKA_NEVER_EXTRACTABLE | 密钥是否从未可导出 |
这些属性在密钥创建时设置,部分属性不可修改。
安全属性组合示例
最高安全密钥:
CKA_SENSITIVE = true
CKA_EXTRACTABLE = false
CKA_ALWAYS_SENSITIVE = true
CKA_NEVER_EXTRACTABLE = true
→ 密钥值永远不可离开HSM
可备份密钥:
CKA_SENSITIVE = true
CKA_EXTRACTABLE = true
→ 密钥可以通过安全包装导出备份
文件系统操作
HSM内部有文件系统,用于存储数据(如配置、证书等)。
文件操作流程
// 步骤1:验证管理员PIN
ret = hsm_verify_pin(&admin_pin);
// 步骤2:创建文件
uint16_t fid = 0x0006;
ret = hsm_create_file(fid);
// 步骤3:选择文件
ret = hsm_select_device_file(fid);
// 步骤4:写入数据
uint8_t data[16] = {...};
ret = hsm_write_file(0, 16, data); // 从偏移0写入16字节
// 步骤5:读取数据
uint8_t read_buf[16];
uint32_t read_len;
ret = hsm_read_file(0, 16, read_buf, &read_len);
文件系统特点
HSM的“文件”与传统文件系统不同:
- 文件ID(FID):2字节标识,范围0x0000-0xFFFF(0xFFFF被系统占用)
- 存储空间:用户空间约256KB
- 访问控制:需要PIN验证才能读写
- 传输限制:单次SPI传输最多128字节(受硬件限制)
文件存储建议
每个文件存储数据不超过128字节
需要存储更多数据时,分成多个文件
安全操作的最佳实践
操作流程模板
se_error_t secure_operation(void) {
se_error_t ret;
// 步骤1:验证PIN,获取权限
ret = hsm_verify_pin(&admin_pin);
if (ret != HSM_OK) {
LOGE("PIN验证失败");
return ret;
}
// 步骤2:执行敏感操作
ret = hsm_generate_keypair(...);
if (ret != HSM_OK) {
LOGE("密钥生成失败");
// 注意:失败后HSM仍处于解锁状态
// 建议继续执行reset操作
}
// 步骤3:重置HSM,回到锁定状态
ret = hsm_reset();
// 降低安全风险:操作完成后立即锁定
return ret;
}
错误处理
安全操作失败时的常见错误码:
| 错误码 | 含义 | 处理建议 |
|---|---|---|
| 0x6982 | 安全条件不满足 | 先执行PIN验证 |
| 0x6985 | 使用条件不满足 | 检查操作顺序是否正确 |
| 0x63CX | PIN验证失败 | 检查PIN值,关注剩余重试次数 |
| 0x6A82 | 文件未找到 | 检查文件ID是否正确 |
| 0x6A86 | 参数不正确 | 检查APDU参数 |
安全建议
- 操作完成后立即reset:让HSM回到锁定状态,降低风险
- 使用传输密钥加密敏感数据:避免明文传输密钥值
- 妥善保管PIN值:PIN是访问HSM的凭证,不要硬编码在代码中
- 监控重试次数:多次失败可能导致锁定
- 区分密钥用途:临时密钥用于会话,固定密钥用于长期存储
小结
HSM的安全机制像一个多层安保系统:
- PIN验证是门禁卡,只有持卡人才能进入
- 传输密钥是保险柜钥匙,保护贵重物品的运输
- 密钥属性是访问规则,定义谁能看到什么
理解这些机制,才能正确使用HSM的安全功能,避免“安全条件不满足”这样的错误。
下一节,我们将进入AUTOSAR集成——看UDS服务的实现。
【下集预告】
UDS 27服务怎么实现?
异步回调怎么处理?状态机怎么设计?
多安全等级怎么管理?
下一节,AUTOSAR集成实战。