3. 升级包制作与签名:差分算法、压缩与安全机制
升级包怎么做?说白了就三步:算出差分、打成压缩包、签上名。每一步都有坑,我一个个讲。
3.1 差分算法:bsdiff vs hdiffpatch
差分算法的核心目标只有一个——让升级包尽可能小。你想想看,一个 100MB 的固件,如果每次都要全量下载,用户流量扛不住,服务器带宽也扛不住。
我个人习惯在两种算法里选:bsdiff 和 hdiffpatch。
| 对比项 | bsdiff | hdiffpatch |
|---|---|---|
| 算法原理 | 基于后缀排序 + 二分匹配 | 基于哈希 + 滑动窗口 |
| 差分包大小 | 通常更小(5%~15%) | 稍大(10%~20%) |
| 内存占用 | 高(约原固件 5 倍) | 低(约原固件 1.5 倍) |
| 生成速度 | 慢(大文件可能数分钟) | 快(秒级) |
| 适用场景 | 服务器端生成,资源充足 | 嵌入式端生成或资源受限环境 |
我曾经在一个智能家居项目里踩过坑。当时用 bsdiff 给一个 80MB 的摄像头固件做差分,生成时内存飙到 400MB,直接把构建服务器搞 OOM 了。后来加了 --memlimit 256MB 参数才解决。
bsdiff 的基本用法:
# 生成差分包
bsdiff old_firmware.bin new_firmware.bin upgrade.patch
# 应用差分包
bspatch old_firmware.bin new_firmware.bin upgrade.patch
hdiffpatch 的用法类似:
# 生成差分包
hdiffz -s old_firmware.bin -d new_firmware.bin -o upgrade.patch
# 应用差分包
hpatchz -s old_firmware.bin -d new_firmware.bin -o upgrade.patch
3.2 升级包压缩:选对格式,省一半流量
差分包生成后,还要再压缩一层。为什么?因为差分算法只处理二进制差异,不负责通用压缩。再压一层,包体还能再小 20%~40%。
常用的压缩格式就三种:
- gzip:兼容性最好,几乎所有嵌入式系统都支持。压缩率中等。
- lzma / xz:压缩率最高,但解压需要更多内存和 CPU。适合资源充足的设备。
- zstd:我最近两年最常用的。压缩率接近 lzma,但解压速度是 gzip 的 3~5 倍。
来看一组实测数据(基于一个 32MB 的差分包):
| 压缩格式 | 压缩后大小 | 压缩耗时 | 解压耗时(嵌入式 ARM Cortex-A7) |
|---|---|---|---|
| gzip -6 | 18.2 MB | 2.1s | 1.8s |
| lzma -6 | 14.5 MB | 12.4s | 4.2s |
| zstd -6 | 15.1 MB | 1.5s | 0.9s |
压缩命令示例:
# zstd 压缩
zstd -6 upgrade.patch -o upgrade.patch.zst
# zstd 解压
zstd -d upgrade.patch.zst -o upgrade.patch
3.3 数字签名与验签机制:防篡改的最后一道防线
升级包在传输过程中可能被篡改。你想想看,如果攻击者替换了你的升级包,设备刷了恶意固件,后果是什么?
所以,签名是必须的。我常用的方案是 ECDSA(椭圆曲线数字签名算法),相比 RSA,它的签名更短、验签更快。
签名流程:
- 对升级包(压缩后的差分包)计算哈希值,比如 SHA-256。
- 用私钥对哈希值签名,生成签名文件。
- 将签名文件与升级包一起打包分发。
验签流程:
- 设备收到升级包后,先计算本地哈希值。
- 用公钥解密签名,得到原始哈希值。
- 对比两个哈希值,一致则通过,否则拒绝升级。
我曾经在一个 IoT 项目里发现,有些设备因为 RTC 电池没电,系统时间回到了 1970 年,导致证书验证失败。后来我改成了 固定公钥硬编码 + 签名验签,不再依赖证书链,问题就解决了。
签名命令示例(使用 OpenSSL + ECDSA):
# 生成私钥
openssl ecparam -genkey -name prime256v1 -out private.pem
# 提取公钥
openssl ec -in private.pem -pubout -out public.pem
# 对升级包签名
openssl dgst -sha256 -sign private.pem -out upgrade.patch.sig upgrade.patch.zst
# 验签
openssl dgst -sha256 -verify public.pem -signature upgrade.patch.sig upgrade.patch.zst
3.4 升级包版本管理:别让设备升错级
版本管理看起来简单,但做不好会出大问题。我记得有一次,一个设备从 v2.0 直接升到 v3.0,跳过了 v2.5 的中间版本,结果因为数据库 schema 不兼容,设备直接变砖。
所以,升级包必须包含完整的版本信息。我一般用 JSON 格式的元数据文件:
{
"package_version": "3.0.0",
"target_version": "3.0.0",
"min_supported_version": "2.5.0",
"max_supported_version": "2.9.9",
"checksum": "sha256:abcdef...",
"signature": "base64:xxxx...",
"release_date": "2025-03-15",
"hardware_compatibility": ["HW_REV_A", "HW_REV_B"]
}
版本管理的几个原则:
- 语义化版本:主版本.次版本.修订号。主版本不兼容时,必须全量升级。
- 版本范围限制:每个差分包只适用于特定版本区间。超出范围就拒绝。
- 硬件兼容性检查:同一款产品可能有不同硬件版本,升级包必须标明兼容的硬件。
- 回滚保护:升级成功后,保存旧版本号。如果新版本有问题,可以回滚。
3.5 本章知识体系
下面这张图概括了升级包制作与签名的完整流程:
整个流程环环相扣。差分算法决定了包大小,压缩进一步优化传输效率,签名保证安全性,版本管理防止升级错乱。任何一个环节出问题,都可能导致升级失败甚至设备变砖。