2. 升级包制作与签名:升级包结构解析、差分升级包制作、升级包签名与校验机制
好,咱们进入第二章。这一章聊的是OTA升级里最核心的环节——升级包的制作与签名。说实话,很多刚入行的朋友觉得这步就是打个包、签个名,没什么技术含量。但我在项目里踩过的坑告诉我,这恰恰是决定升级成败的关键一步。
2.1 升级包结构解析
先说说升级包长什么样。你想想看,一个完整的升级包,它其实就是一个精心设计的容器。我习惯把它拆成三个部分:头部、元数据和载荷。
| 组成部分 | 内容说明 | 我踩过的坑 |
|---|---|---|
| 头部(Header) | 魔数、版本号、包类型、载荷大小、校验算法标识 | 魔数写错,设备直接不认包 |
| 元数据(Metadata) | 目标设备型号、硬件版本、依赖关系、升级策略 | 型号匹配漏了,刷成砖头 |
| 载荷(Payload) | 实际固件数据或差分补丁 | 压缩率没算好,包太大传不下去 |
头部里那个魔数,说白了就是个身份标识。我记得有一次,团队里新来的同事把魔数写成了0xDEADBEEF,结果设备端校验时发现不对,直接拒绝升级。嗯,从那以后我要求所有魔数必须写在设计文档里,谁改谁负责。
元数据这块,我个人习惯把设备型号和硬件版本写死在里面。为什么?因为有一次我在现场升级,发现同一批设备硬件版本有差异,结果升级包不兼容,差点把产线搞停。从那以后,元数据里必须包含完整的兼容性列表。
2.2 差分升级包制作
差分升级,说白了就是只传变化的部分。你想想看,一个固件动辄几十兆,如果每次升级都全量传输,那网络压力太大了。尤其是物联网设备,很多还在用2G网络,传个大包简直是噩梦。
差分升级的原理其实不复杂。我常用的工具是bsdiff和hdiffpatch。它们的工作流程是这样的:
- 拿旧固件和新固件做对比,找出差异
- 生成一个补丁文件,里面只包含变化的数据
- 设备端拿到补丁后,和本地旧固件合并,生成新固件
这里有个关键点——差分算法对内存要求很高。我在一个资源受限的MCU项目上吃过亏。当时用bsdiff生成的补丁只有几百KB,但设备端解压时需要几十MB的临时内存。结果呢?设备直接内存溢出重启了。
差分升级包的制作命令,我一般这么写:
# 生成差分补丁
bsdiff old_firmware.bin new_firmware.bin patch.bin
# 设备端合并
bspatch old_firmware.bin new_firmware.bin patch.bin
嗯,这里要注意,bsdiff对二进制文件效果很好,但如果你的固件是压缩过的,那差分效果会大打折扣。为什么?因为压缩后的数据变化一点,整个块都会变。我建议在制作差分包前,先解压固件,再做差分。
2.3 升级包签名与校验机制
签名,这是整个升级流程里最不能省的一步。你想想看,如果升级包没有签名,那黑客可以随便伪造一个恶意固件,远程刷到你的设备上。后果?轻则设备变砖,重则整个网络被控制。
我常用的签名方案是RSA或ECDSA。流程是这样的:
- 制作方用私钥对升级包的哈希值进行签名
- 签名和升级包一起下发
- 设备端用公钥验证签名
这里有个细节——签名的是哈希值,不是整个包。为什么?因为签名算法计算量大,对整个包签名太慢了。我一般用SHA-256算哈希,再用RSA-2048签名。
签名验证的代码,我一般这么写:
// 设备端验证签名
int verify_signature(const uint8_t* package, size_t pkg_len,
const uint8_t* signature, size_t sig_len) {
uint8_t hash[SHA256_DIGEST_LENGTH];
sha256(package, pkg_len, hash);
// 用公钥验证签名
return rsa_verify(public_key, hash, sizeof(hash),
signature, sig_len);
}
校验机制这块,我建议做双重校验:
- 第一层: 签名验证。确保包是官方发布的,没有被篡改。
- 第二层: 完整性校验。用CRC32或SHA-256检查包在传输过程中有没有损坏。
我记得有一次,设备在升级过程中网络断了一下,结果包只传了一半。如果没有完整性校验,设备就会拿半截数据去升级,结果可想而知。所以,我要求设备端在接收完整个包后,先算哈希,再验证签名,最后才执行升级。
最后说一句,签名和校验不是一劳永逸的。公钥如果泄露了,整个信任链就断了。我建议在设备出厂时烧录公钥,并且提供远程更新公钥的能力。当然,更新公钥本身也需要签名验证,这就形成了一个信任链的闭环。
嗯,这一章的内容就到这里。下一章我们会聊升级包的传输协议和断点续传,那又是另一番天地了。