4、固件准备与校验:固件编译与Hex/Bin格式、固件CRC校验、固件签名与加密

固件准备,说白了就是给芯片“喂”对的东西。我见过太多产线翻车,最后发现是固件本身出了问题——要么格式不对,要么校验码算错了,要么压根没加密就被抄了。这一章,咱们就把这些坑一个一个填上。

4.1 固件编译:从源码到机器码

你写好的C代码,编译器会把它变成两种常见格式:Hex和Bin。很多人觉得这俩差不多,其实差别大了去了。

特性 Hex(Intel HEX) Bin(Binary)
存储内容 ASCII文本,含地址、数据、校验 纯二进制数据,无地址信息
文件大小 约为Bin的2~3倍 紧凑,与Flash容量一致
地址信息 每行都带起始地址 无,需烧录器指定基址
烧录速度 较慢(解析开销) 快,直接按地址写入
适用场景 调试、分段烧录 量产、整片烧录
我的习惯:量产时统一用Bin格式。Hex虽然带校验,但解析器一旦有bug,烧进去的地址可能全错。Bin文件配合外部CRC校验,更可控。

编译时还有个小细节——优化等级。我踩过这个坑:开发时用-O0,量产时改成-Os,结果某个延时函数被优化掉了,整批板子跑飞。所以,量产固件必须用最终确定的编译选项重新编译,不能拿开发版的Hex直接上。

4.2 固件CRC校验:给固件上“指纹”

CRC(循环冗余校验)是量产烧录中最常用的校验手段。它不像MD5或SHA那么重,但足够检测出传输或存储中的单比特错误。

我个人习惯用CRC32,多项式0x04C11DB7。为什么?因为大多数MCU硬件都支持这个多项式,软件算起来也快。

来看一个典型的CRC计算流程:

// 伪代码:计算固件CRC32
uint32_t crc32_calculate(uint8_t *data, uint32_t len) {
    uint32_t crc = 0xFFFFFFFF;
    for (uint32_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (int j = 0; j < 8; j++) {
            if (crc & 1) {
                crc = (crc >> 1) ^ 0xEDB88320;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc ^ 0xFFFFFFFF;
}
注意:CRC计算时,一定要把固件末尾的填充字节(比如0xFF或0x00)也算进去。我曾经因为没算填充字节,导致烧录器校验通过,但芯片上电后CRC自检失败——因为Flash里实际存了填充数据。

量产时,我通常把CRC值放在固件的固定偏移位置(比如最后4字节)。烧录器烧完固件后,重新读回整个Flash区域,计算CRC,跟固件里预存的CRC比对。这叫“读回校验”,比只校验烧录过程可靠得多。

4.3 固件签名与加密:防抄板、防篡改

如果你的产品卖得好,一定会有人抄。固件签名和加密,就是最后一道防线。

4.3.1 固件签名

签名的作用是验证固件来源和完整性。流程是这样的:

  1. 编译固件,得到Bin文件。
  2. 用私钥对固件哈希(比如SHA256)进行签名。
  3. 将签名附加到固件末尾。
  4. 芯片Bootloader用公钥验证签名。

这里有个关键点:私钥必须离线保存。我见过有人把私钥放在编译服务器上,结果服务器被黑,所有固件都能被伪造签名。正确的做法是:用HSM(硬件安全模块)或离线签名机,每次签名都手动操作。

避坑指南:我曾经在量产时发现,某批芯片的Bootloader公钥被误刷成了测试版本。结果所有固件签名验证都失败,整批板子返工。从那以后,我要求产线在烧录Bootloader后,必须执行一次“自签名验证”——用芯片自己的公钥验证一个已知的测试签名,通过才放行。

4.3.2 固件加密

加密是为了防止别人直接读Flash就能拿到你的代码。常见方案有两种:

方案 原理 优点 缺点
对称加密(AES) 烧录时用密钥加密,芯片内部解密 速度快,适合量产 密钥需安全存储
非对称加密(RSA/ECC) 用公钥加密对称密钥,芯片用私钥解密 密钥分发安全 计算量大,Bootloader复杂

量产时,我推荐AES-128-CTR模式。CTR模式可以并行解密,不影响启动速度。密钥可以烧录在芯片的OTP(一次性可编程)区域,或者由芯片唯一ID派生。

一个小技巧:加密时不要把整个固件都加密。可以把Bootloader部分明文保留,只加密应用程序区。这样Bootloader可以正常启动,再解密应用代码。既保证了安全,又降低了复杂度。

4.4 固件准备与校验流程总图

下面这张图,把整个流程串起来了。你可以把它贴在工位上,每次准备固件时对照检查。

固件准备与校验流程 1. 源码编译 2. 生成Hex/Bin 3. 计算CRC32 4. 私钥签名 5. AES加密 6. 打包固件 7. 产线烧录 关键检查点 • 编译选项与量产一致 • CRC包含填充字节 • 私钥离线保存 产线验证项 • 读回CRC比对 • 签名验证测试 • 解密后功能自检

4.5 量产中的常见坑与对策

最后,分享几个我在产线上真实踩过的坑:

  • 坑1:CRC算法不一致。 烧录器用软件算CRC,芯片用硬件算CRC,结果两个结果不一样。对策:统一用同一个CRC库,并在烧录前做一次交叉验证。
  • 坑2:签名长度没算对。 签名附加到固件末尾后,总长度变了,导致CRC校验失败。对策:先签名,再计算包含签名的CRC。
  • 坑3:加密密钥写死在代码里。 有人把AES密钥直接写在源码中,结果固件被反编译后密钥泄露。对策:密钥由芯片唯一ID派生,或者从OTP区域读取。
  • 坑4:产线烧录器不支持加密。 有些老式烧录器只能烧明文Bin,加密固件需要先解密再烧。对策:在烧录器前加一个“解密网关”,或者升级烧录器固件。
血的教训:我曾经有一批产品,固件签名和加密都做了,但忘了在Bootloader里加入“回退保护”。结果升级失败后,芯片无法回退到旧版本,整批变砖。从那以后,我每次都会在固件头里加一个版本号字段,Bootloader根据版本号决定是否允许回退。