Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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;
}

这种机制的优势:

  1. PIN不直接传输:即使通信被截获,也无法获取PIN值
  2. 随机数挑战:每次验证使用不同的随机数,防止重放攻击
  3. 哈希+加密:双重处理,安全性更高

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的“文件”与传统文件系统不同:

  1. 文件ID(FID):2字节标识,范围0x0000-0xFFFF(0xFFFF被系统占用)
  2. 存储空间:用户空间约256KB
  3. 访问控制:需要PIN验证才能读写
  4. 传输限制:单次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使用条件不满足检查操作顺序是否正确
0x63CXPIN验证失败检查PIN值,关注剩余重试次数
0x6A82文件未找到检查文件ID是否正确
0x6A86参数不正确检查APDU参数

安全建议

  1. 操作完成后立即reset:让HSM回到锁定状态,降低风险
  2. 使用传输密钥加密敏感数据:避免明文传输密钥值
  3. 妥善保管PIN值:PIN是访问HSM的凭证,不要硬编码在代码中
  4. 监控重试次数:多次失败可能导致锁定
  5. 区分密钥用途:临时密钥用于会话,固定密钥用于长期存储

小结

HSM的安全机制像一个多层安保系统:

  • PIN验证是门禁卡,只有持卡人才能进入
  • 传输密钥是保险柜钥匙,保护贵重物品的运输
  • 密钥属性是访问规则,定义谁能看到什么

理解这些机制,才能正确使用HSM的安全功能,避免“安全条件不满足”这样的错误。

下一节,我们将进入AUTOSAR集成——看UDS服务的实现。

【下集预告】

UDS 27服务怎么实现?

异步回调怎么处理?状态机怎么设计?

多安全等级怎么管理?

下一节,AUTOSAR集成实战。