OTA 实战四:安全启动与固件签名(Secure Boot)—— 拒绝非法固件

适用人群:做完全量/差分 OTA、想堵住"别人能给你设备刷恶意固件"这个洞的同学

读完你能得到:① 搞懂"为什么没签名的 OTA 等于开门揖盗";② 理解非对称签名如何做到"只有你能签、谁都改不了";③ 拿到把验签接进 OTA_02 Bootloader 的具体代码。


一、一个没签名的 OTA,等于门没锁

前面三篇我们让设备能升级、能省流量,但都默认一件事:下到设备的固件就是厂家发的。现实不是。

你的智能设备卖到千家万户,连着网。某天:

  • 中间人攻击 :同一 Wi-Fi / 4G 链路上有人截获你的 OTA 包,换成带木马的 evil.bin → 设备变成僵尸网络节点;
  • 物理接触:维修点、二手市场的人用串口/DFU 直接刷入竞品的固件 → 你的硬件被"洗"成别家的;
  • OTA 服务器被入侵:攻击者直接推送恶意固件给全网设备。

没有签名验证,以上全都能得手

安全启动 / 固件签名 要解决的就一句话:只有用你私钥签过的固件,设备才认;别人的、改过的,bootloader 直接从源头拒绝,根本不运行。


二、前置概念(4 个词)

名词 白话解释
非对称加密 一对钥匙:私钥(你藏好)+ 公钥(烧进设备)。私钥签名,公钥验签;有公钥也推不出私钥
数字签名 对"固件哈希"用私钥签名生成一段数据。设备用公钥验:① 确认是你签的( authenticity );② 确认固件没被改过( integrity )
安全启动 Secure Boot 一条"信任链":不可改的 ROM 验 stage1 → stage1 验 app,每一级只跑下一级当且仅当签名合法
签名 ≠ 加密 签名证明"谁发的、没被改",但不保密;想隐藏固件内容得另加 AES 加密

💡 核心直觉:你用私钥 给固件"盖章"。设备用公钥 验章。攻击者没有私钥,永远盖不出真章------哪怕他把固件改得亲妈都不认识,验签也过不了。


三、信任链:从 ROM 到 app 一级咬一级

复制代码
┌─────────────┐   验签   ┌────────────────┐   验签   ┌──────────┐
│ ROM Boot    │ ───────▶ │ Stage-1 BL     │ ───────▶ │  App     │
│ (不可改)     │ 公钥A    │ (验 app 用公钥B)│ 公钥B    │ (你的业务)│
└─────────────┘          └────────────────┘          └──────────┘
   锁死                        锁死                        锁死
  • ROM 里的验签公钥/哈希是芯片出厂烧死的,改不了
  • Stage-1(就是 OTA_02 你写的 Bootloader)用自己的公钥验 app;
  • 任意一级验签失败 → 整条链断裂,设备不启动非法代码

我们 OTA_02 的 Bootloader,在这里就升级成了"会验签的 Stage-1"。


四、签名与验签:完整流程

4.1 构建时(服务器 / CI,用私钥)

复制代码
firmware.bin ──SHA256──▶ hash
                      私钥 ──ECDSA签名──▶ signature.bin (约64~72字节, P-256)
最终镜像 = firmware.bin ‖ signature.bin

4.2 设备端(Bootloader,用公钥)

复制代码
收到镜像 → 拆出 firmware 和 signature
        → SHA256(firmware) 算 hash
        → ecdsa_verify(公钥, hash, signature)
             合法 → 写 Flash / 跳转
             非法 → 拒绝,停在 bootloader

算法选型:ECDSA P-256 + SHA256 是嵌入式 IoT 的甜点(密钥短、验签快、生态全)。RSA-2048 也能用但更慢更占空间;资源足可选 Ed25519。


五、可跑的示例

5.1 服务端签名(OpenSSL,直接能敲)

bash 复制代码
# 1) 生成 ECDSA P-256 密钥对(私钥保密!公钥烧设备)
openssl ecparam -name prime256v1 -genkey -noout -out priv.key
openssl ec -in priv.key -pubout -out pub.key

# 2) 对固件哈希并用私钥签名(签的是 hash,不是整包)
openssl dgst -sha256 -sign priv.key -out firmware.bin.sig firmware.bin

# 3)(演示)验证:用公钥验 (固件, 签名)
openssl dgst -sha256 -verify pub.key -signature firmware.bin.sig firmware.bin
# 输出 Verification OK 即合法

