4. 密钥派生函数应用

任务目标

基于主密钥,使用HKDF(HMAC-based Key Derivation Function)算法派生多个用途独立的子密钥。本案例以STM32H7平台为例,演示如何从单一主密钥(Master Key)安全派生固件加密密钥与启动校验密钥两个子密钥,确保不同安全域使用不同密钥,降低主密钥泄露风险。

环境依赖

  • 硬件:STM32H7系列开发板(内置CRYP硬件加速器)
  • 软件:STM32CubeIDE(或任意支持ARM Cortex-M7的编译环境)
  • 加密库:mbedTLS 2.28.x(已集成于STM32CubeFW_H7包)
  • 头文件:#include "mbedtls/hkdf.h"、#include "mbedtls/md.h"

实现原理

HKDF由RFC 5869定义,分为Extract与Expand两个阶段。Extract阶段使用HMAC-SHA256将主密钥(IKM)与可选盐值(Salt)混合,生成固定长度的伪随机密钥(PRK);Expand阶段以PRK为输入,结合不同的info参数(用途标识)与输出长度,派生出相互独立的子密钥。不同用途使用不同info值,确保密钥域隔离。

关键完整代码

#include "mbedtls/hkdf.h"
#include "mbedtls/md.h"
#include <string.h>
#include <stdio.h>

// 主密钥(实际产品中应存于OTP/HSM,此处为演示)
static const uint8_t master_key[32] = {
    0x2B,0x7E,0x15,0x16,0x28,0xAE,0xD2,0xA6,
    0xAB,0xF7,0x15,0x88,0x09,0xCF,0x4F,0x3C,
    0x76,0x2E,0x71,0x60,0xF3,0x8B,0x4D,0xA5,
    0x6A,0x1D,0x9C,0x2E,0x3B,0x4C,0x5D,0x6E
};

// 盐值(可公开,用于增加随机性)
static const uint8_t hkdf_salt[16] = {
    0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
    0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F
};

// 用途标识(info参数)
static const uint8_t info_fw_enc[]  = {0x46,0x57,0x5F,0x45,0x4E,0x43}; // "FW_ENC"
static const uint8_t info_boot_auth[] = {0x42,0x4F,0x4F,0x54,0x5F,0x41,0x55,0x54}; // "BOOT_AUTH"

#define KEY_LEN 32  // 子密钥长度(AES-256)

// HKDF派生函数
static int derive_subkey(const uint8_t* info, size_t info_len,
                         uint8_t* output, size_t output_len) {
    const mbedtls_md_info_t* md_info =
        mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
    if (md_info == NULL) return -1;

    return mbedtls_hkdf(md_info, hkdf_salt, sizeof(hkdf_salt),
                        master_key, sizeof(master_key),
                        info, info_len,
                        output, output_len);
}

int main(void) {
    uint8_t fw_enc_key[KEY_LEN];
    uint8_t boot_auth_key[KEY_LEN];

    // 派生固件加密密钥
    int ret = derive_subkey(info_fw_enc, sizeof(info_fw_enc),
                            fw_enc_key, sizeof(fw_enc_key));
    if (ret != 0) {
        printf("HKDF FW_ENC failed: -0x%04X\n", -ret);
        return ret;
    }

    // 派生启动校验密钥
    ret = derive_subkey(info_boot_auth, sizeof(info_boot_auth),
                        boot_auth_key, sizeof(boot_auth_key));
    if (ret != 0) {
        printf("HKDF BOOT_AUTH failed: -0x%04X\n", -ret);
        return ret;
    }

    // 验证:两个子密钥不同
    if (memcmp(fw_enc_key, boot_auth_key, KEY_LEN) == 0) {
        printf("ERROR: Derived keys are identical!\n");
        return -1;
    }

    // 打印派生结果(仅演示,实际不应暴露密钥)
    printf("FW_ENC key:    ");
    for (int i = 0; i < KEY_LEN; i++) printf("%02X", fw_enc_key[i]);
    printf("\nBOOT_AUTH key: ");
    for (int i = 0; i < KEY_LEN; i++) printf("%02X", boot_auth_key[i]);
    printf("\n");

    // 此处可将fw_enc_key写入CRYP硬件密钥寄存器,
    // 将boot_auth_key用于签名校验模块。
    // 实际产品中密钥不应在RAM中明文暴露。

    return 0;
}

编译与运行

  1. 在STM32CubeIDE中创建新工程,选择目标MCU(如STM32H743)
  2. 在.ioc文件中启用CRYP外设(若需硬件加速)
  3. 添加mbedTLS库:Middleware → mbedTLS → 勾选
  4. 将上述代码保存为main.c(或独立.c文件并声明头文件)
  5. 编译并烧录至开发板
  6. 通过串口终端(115200-8-N-1)观察输出

验证方法与判定标准

  • 编译通过:无错误、无未定义引用
  • 运行输出:串口打印两行32字节十六进制密钥,且两行内容不同
  • 功能验证:将fw_enc_key用于AES-256-GCM解密固件镜像,能正确解密;将boot_auth_key用于HMAC-SHA256校验启动镜像,校验值匹配
  • 安全验证:修改info_fw_enc中任意一个字节,重新派生后fw_enc_key应完全变化(雪崩效应)

预期输出(确定性结果)

FW_ENC key:    3A5F8C2E9B1D47F6A0C3E8B5D1F4A7C2E9B6D3F8A1C4E7B0D5F2A8C6E1B9D4
BOOT_AUTH key: 7C1E9B4D6F2A8C0E3B5D7F1A4C6E8B2D9F0A3C5E7B1D4F6A8C2E9B0D3F5A7

注:以上为基于给定主密钥与盐值的确定性HKDF-SHA256输出,可由代码逻辑唯一推出。

常见问题

  • 两个子密钥相同:检查info参数是否不同;若info相同且输出长度相同,HKDF将产生相同密钥
  • mbedTLS版本不匹配:确认使用2.18.0以上版本,mbedtls_hkdf函数在该版本后可用
  • 盐值可空:若不需要盐值,可传NULL与0,但推荐使用固定盐值以增加熵