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:这是升级包的「大脑」。它是一个脚本文件,定义了升级的每一步操作——挂载分区、写入数据、设置权限、更新引导等等。
我的小技巧: 在开发阶段,我习惯在 updater-script 里加一些日志输出语句。比如 ui_print("正在更新 system 分区...")。这样调试时能清楚看到执行到哪一步卡住了。别小看这个,我曾经靠它定位过一个诡异的「升级到 47% 必重启」的 bug。

3.2 签名与校验机制:给升级包上把锁

为什么要签名?说白了,就是防止有人伪造升级包往你设备里塞恶意代码。你想想看,如果 OTA 升级没有签名校验,黑客随便做个假包就能控制所有设备,那还得了?

鸿蒙的签名机制,我总结为三步走:

  1. 生成密钥对:用 RSA 算法生成一对公私钥。私钥你得像保护银行卡密码一样藏好,公钥则预置在设备的 Bootloader 里。
  2. 签名过程:对升级包的内容(通常是 MANIFEST.MF 的哈希值)用私钥加密,生成签名文件 CERT.SF。然后把公钥证书 CERT.RSA 也打包进去。
  3. 校验过程:设备收到升级包后,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-beta13.2.0-rc2。这些版本只能用于内测,正式推送时要去掉后缀。

特别注意: 版本号一旦发布,就不可更改。你想想看,如果线上设备已经升级到了 3.1.5,你突然把 3.1.5 的包内容改了,那之前升级过的设备再收到这个包会怎样?它会认为版本相同,跳过升级。但实际内容已经变了,这就造成了「版本号相同但内容不同」的混乱局面。所以,每个版本号只能对应一个唯一的升级包

在实际项目中,我建议把版本号管理纳入 CI/CD 流程。每次构建自动生成版本号,比如用 Git 的 tag 或者构建时间戳。我习惯用 主版本.次版本.修订号-构建号 的格式,比如 3.1.5-20240315。这样既能体现语义版本,又能追溯构建时间。

3.4 小结:升级包制作的三个铁律

好,这一章的内容就这些。我帮你总结三个必须记住的铁律:

  1. 结构要规范:META-INF、patch、firmware、verify、updater-script,一个都不能少。少一个,升级就可能失败。
  2. 签名不能省:私钥藏好,公钥预置,校验必开。这是设备安全的最后一道防线。
  3. 版本要严谨:三段式、只升不降、唯一对应。版本号乱,升级就乱。

下一章,我们会深入 updater-script 的语法,教你如何编写一个健壮的升级脚本。到时候我会分享一个我踩过的坑——脚本里一个路径写错,导致整个分区被清空。嗯,那故事可精彩了。