5.2 设备端验签(基于 mbedTLS,示意)

生产请用成熟库(mbedTLS / wolfSSL / tinycrypt),不要自己手搓椭圆曲线 。下面用 mbedTLS 的 pk(公钥)高层接口,自动兼容 P-256 与各版本差异:

c 复制代码
#include "mbedtls/sha256.h"
#include "mbedtls/ecdsa.h"
#include "mbedtls/ecp.h"

// 返回 0 = 验签通过;非0 = 拒绝
int verify_firmware(const uint8_t *fw, uint32_t fw_len,
                    const uint8_t *sig, uint32_t sig_len,
                    const uint8_t *pubkey_pem, uint32_t key_len)
{
    uint8_t hash[32];
    mbedtls_sha256(fw, fw_len, hash, 0);          // 算固件哈希

    mbedtls_pk_context pk;
    mbedtls_pk_init(&pk);
    // 解析 PEM/DER 公钥:openssl ec -pubout 出的是 PEM,正好对上
    int ret = mbedtls_pk_parse_public_key(&pk, pubkey_pem, key_len);
    if (ret != 0) { mbedtls_pk_free(&pk); return ret; }

    // 高层验签接口,自动处理 P-256 曲线与 mbedTLS 版本差异
    ret = mbedtls_pk_verify(&pk, MBEDTLS_MD_SHA256, hash, 32, sig, sig_len);
    mbedtls_pk_free(&pk);
    return ret;   // 0 合法,其它拒绝
}

> ⚠️ **版本与格式坑**:① 旧写法用 `mbedtls_ecdsa_context` 直接访问 `ctx.grp/ctx.Q`------在 **mbedTLS 3.x(当前 ESP-IDF v5 自带)这些字段已 opaque,编不过**;上面改用 `mbedtls_pk_*` 高层接口可跨版本。② `openssl ec -pubout` 产出的是 **PEM(SubjectPublicKeyInfo)**,要用 `mbedtls_pk_parse_public_key` 解析,别用 `mbedtls_ecp_point_read_binary` 去喂 PEM 文件(那是裸 65 字节非压缩点格式)。

5.3 接回 OTA_02 的 Bootloader(关键一步)

把验签放在 app_is_valid 之后、jump_to_app 之前:

c 复制代码
if (app_is_valid(APP_ADDR)) {
    if (verify_firmware((uint8_t *)APP_ADDR, app_size,
                        sig_addr, sig_len,
                        PUBKEY, PUBKEY_LEN) != 0) {
        // 验签失败:绝不下跳!停在 bootloader 等重传合法固件
        while (1) { LED_TOGGLE(); HAL_Delay(500); }
    }
    jump_to_app(APP_ADDR);   // 合法才跳
}

对 OTA_01 的 ESP32:要么在 esp_ota_end 后自己加一次 esp_ota_ops + 验签,要么直接开启 ESP-IDF 的 Secure Bootespsecure.py 签名 + eFuse 烧公钥摘要),ROM 层面帮你验,比 DIY 更稳。
实操部分以 ESP-IDF 安全启动文档、mbedTLS 文档和 ST 的应用笔记(AN4992 / AN4657)为准。


六、公钥存哪?这是安全的最关键一环

验签安不安全,取决于公钥能不能被攻击者替换

存放位置 攻击者能否改 结论
普通 Flash(和应用同片) ❌ 攻击者可同时换公钥+恶意固件,签名形同虚设
OTP / eFuse(一次性烧写) 不能(烧死后不可改) ✅ 正确做法
ROM(芯片出厂固化) 绝不可能 ✅ 最强(平台自带安全启动)

结论 :公钥必须进 eFuse / OTP / 或用更高一级的密钥签名后锁死 。配合 STM32 的 RDP 读保护 + WRP 写保护 + PCROP 代码保护,把 Bootloader 和密钥区锁成"读不出、改不了"。


七、各平台自带方案(优先用官方的,别自己造)

  • ESP32CONFIG_SECURE_BOOT + eFuse 烧公钥摘要。Bootloader 和 app 都用 espsecure.py 签名,ROM 逐级验。直接开这个,比手搓可靠得多
  • STM32:部分系列(L5/U5/H5 带 TrustZone-M,或支持 SFI 安全固件安装)有官方安全启动;通用 F1/F4 用"自建验签 + RDP/WRP/PCROP 提门槛"。注意全功能安全启动不是所有线都原生支持。
  • nRF(Nordic) :ARM TrustZone-M + CryptoCell,或 MCUboot + imgtool 签名验签,生态成熟。

