4、固件签名与打包:imgtool工具使用、生成签名固件、固件版本管理、固件元数据(TLV)结构解析

好,咱们进入第四章。这一章讲的是固件签名与打包,说白了就是给你的固件「上锁」和「贴标签」。我在做OTA项目时,早期吃过没签名的亏——设备被刷了第三方固件,直接变砖。从那以后,我对签名这件事就特别较真。

4.1 imgtool工具:你的固件「身份证」制作器

imgtool是MCUboot官方提供的命令行工具。它负责两件事:给固件签名,以及打包成MCUboot能识别的格式。我个人习惯把它叫做「固件身份证制作器」。

安装很简单,用pip就能搞定:

pip install imgtool

装好后,你可以用 imgtool version 检查是否成功。嗯,这里要注意,Python版本建议3.8以上,我遇到过3.6版本下某些TLV字段解析异常的问题。

4.2 生成签名固件:三步走

签名固件的生成,我总结为三步:准备密钥、签名、验证。咱们一步步来。

4.2.1 准备密钥

首先,你得有一对公私钥。私钥自己保管,公钥要烧到设备里。生成密钥的命令:

imgtool keygen -k mykey.pem -t rsa-2048

这里我用了RSA-2048,你也可以选ECDSA-P256。我个人更倾向ECDSA,因为签名短,省空间。但有些客户指定要RSA,那就得用RSA。

警告:私钥文件一定要妥善保管!我曾经有个同事把私钥传到了Git仓库,结果整个产品线都得换密钥。别问我怎么知道的。

4.2.2 签名固件

有了密钥,就可以签名了。假设你编译好的固件叫 app.bin

imgtool sign \
  --key mykey.pem \
  --align 8 \
  --version 1.2.3 \
  --slot-size 0x100000 \
  --max-align 8 \
  --header-size 0x200 \
  app.bin signed_app.bin

参数说明:

  • --align 8:对齐方式,通常和Flash擦除块大小一致
  • --version:固件版本号,这个很重要,后面会细说
  • --slot-size:每个slot的大小,必须和实际Flash分区一致
  • --header-size:头部大小,MCUboot用它来存放元数据
提示:如果你不确定对齐值,可以先用 --align 1 试试。但生产环境我建议用8或16,性能更好。

4.2.3 验证签名

签名完,记得验证一下:

imgtool verify signed_app.bin

这个命令会检查签名是否有效,以及TLV结构是否完整。我每次打包完都会跑一遍,确保万无一失。

4.3 固件版本管理:别让版本号成为你的噩梦

版本管理看似简单,但坑不少。MCUboot的版本号格式是 major.minor.patch+0,比如 1.2.3+0。后面的 +0 是build号,可以递增。

为什么版本号重要?因为MCUboot升级时,会检查新固件的版本是否高于当前版本。如果版本号一样或更低,它就不升级。我曾经在测试时,忘了改版本号,结果刷了十几次都没成功,还以为是bug。

我建议的版本管理策略:

  • 开发阶段:用 0.x.x,每次提交递增patch
  • 内部测试:用 1.x.x,每次测试递增build号
  • 正式发布:用 2.x.x 及以上,严格遵循语义化版本
重要:版本号是写在固件头部的,不是文件名。所以即使你改了文件名,MCUboot读到的还是内部版本号。别问我怎么知道的,问就是吃过亏。

4.4 固件元数据(TLV)结构解析

TLV是Type-Length-Value的缩写。MCUboot用它来存储签名、哈希、版本等元数据。说白了,就是在固件后面附加一些「小纸条」。

TLV结构长这样:

字段 大小 说明
Type 2字节 标识数据类型,比如0x01表示签名
Length 2字节 Value的长度
Value 可变 实际数据

常见的TLV类型:

  • 0x01:SHA256哈希
  • 0x02:RSA签名
  • 0x03:ECDSA签名
  • 0x10:固件版本号
  • 0x11:固件大小

你可以用 imgtool dump 查看固件的TLV结构:

imgtool dump signed_app.bin

输出大概是这样:

TLV block at offset 0x100200:
  Type: 0x01 (SHA256) Length: 32
  Type: 0x02 (RSA2048) Length: 256
  Type: 0x10 (VERSION) Length: 8

你想想看,MCUboot启动时,会先读取固件头部的TLV区域,验证签名和哈希。如果签名不对,直接拒绝启动。这就是安全启动的核心机制。

经验之谈:如果你自己写工具解析TLV,注意字节序。MCUboot默认是小端,但有些工具是大端。我踩过这个坑,解析出来的版本号是反的。

4.5 实战:完整的签名打包流程

最后,我分享一个我常用的脚本片段。它把编译、签名、打包串起来:

#!/bin/bash
# 编译固件
west build -b nrf52840dk_nrf52840 app
# 签名
imgtool sign \
  --key private.pem \
  --version 2.0.1 \
  --slot-size 0x100000 \
  --header-size 0x200 \
  build/zephyr/zephyr.bin \
  build/signed_firmware.bin
# 验证
imgtool verify build/signed_firmware.bin
echo "签名固件已生成:build/signed_firmware.bin"

这个脚本我用了两年,基本没出过问题。唯一要注意的是,每次发布前记得更新版本号。

好,这一章就到这里。下一章我们会讲固件加密与安全启动,到时候会用到今天签名的知识。记得把密钥保管好,别像我那个同事一样。