嵌入式安全与固件保护深度解析——从代码保护到安全启动

发布时间:2026/7/17 21:02:03
嵌入式安全与固件保护深度解析——从代码保护到安全启动 一、引言嵌入式安全曾经是锦上添花现在已经成为必不可少。当你连接到互联网的设备被远程入侵、当你固件被竞争对手克隆、当你的产品密钥被破解——这些不是假设场景而是每天都在发生的现实。嵌入式安全涉及多个层面从芯片的物理保护到网络通信的加密——任何一环薄弱都可能导致全局崩溃。本文将梳理嵌入式安全的关键领域代码保护RDP、Flash 读保护固件加密与安全启动密钥管理怎样在 MCU 上安全存储密钥通信加密TLS/DTLS 在 MCU 上的实现常见的攻击手段与防御安全开发生命周期平台STM32F407Cortex-M4F为硬件基准 适用所有具备硬件加密能力的 Cortex-M MCU二、芯片级代码保护2.1 STM32 的 RDP读保护机制STM32 的 Flash 读保护分为三个级别 ​ ┌─────────────────────────────────────────────────────┐ │ Level 0无保护 │ │ → Flash 可通过 JTAG/SWD 完全读取 │ │ → 任何调试器都能 dump 出固件 │ │ → 默认状态 │ ├─────────────────────────────────────────────────────┤ │ Level 1读保护 │ │ → Flash 和备份 SRAM 只能由内部代码访问 │ │ → 调试器连接时 Flash 被自动锁定 │ │ → 只能通过全片擦除 RDP 降级来恢复 │ │ ★ 需要 Level 1 → 0先擦除全部 Flash再改 RDP │ ├─────────────────────────────────────────────────────┤ │ Level 2不可逆保护 │ │ → 与 Level 1 相同 禁止降级 │ │ → 调试器永久无法连接 │ │ → ★ 一旦设置永远无法修改 Flash 或调试 │ │ → 适用于最终量产但绝不能用在开发/调试阶段 │ └─────────────────────────────────────────────────────┘ ​ 选项字节配置 ​ [RDP] [保护级别] 0xAA → Level 0无保护 0xBB 或 0xCC取决于芯片F1 是 0x00 以外的任何值→ Level 1 0xCC → Level 2不可逆慎用2.2 RDP 的局限性// RDP 能防住的 // ✅ 阻止通过 SWD/JTAG 读取 Flash // ✅ 阻止调试器连接 ​ // RDP 防不住的 // ❌ 芯片拆盖 电子显微镜读 Flash 内容需要 $$$ 设备 // ❌ 电压/时钟故障注入攻击Voltage Glitching // ❌ 侧信道攻击功耗分析等 // ❌ 通过固件本身的漏洞如未检查固件签名 // ❌ 通过 Bootloader 的后门 ​ // 结论RDP 是嵌入式安全的底线不是全部。 // 必须配合固件加密、签名验证等措施。2.3 代码实现// 设置 RDP Level 1通过选项字节编程 void Flash_SetReadProtection(uint8_t level) { FLASH_OBProgramInitTypeDef ob; // 1. 解锁选项字节 FLASH_OB_Unlock(); // 2. 读取当前选项字节 FLASH_OB_GetConfig(ob); // 3. 修改 RDP ob.OptionType OPTIONBYTE_RDP; ob.RDPLevel level; // OB_RDP_LEVEL_1 或 OB_RDP_LEVEL_2 // 4. 编程选项字节 FLASH_OB_Program(ob); // 5. 锁定 强制重载 FLASH_OB_Lock(); FLASH_OB_Launch(); // ★ 复位后生效 } ​ // ⚠️ 设置 Level 2 之前——三思再三思 // 之后再也无法通过 SWD 调试或重烧固件三、固件加密3.1 加密流程设计固件加密的完整流程 ​ PC 端出厂烧录前 MCU 端启动时 ┌──────────────────────┐ ┌──────────────────────────┐ │ 原始固件 firmware.bin│ │ Bootloader │ │ │ │ │ │ │ │ ▼ │ │ ▼ │ │ AES-256-GCM 加密 │ │ 从内部 Flash 读取 │ │ 密钥: 设备唯一密钥 │ │ 设备唯一密钥 │ │ │ │ │ (在安全存储区) │ │ ▼ │ │ │ │ │ firmware.enc │ ──烧录──► │ ▼ │ │ 认证标签(GCM Tag) │ │ AES-256-GCM 解密 │ │ │ │ 认证标签验证 │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ 验证通过 → 跳转 APP │ │ │ │ 验证失败 → 停止启动 │ └──────────────────────┘ └──────────────────────────┘3.2 使用 STM32 硬件 CRYP 加速 AES// STM32F407 有硬件 CRYP 模块AES-256 速度快、功耗低 #include stm32f4xx_cryp.h ​ // AES-256-GCM 解密硬件加速 int firmware_decrypt(uint8_t *ciphertext, uint32_t ct_len, uint8_t *key_256bit, // 32 字节密钥 uint8_t *iv_96bit, // 12 字节 IV uint8_t *tag_128bit, // 16 字节认证标签 uint8_t *plaintext) { CRYP_InitTypeDef cryp; CRYP_KeyInitTypeDef key; CRYP_IVInitTypeDef iv; CRYP_GCMInitTypeDef gcm; uint8_t calc_tag[16]; // 1. 使能 CRYP 时钟 RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_CRYP, ENABLE); // 2. 配置 AES-GCM cryp.CRYP_AlgoDir CRYP_AlgoDir_Decrypt; // 解密 cryp.CRYP_AlgoMode CRYP_AlgoMode_AES_GCM; cryp.CRYP_DataType CRYP_DataType_8b; cryp.CRYP_KeySize CRYP_KeySize_256b; CRYP_Init(cryp); // 3. 加载密钥 key.CRYP_Key0Left *(uint32_t *)(key_256bit 0); key.CRYP_Key0Right *(uint32_t *)(key_256bit 4); key.CRYP_Key1Left *(uint32_t *)(key_256bit 8); key.CRYP_Key1Right *(uint32_t *)(key_256bit 12); key.CRYP_Key2Left *(uint32_t *)(key_256bit 16); key.CRYP_Key2Right *(uint32_t *)(key_256bit 20); key.CRYP_Key3Left *(uint32_t *)(key_256bit 24); key.CRYP_Key3Right *(uint32_t *)(key_256bit 28); CRYP_KeyInit(key); // 4. 加载 IV iv.CRYP_IV0Left *(uint32_t *)(iv_96bit 0); iv.CRYP_IV0Right *(uint32_t *)(iv_96bit 4); iv.CRYP_IV1Left *(uint32_t *)(iv_96bit 8); iv.CRYP_IV1Right 0; CRYP_IVInit(iv); // 5. GCM 模式配置 gcm.CRYP_Header NULL; // 无附加认证数据(AAD) gcm.CRYP_HeaderSize 0; CRYP_GCMInit(gcm); // 6. 执行解密128 位块为单位 CRYP_FIFOFlush(); uint32_t blocks ct_len / 16; for (uint32_t i 0; i blocks; i) { // 将密文写入 CRYP读出明文 CRYP_DataIn(ciphertext[i * 16]); CRYP_DataOut(plaintext[i * 16]); while (!(CRYP_GetFlagStatus(CRYP_FLAG_OUTFIFO))); } // 7. 读取计算出的认证标签与期望的标签比较 CRYP_GCMReadPHASE(calc_tag); if (memcmp(calc_tag, tag_128bit, 16) ! 0) { // ★ 标签不匹配——固件被篡改或密钥错误 return -1; } return 0; }3.3 固件加密工具PC 端# encrypt_firmware.py —— 在 PC 上加密固件 from cryptography.hazmat.primitives.ciphers.aead import AESGCM import os import struct ​ def encrypt_firmware(firmware_path, key_hex, output_path): key bytes.fromhex(key_hex) # 32 字节AES-256 # 生成随机 IV96 位 12 字节 iv os.urandom(12) # AES-GCM 加密 aesgcm AESGCM(key) ciphertext aesgcm.encrypt(iv, open(firmware_path, rb).read(), None) # ciphertext 密文 16 字节认证标签GCM 自动追加 ct ciphertext[:-16] tag ciphertext[-16:] # 打包魔数 版本 IV TAG 密文长度 密文 with open(output_path, wb) as f: f.write(bFWENC) # 魔数 f.write(struct.pack(I, 0x00010000)) # 版本 f.write(iv) # 12 字节 IV f.write(tag) # 16 字节 TAG f.write(struct.pack(I, len(ct))) # 密文长度 f.write(ct) # 密文体 print(fEncrypted: {firmware_path} - {output_path}) print(f Original size: {os.path.getsize(firmware_path)}) print(f Encrypted size: {os.path.getsize(output_path)}) ​ if __name__ __main__: encrypt_firmware(firmware.bin, 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef, firmware.enc)四、密钥管理——嵌入式安全最硬的骨头4.1 密钥存在哪┌─────────────────────────────────────────────────────┐ │ 嵌入式密钥存储方案对比 │ ├────────────────┬──────────┬──────────┬──────────────┤ │ 存储位置 │ 安全性 │ 实现难度 │ 批量生产 │ ├────────────────┼──────────┼──────────┼──────────────┤ │ 硬编码在代码中 │ ★☆☆☆☆ │ ★★★★★ │ 简单但极不安全│ │ (const uint8_t) │ │ │ 反汇编即暴露 │ ├────────────────┼──────────┼──────────┼──────────────┤ │ Flash 独立区域 │ ★★☆☆☆ │ ★★★★☆ │ 需保护 RDP │ │ RDP Level 1 │ │ │ │ ├────────────────┼──────────┼──────────┼──────────────┤ │ 备份 SRAM │ ★★★☆☆ │ ★★★☆☆ │ 需电池供电 │ │ (防篡改检测) │ │ │ │ ├────────────────┼──────────┼──────────┼──────────────┤ │ OTP (一次性编程)│ ★★★★☆ │ ★★★☆☆ │ 只能写一次 │ │ │ │ │ 测试时要小心 │ ├────────────────┼──────────┼──────────┼──────────────┤ │ 安全元件 (SE) │ ★★★★★ │ ★★☆☆☆ │ 额外芯片BOM │ │ ATECC608A等 │ │ │ 成本$0.5 │ └────────────────┴──────────┴──────────┴──────────────┘4.2 使用 STM32 的 OTP 区域存储密钥// STM32F407 的 OTPOne-Time Programmable区域 // OTP 基址0x1FFF7800共 16 字节512 字节空间但只有前 16 字节可用 ​ #define OTP_BASE 0x1FFF7800 #define OTP_SIZE 16 ​ // 写入密钥只能写一次 int OTP_WriteKey(uint8_t *key_16byte) { // 1. 检查是否已写入OTP 擦除后是 0xFF uint8_t *otp (uint8_t *)OTP_BASE; for (int i 0; i 16; i) { if (otp[i] ! 0xFF) { printf(OTP already programmed! Cannot write again.\r\n); return -1; // 已写入不能再写 } } // 2. 解锁 Flash FLASH_Unlock(); // 3. 写入OTP 按字节编程 for (int i 0; i 16; i) { FLASH_ProgramByte(OTP_BASE i, key_16byte[i]); } // 4. 锁定 FLASH_Lock(); // 5. 验证 if (memcmp((void *)OTP_BASE, key_16byte, 16) 0) { printf(OTP key programmed successfully!\r\n); return 0; } else { printf(OTP verification failed!\r\n); return -2; } } ​ // ⚠️ 警告 // OTP 写入不可逆16 字节中的每一位只能从 1 → 0不能从 0 → 1 // 如 OTP 擦除值是 0xFF全 1写入 0xAA 后无法改成 0x55因为位只能清 04.3 安全的密钥派生// 不要直接用出厂预设的单一密钥 // 使用密钥派生函数KDF从设备唯一 ID 派生 #include stm32f4xx.h // 从设备唯一 ID 派生加密密钥 void derive_device_key(uint8_t *output_key_32byte) { // 1. 读取设备唯一 ID96 位出厂固化 uint32_t uid[3]; uid[0] *(volatile uint32_t *)0x1FFF7A10; // U_ID[31:0] uid[1] *(volatile uint32_t *)0x1FFF7A14; // U_ID[63:32] uid[2] *(volatile uint32_t *)0x1FFF7A18; // U_ID[95:64] // 2. 使用 HMAC-SHA256 派生密钥 // Key HMAC-SHA256(MasterKey, UID || firmware_encryption) // MasterKey 存储在 OTP 中 uint8_t master_key[16]; memcpy(master_key, (void *)OTP_BASE, 16); // 构建消息UID 用途标签 uint8_t message[20]; memcpy(message, uid, 12); memcpy(message 12, fw_enc, 6); // HMAC-SHA256可用 STM32F407 的 HASH 模块硬件加速 // ... 在此计算 HMAC-SHA256 ... // output_key_32byte 的前 32 字节作为派生密钥 // ★ 好处 // 1. 每台设备的加密密钥不同基于 UID // 2. 不同用途使用不同标签如 fw_enc vs comm_key // 3. 即使 UID 公开没有 MasterKey 也无法派生 }五、通信安全——TLS 在 MCU 上的实现5.1 mbedTLS 移植// mbedTLS——专为嵌入式设计的 TLS 库 // 比 OpenSSL 小 10~50 倍 #include mbedtls/ssl.h #include mbedtls/entropy.h #include mbedtls/ctr_drbg.h mbedtls_ssl_context ssl; mbedtls_ssl_config conf; mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; int TLS_Client_Init(void) { // 1. 初始化随机数生成器 mbedtls_entropy_init(entropy); mbedtls_ctr_drbg_init(ctr_drbg); mbedtls_ctr_drbg_seed(ctr_drbg, mbedtls_entropy_func, entropy, NULL, 0); // 2. 配置 TLS mbedtls_ssl_init(ssl); mbedtls_ssl_config_init(conf); // TLS 1.2, 客户端模式 mbedtls_ssl_config_defaults(conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); mbedtls_ssl_conf_rng(conf, mbedtls_ctr_drbg_random, ctr_drbg); // 3. 设置证书验证生产环境必须验证服务器证书 mbedtls_ssl_conf_authmode(conf, MBEDTLS_SSL_VERIFY_REQUIRED); mbedtls_ssl_conf_ca_chain(conf, ca_cert, NULL); // 信任 CA 证书 // 4. 绑定 SSL 上下文 mbedtls_ssl_setup(ssl, conf); mbedtls_ssl_set_bio(ssl, tcp_fd, mbedtls_net_send, mbedtls_net_recv, NULL); // 5. TLS 握手 int ret; while ((ret mbedtls_ssl_handshake(ssl)) ! 0) { if (ret ! MBEDTLS_ERR_SSL_WANT_READ ret ! MBEDTLS_ERR_SSL_WANT_WRITE) { printf(TLS handshake failed: -0x%x\r\n, -ret); return -1; } } printf(TLS Connected! Cipher: %s\r\n, mbedtls_ssl_get_ciphersuite(ssl)); return 0; } // mbedTLS 配置裁剪减少 ROM/RAM 占用 // 在 mbedtls_config.h 中 // #define MBEDTLS_AES_ROM_TABLES // AES 表放 ROM // #undef MBEDTLS_CIPHER_MODE_CBC // 不需要的密码模式禁用 // #undef MBEDTLS_CIPHER_MODE_XTS // #define MBEDTLS_SSL_MAX_CONTENT_LEN 4096 // 减小 SSL 缓冲区5.2 TLS 在 MCU 上的资源开销mbedTLS 典型资源占用TLS 1.2, ECDHE-RSA-AES128-GCM-SHA256 ROM: ~80 KB RAM: ~16 KB每个 TLS 连接 优化后去掉不用的算法 ROM: ~45 KB RAM: ~8 KB 如果你的 MCU 有 20KB RAM → 一个 TLS 连接就吃掉 40%FreeRTOS LwIP 再吃掉 50% → 留给应用的只有 10%需要仔细权衡 替代方案 → DTLS基于 UDP开销更小 → 预共享密钥PSK不需要证书链 → 在网关/MCU 外部处理 TLS六、常见攻击手段与防御攻击方式原理防御措施固件克隆通过 SWD 读取 FlashRDP Level 1 固件加密中间人攻击拦截网络通信TLS 服务器证书验证故障注入电压/时钟毛刺跳过安全检查硬件安全芯片 冗余检查侧信道攻击功耗分析恢复密钥恒定时间算法 硬件防护固件回滚刷入旧版本利用已知漏洞版本号防回滚 OTP 计数JTAG 后门调试接口未关闭RDP Level 1量产熔断 SWD缓冲区溢出输入数据覆盖栈/堆启用 MPU 栈保护Stack Canary七、安全开发的实践清单嵌入式安全开发自检清单量产前必查 □ 1. RDP Level 1 或 Level 2 已启用 □ 2. OTP 区域已正确写入密钥且已验证 □ 3. Bootloader 验证固件签名或认证标签 □ 4. 固件版本号防回滚 □ 5. 所有网络通信使用 TLS/DTLS □ 6. 生产烧录密钥与开发测试密钥分离 □ 7. 调试串口、隐藏命令在量产固件中禁用 □ 8. 编译器安全选项-fstack-protector-strong □ 9. MPU 保护关键内存区域 □ 10. 输入数据长度验证防止溢出 □ 11. 错误处理不泄露内部信息 □ 12. 固件更新包经过签名验证八、总结安全层核心措施关键原则芯片级RDP 读保护量产必须 Level 1固件级AES-GCM 加密 签名加密和认证都要做密钥级OTP 设备 UID 派生每台设备密钥独立通信级mbedTLS / PSK根据 MCU 能力权衡工程级安全清单逐项检查安全不是可选项核心认知嵌入式安全是一个木桶——任何一个短板都会让其他所有措施失效。物理攻击拆盖/故障注入需要 $$$ 的成本对于大多数产品做好芯片保护 固件加密 通信加密已经可以阻止 99% 的攻击者。