升级包制作:差分升级原理、全量升级包制作、升级包压缩与加密
好,咱们进入第三章。升级包制作,这活儿看着简单,其实坑不少。我见过太多团队,升级流程画得漂漂亮亮,结果一到生成升级包就翻车——要么包太大,要么解压失败,要么签名校验过不去。今天咱们就把这块掰开揉碎讲清楚。
差分升级原理
先说说差分升级。说白了,就是只传输新旧版本之间的差异部分。你想想看,一个固件可能 10MB,每次升级都传 10MB,那用户流量扛不住,服务器带宽也扛不住。差分升级就能把这个量降到几百 KB。
原理其实不复杂。我们用 bsdiff 这类工具,对比旧固件和新固件,生成一个 patch 文件。设备端拿到 patch 后,结合本地旧固件,就能还原出新固件。
我在项目中遇到过一个问题:差分升级对旧固件的完整性要求极高。旧固件哪怕有一个 bit 不对,还原出来的新固件就是坏的。所以差分升级前,一定要校验旧固件的哈希值。
核心要点:差分升级节省流量,但依赖旧固件完整性。建议在设备端保存旧固件的 SHA256 值,升级前先校验。
差分升级的算法选择上,我个人习惯用 bsdiff。它生成的 patch 比较小,但内存消耗大。如果设备内存紧张,可以考虑 hdiffpatch 或者 xdelta3。嗯,这里要注意:内存小于 256KB 的 MCU,就别折腾差分升级了,老老实实用全量包吧。
全量升级包制作
全量升级包就简单多了。就是把新固件整个打包,加上一些元数据。但简单归简单,格式设计上还是有不少讲究。
我一般会这样组织全量升级包的结构:
升级包结构:
+------------------+
| 包头 (64字节) | ← 魔数、版本号、固件大小、校验算法等
+------------------+
| 固件数据 | ← 实际固件二进制
+------------------+
| 签名数据 (256字节)| ← ECDSA 或 RSA 签名
+------------------+
| 尾部标记 (4字节) | ← 0xDEADBEEF 之类的结束标记
+------------------+
包头里我建议至少包含这些字段:
| 字段 | 大小 | 说明 |
|---|---|---|
| 魔数 | 4 字节 | 用于识别升级包格式,比如 0x55475200 |
| 版本号 | 4 字节 | 主版本+次版本+修订号 |
| 固件大小 | 4 字节 | 固件数据的实际长度 |
| 哈希算法 | 1 字节 | 0x01=SHA256, 0x02=SHA384 |
| 固件哈希 | 32 字节 | 固件数据的哈希值 |
| 签名算法 | 1 字节 | 0x01=ECDSA, 0x02=RSA2048 |
| 保留字段 | 18 字节 | 留给未来扩展 |
我曾经踩过一个坑:包头里的固件大小字段写错了,导致设备端只读取了部分固件就以为升级完成了。从那以后,我强制要求:设备端必须用固件哈希来校验完整性,而不是只看大小。
升级包压缩与加密
压缩和加密,这两个经常被混为一谈。其实它们是两回事。压缩是为了减小体积,加密是为了防止泄密。我建议先压缩再加密,这样加密的数据量更小,效率更高。
压缩算法我推荐用 zlib 或者 lzma。zlib 解压速度快,适合资源受限的设备。lzma 压缩率更高,但解压需要更多内存。怎么选?看你的设备硬件资源。
实战技巧:如果设备 Flash 空间紧张,可以尝试用 lzma 的预设等级 1-3,压缩率不错,解压内存需求也低。我有个项目就是用 lzma 等级 2,把 2MB 的固件压到了 600KB。
加密这块,我强烈建议用 AES-128-GCM。为什么是 GCM 模式?因为它同时提供加密和认证,能防止篡改。CBC 模式虽然也能加密,但不防篡改,攻击者可以翻转密文比特来破坏固件。
加密密钥怎么管理?这是个大学问。简单说几个原则:
- 密钥不能硬编码在固件里。我见过有人把 AES 密钥写在代码里,结果固件被反编译后密钥直接暴露。
- 建议用设备唯一密钥,每台设备不同。可以用设备序列号派生密钥。
- 密钥存储要使用硬件安全模块(HSM)或安全元件(SE),没有的话至少用芯片的 eFuse 或 OTP。
警告:千万不要自己实现加密算法!用现成的库,比如 mbedTLS、OpenSSL 或者芯片厂商提供的加密库。自己写的加密算法,十有八九有漏洞。
最后说说压缩加密的流程。我一般这样操作:
- 生成新固件二进制文件
- 计算固件的 SHA256 哈希
- 用 zlib 压缩固件数据
- 用 AES-128-GCM 加密压缩后的数据
- 组装包头(包含哈希、加密参数等)
- 对整个包进行签名(ECDSA 或 RSA)
- 添加尾部标记
设备端解包时,顺序是反过来的:先验证签名,再解密,再解压,最后校验哈希。签名验证一定要放在第一步,这样可以尽早发现篡改,避免后续的解密解压操作浪费资源。
我记得有一次,客户反馈说升级包在传输过程中损坏了。查了半天,发现是他们的打包工具没有做完整性校验。从那以后,我每个升级包都会在尾部加一个 CRC32 校验,虽然简单,但能快速发现传输错误。
好了,升级包制作这块就讲到这里。下一章咱们聊聊升级过程中的安全校验,那才是真正的重头戏。