3、升级包制作基础:升级包文件结构、签名与校验机制、版本号管理规范
好,咱们进入第三章。升级包制作,说白了就是给设备准备一份「可执行的更新说明书」。你想想看,OTA升级要是连包都做不对,后面全是白搭。我个人习惯把升级包比作一个「加密快递」——里面装着新系统,外面贴着防拆标签,还得有唯一的快递单号。
这一章,我们就来拆解这个快递。我会把文件结构、签名校验、版本号管理这三个核心点讲透。嗯,这里要注意,很多新手栽跟头就栽在这三个地方。
3.1 升级包的文件结构:一个压缩包里的乾坤
鸿蒙的升级包,本质上是一个经过特殊处理的 ZIP 文件。但别被「ZIP」骗了,它内部的结构是有严格规范的。
一个标准的升级包,解压后你会看到这样的目录:
update.zip
├── META-INF/
│ ├── MANIFEST.MF # 清单文件,记录所有文件的摘要
│ ├── CERT.RSA # 签名证书
│ └── CERT.SF # 签名文件
├── patch/
│ ├── system_patch.img # 系统分区增量补丁
│ ├── vendor_patch.img # 厂商分区增量补丁
│ └── boot_patch.img # 启动分区补丁
├── firmware/
│ ├── abl.elf # 底层固件
│ └── xbl.elf
├── verify/
│ └── hash_list.txt # 全量校验哈希列表
└── updater-script # 升级脚本(核心执行逻辑)
我来解释一下每个部分的作用:
- META-INF 目录:这是升级包的「身份证」。MANIFEST.MF 记录了包里每个文件的 SHA-256 哈希值,用来防篡改。CERT.RSA 和 CERT.SF 则是签名信息,后面会细讲。
- patch 目录:存放增量补丁。我建议你记住一个原则——增量包只包含差异部分。比如 system 分区从 v1 升级到 v2,我们只打包变化的数据块,而不是整个分区镜像。这样包体积能缩小 60%-80%。
- firmware 目录:存放底层固件。这部分很敏感,我在项目中遇到过因为固件版本不匹配导致设备变砖的情况。所以,固件升级一定要做版本兼容性检查。
- verify 目录:全量校验文件。升级完成后,系统会用这个列表里的哈希值去比对实际分区,确保每个字节都写对了。
- updater-script:这是升级包的「大脑」。它是一个脚本文件,定义了升级的每一步操作——挂载分区、写入数据、设置权限、更新引导等等。
ui_print("正在更新 system 分区...")。这样调试时能清楚看到执行到哪一步卡住了。别小看这个,我曾经靠它定位过一个诡异的「升级到 47% 必重启」的 bug。
3.2 签名与校验机制:给升级包上把锁
为什么要签名?说白了,就是防止有人伪造升级包往你设备里塞恶意代码。你想想看,如果 OTA 升级没有签名校验,黑客随便做个假包就能控制所有设备,那还得了?
鸿蒙的签名机制,我总结为三步走:
- 生成密钥对:用 RSA 算法生成一对公私钥。私钥你得像保护银行卡密码一样藏好,公钥则预置在设备的 Bootloader 里。
- 签名过程:对升级包的内容(通常是 MANIFEST.MF 的哈希值)用私钥加密,生成签名文件 CERT.SF。然后把公钥证书 CERT.RSA 也打包进去。
- 校验过程:设备收到升级包后,Bootloader 先用预置的公钥解密 CERT.SF,得到原始的哈希值。然后自己重新计算 MANIFEST.MF 的哈希,两者比对。一致,则通过;不一致,直接拒绝升级。
这里有个关键点——签名是在打包服务器上完成的,校验是在设备端完成的。私钥绝对不能出现在设备上,否则就失去了签名意义。
避坑指南: 我曾经犯过一个低级错误——在测试阶段为了方便,把签名校验给关掉了。结果有一次打包脚本出了问题,生成了一个损坏的升级包。因为没有校验,设备傻乎乎地刷了进去,直接变砖。后来花了整整两天才救回来。从那以后,我再也不敢跳过签名校验,哪怕是在开发环境。
签名算法的选择上,我建议用 RSA-2048 或 ECC-256。RSA-2048 兼容性好,几乎所有芯片都支持。ECC-256 性能更好,签名体积更小,适合对包大小敏感的场景。我个人偏向 ECC,因为鸿蒙原生支持得不错。
3.3 版本号管理规范:别让版本号成为灾难
版本号看似简单,但我在项目中见过太多因为版本号混乱导致的升级失败。你想想看,如果设备当前是 v2.0.1,服务器推送了一个 v2.0.1 的包,设备该不该升级?如果推送的是 v1.9.9,又该不该降级?
鸿蒙推荐使用 三段式版本号:主版本.次版本.修订号。比如 3.1.5。
| 字段 | 含义 | 变化场景 |
|---|---|---|
| 主版本 | 重大架构变更、不兼容的 API 改动 | 比如从 HarmonyOS 3 升级到 4 |
| 次版本 | 新功能添加、但保持向后兼容 | 比如从 3.1 升级到 3.2 |
| 修订号 | Bug 修复、安全补丁 | 比如从 3.1.5 升级到 3.1.6 |
版本号的比较规则也很重要:
- 只能升,不能降:设备固件默认禁止降级。这是为了防止安全漏洞被回滚利用。如果你确实需要降级(比如开发调试),得在 Bootloader 里关闭版本检查。
- 版本号必须单调递增:不能出现 3.1.5 -> 3.1.4 这种操作。我见过有人把修订号从 5 改回 4,结果设备死活不认。
- 预发布版本用后缀标识:比如
3.2.0-beta1、3.2.0-rc2。这些版本只能用于内测,正式推送时要去掉后缀。
特别注意: 版本号一旦发布,就不可更改。你想想看,如果线上设备已经升级到了 3.1.5,你突然把 3.1.5 的包内容改了,那之前升级过的设备再收到这个包会怎样?它会认为版本相同,跳过升级。但实际内容已经变了,这就造成了「版本号相同但内容不同」的混乱局面。所以,每个版本号只能对应一个唯一的升级包。
在实际项目中,我建议把版本号管理纳入 CI/CD 流程。每次构建自动生成版本号,比如用 Git 的 tag 或者构建时间戳。我习惯用 主版本.次版本.修订号-构建号 的格式,比如 3.1.5-20240315。这样既能体现语义版本,又能追溯构建时间。
3.4 小结:升级包制作的三个铁律
好,这一章的内容就这些。我帮你总结三个必须记住的铁律:
- 结构要规范:META-INF、patch、firmware、verify、updater-script,一个都不能少。少一个,升级就可能失败。
- 签名不能省:私钥藏好,公钥预置,校验必开。这是设备安全的最后一道防线。
- 版本要严谨:三段式、只升不降、唯一对应。版本号乱,升级就乱。
下一章,我们会深入 updater-script 的语法,教你如何编写一个健壮的升级脚本。到时候我会分享一个我踩过的坑——脚本里一个路径写错,导致整个分区被清空。嗯,那故事可精彩了。