2. HSM安全密钥存储与读取
任务定义
在MCU HSM(硬件安全模块)应用中,设备唯一密钥是安全通信、固件签名校验和数据加密的基础。本案例目标是在HSM内部生成一个128位随机密钥,将其安全存储于HSM非易失性存储区(NVM),并提供读取接口供应用层在安全上下文中使用。要求密钥永不以明文形式出现在MCU主处理器内存中,且具备防止重复生成的保护机制。
硬件连接与HSM内部结构
代码实现
以下代码基于NXP S32K3系列HSM固件接口,使用C语言实现。核心逻辑包括:检查密钥是否已存在、调用HSM安全服务生成密钥、将密钥写入NVM指定槽位。
/* hsm_unique_key.c */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "hsm_api.h" /* HSM安全服务API */
#include "hsm_nvm.h" /* HSM非易失存储驱动 */
#define KEY_SLOT_ID 0x01u /* NVM中密钥槽位编号 */
#define KEY_SIZE_BYTES 16u /* AES-128密钥长度 */
/* HSM密钥生成状态 */
typedef enum {
KEY_STATE_UNINIT = 0,
KEY_STATE_GENERATED = 1,
KEY_STATE_ERROR = 0xFF
} key_state_t;
static key_state_t key_state = KEY_STATE_UNINIT;
/**
* @brief 检查密钥槽位是否已有有效密钥
* @return 1=已存在, 0=未生成, -1=错误
*/
static int hsm_key_slot_occupied(void)
{
uint8_t slot_status = 0;
hsm_err_t err;
err = hsm_nvm_read_status(KEY_SLOT_ID, &slot_status);
if (err != HSM_OK) {
return -1;
}
return (slot_status == 0xA5u) ? 1 : 0; /* 0xA5为有效标志 */
}
/**
* @brief 生成并存储设备唯一密钥
* @return 0=成功, 1=密钥已存在, -1=失败
*/
int hsm_generate_unique_key(void)
{
hsm_err_t err;
uint8_t key_material[KEY_SIZE_BYTES];
hsm_key_handle_t key_handle;
/* 检查是否已生成,防止重复覆盖 */
int occupied = hsm_key_slot_occupied();
if (occupied < 0) {
return -1;
}
if (occupied == 1) {
key_state = KEY_STATE_GENERATED;
return 1; /* 密钥已存在,无需重新生成 */
}
/* 步骤1: 调用HSM安全服务生成随机密钥 */
hsm_key_generate_input_t gen_input = {
.key_type = HSM_KEY_TYPE_AES_128,
.key_usage = HSM_KEY_USAGE_ENCRYPT | HSM_KEY_USAGE_DECRYPT,
.key_lifetime = HSM_KEY_LIFETIME_PERSISTENT
};
err = hsm_key_generate(&gen_input, &key_handle);
if (err != HSM_OK) {
return -1;
}
/* 步骤2: 从HSM内部导出密钥材料(仅在HSM边界内传输) */
err = hsm_key_export(key_handle, key_material, KEY_SIZE_BYTES);
if (err != HSM_OK) {
hsm_key_delete(key_handle);
return -1;
}
/* 步骤3: 将密钥写入NVM槽位 */
err = hsm_nvm_write_key(KEY_SLOT_ID, key_material, KEY_SIZE_BYTES);
if (err != HSM_OK) {
memset(key_material, 0, KEY_SIZE_BYTES);
hsm_key_delete(key_handle);
return -1;
}
/* 步骤4: 标记槽位为有效 */
err = hsm_nvm_write_status(KEY_SLOT_ID, 0xA5u);
if (err != HSM_OK) {
memset(key_material, 0, KEY_SIZE_BYTES);
hsm_key_delete(key_handle);
hsm_nvm_erase_key(KEY_SLOT_ID);
return -1;
}
/* 步骤5: 清理临时缓冲区并删除HSM内部临时句柄 */
memset(key_material, 0, KEY_SIZE_BYTES);
hsm_key_delete(key_handle);
key_state = KEY_STATE_GENERATED;
return 0;
}
/**
* @brief 获取设备唯一密钥句柄(供加密/签名等操作使用)
* @param handle 输出参数,密钥句柄
* @return 0=成功, -1=失败
*/
int hsm_get_unique_key(hsm_key_handle_t *handle)
{
if (handle == NULL) {
return -1;
}
/* 检查密钥是否已生成 */
if (key_state != KEY_STATE_GENERATED) {
int occupied = hsm_key_slot_occupied();
if (occupied != 1) {
return -1;
}
key_state = KEY_STATE_GENERATED;
}
/* 从NVM加载密钥到HSM内部,返回句柄 */
hsm_err_t err = hsm_nvm_load_key(KEY_SLOT_ID, handle);
return (err == HSM_OK) ? 0 : -1;
}
/* 主函数示例:演示调用流程 */
int main(void)
{
hsm_key_handle_t dev_key;
int ret;
/* 初始化HSM模块 */
hsm_init();
/* 首次启动:生成并存储唯一密钥 */
ret = hsm_generate_unique_key();
if (ret == 0) {
printf("设备唯一密钥生成并存储成功\n");
} else if (ret == 1) {
printf("设备唯一密钥已存在,跳过生成\n");
} else {
printf("密钥生成失败\n");
return -1;
}
/* 获取密钥句柄供后续安全操作使用 */
ret = hsm_get_unique_key(&dev_key);
if (ret == 0) {
printf("密钥句柄获取成功,可用于AES加密/解密操作\n");
} else {
printf("密钥句柄获取失败\n");
return -1;
}
/* 后续可使用dev_key进行安全操作,此处不再展开 */
return 0;
}
编译与部署
本案例代码需在HSM固件开发环境中编译,并烧录至MCU的HSM核。具体步骤如下:
- 将上述代码保存为
hsm_unique_key.c,加入HSM固件工程; - 链接HSM安全服务库(
hsm_api.h对应实现)和NVM驱动库(hsm_nvm.h对应实现); - 使用S32 Design Studio或IAR for ARM编译生成HSM固件镜像;
- 通过调试器将固件烧录至HSM专用Flash区域。
验证方法
完成烧录后,通过串口或调试器观察主函数输出,验证标准如下:
- 首次运行:串口输出“设备唯一密钥生成并存储成功”,随后输出“密钥句柄获取成功”;
- 复位后再次运行:输出“设备唯一密钥已存在,跳过生成”,证明密钥持久化存储生效;
- 安全属性验证:通过调试器检查MCU主处理器内存,无法找到16字节连续明文密钥数据;
- 防重复验证:连续调用
hsm_generate_unique_key()两次,第二次返回1且不改变原密钥内容。
判定标准:以上四项全部满足,且密钥槽位状态标志为0xA5,即可判定本案例实现通过。