经验法则:能开平台官方 Secure Boot 就开,不开才自建验签。自建只解决"验签逻辑",平台方案还顺带把密钥锁 eFuse、防回滚、防调试一锅端了。


八、新手必踩的 6 个坑

# 后果 正确做法
1 公钥放可被改写的普通 Flash 攻击者可换公钥+恶意固件,签名失效 公钥进 eFuse/OTP,并锁区
2 验签失败只"警告"不拒绝 恶意固件照样跑 验签非 0 必须硬拒绝、不下跳
3 私钥进代码仓库/泄露 等于没签名,谁都能签 私钥只在 CI/离线机,绝不进 git;泄露即轮换
4 自己手搓加密算法 侧信道/实现漏洞被破 用 mbedTLS / wolfSSL / tinycrypt
5 混淆签名与加密 固件内容仍可被抓包反编译 要保密另加 AES(密钥也进 eFuse)
6 只验签不防回滚 攻击者可降级到你旧漏洞版本 固件带单调版本号,设备拒跑更旧版本

第 6 条最容易被忽略:签名只证明"是你签的",不证明"是最新的" 。攻击者拿你去年有漏洞的旧版本(合法签名)刷进去,设备照样认。所以固件里必须嵌版本号,设备用单调计数器(eFuse)记住"已接受的最高版本",更低的一律拒。


九、动手练一练

  1. 用上面的 OpenSSL 命令生成 P-256 密钥对,对你编译出的 firmware.bin 签名,得到 firmware.bin.sig
  2. openssl dgst -sha256 -verify 验证:合法固件输出 Verification OK
  3. 故意把 firmware.bin 改 1 个字节 (哪怕只是版本字符串),再验签 → 必须 Verification Failure。亲手感受"改一点就废"。
  4. verify_firmware() 接到 OTA_02 的 jump 前,验签失败的设备应当停在 bootloader 闪灯,绝不启动恶意固件。
  5. 讨论题:把公钥放"普通 Flash" vs "eFuse",攻击者分别要多大代价才能得手?这步想通,你就懂了安全启动为什么强调"密钥锁死"。

小结

没签名的 OTA 是开了门没上锁。固件签名用非对称加密 实现"只有你能签、谁都伪造不了":构建时用私钥对固件哈希盖章,设备用烧死在 eFuse 里的公钥验章,验不过就拒绝运行。记住三件事------公钥必须锁死在 eFuse/OTP(不能放普通 Flash)、验签失败必须硬拒绝、签名之外还要防回滚。平台自带 Secure Boot 优先开,自建验签只是兜底。


系列收官预告:OTA_05_量产落地 Checklist ------ 版本号 / 防回滚 / 灰度发布 / 断点续传,把前面四篇串成一份真正能上线的大规模 OTA 方案清单。

下一篇预告:《量产落地 Checklist ------ 版本号 / 防回滚 / 灰度 / 断点续传》

相关推荐
LCMICRO-133108477461 小时前
国产长芯微LP8628完全P2P替代AD8628,零漂移、单电源、输入输出轨到轨高精度运放
arm开发·嵌入式硬件·fpga开发·硬件工程·精密运放·ad8628
学计算机的计算基1 小时前
TCP 传输层硬核整理:三次握手、四次挥手、拥塞控制一次讲透
java·网络·笔记·网络协议·算法
DevHub2 小时前
电视盒子刷 Armbian 教程:30 元旧盒子变 NAS 跑 Docker,200+ 机型可刷
linux·嵌入式硬件·docker·容器·开源·电视盒子
砚凝霜2 小时前
软考网络工程师|案例分析:华为全套综合实验核心知识点 & 考点总结
网络·华为
小新讲网安4 小时前
WiFi安全攻防实战:WPA3新协议与传统破解技术全解析
开发语言·网络·安全·php·漏洞·nmap·漏洞检测
wuyk55510 小时前
4.树:一对多的层次数据结构
开发语言·数据结构·stm32·单片机
清水白石00810 小时前
Python 死锁排查全攻略:从线程卡死到锁依赖定位与工程化修复
linux·网络·python
TechWayfarer11 小时前
批量查IP归属地,在线API、脚本、离线库怎么选?三种方案实测对比
网络·python·网络协议·tcp/ip
天空'之城12 小时前
单片机基础核心知识点汇总(五)
单片机·嵌入式硬件