3. 升级包制作与签名:全量包、差分算法与签名验签
升级包怎么做?说白了就两件事:一是把新系统打包成设备能认的格式,二是给这个包上锁,防止别人篡改。我做了这么多年OTA,见过太多因为包没打好或者签名没做对,导致设备变砖的案例。今天咱们就把这两个核心环节掰开揉碎了讲。
3.1 全量包制作流程
全量包,就是包含完整系统镜像的升级包。设备拿到它,不管当前是什么版本,都能直接刷成新版本。制作流程其实不复杂,但细节特别多。
我习惯把全量包制作分成三步:
- 准备镜像文件:把编译好的系统分区镜像(boot.img、system.img、vendor.img等)收集好。
- 生成文件列表:计算每个文件的哈希值,生成一个清单文件。
- 打包与签名:用工具把所有文件打包成一个zip,再签上名。
具体到Android系统,Google提供了ota_from_target_files这个工具。你给它一个target_files.zip,它就能吐出完整的OTA包。命令大概长这样:
./build/tools/releasetools/ota_from_target_files \
-k build/target/product/security/releasekey \
target_files.zip \
ota_update.zip
这里有个坑——-k参数指定的是签名密钥。如果你忘了加,生成的包就没签名,设备会拒绝安装。我曾经有一次在CI脚本里漏了这个参数,结果测试组反馈说包刷不进去,排查了半天才发现是签名问题。
unzip -l检查包内文件结构,再用openssl验证签名是否完整。别嫌麻烦,这一步能省下后面大量排障时间。
3.2 差分算法:bsdiff与imgdiff
全量包有个硬伤——太大。动辄几个GB的包,通过网络下发,用户流量扛不住,服务器带宽也扛不住。所以就有了差分升级。
差分升级的核心思想是:只下发新旧版本之间的差异部分。设备收到后,把差异应用到当前版本上,生成新版本。这里的关键就是差分算法。
3.2.1 bsdiff:二进制差分
bsdiff是目前最主流的二进制差分算法。它的原理说起来不复杂:
- 把旧文件和新文件都切成小块
- 用后缀排序算法找到新旧文件之间的最长公共子串
- 只记录新增、删除、修改的部分
bsdiff生成的差分包通常很小,压缩率很高。但它有个缺点——吃内存。处理一个几百MB的文件,内存占用可能飙到几个GB。我在嵌入式设备上遇到过内存不够导致差分失败的情况,后来改用bsdiff的--memory参数限制内存使用才解决。
3.2.2 imgdiff:针对镜像的优化
imgdiff是bsdiff的变种,专门针对Android的sparse image(稀疏镜像)做了优化。稀疏镜像里有很多空洞(全零区域),bsdiff会把这些空洞当成普通数据来处理,效率很低。imgdiff能识别空洞并跳过它们,生成的差分包更小,速度也更快。
在Android OTA中,ota_from_target_files会自动判断用bsdiff还是imgdiff。一般来说:
- 普通文件(如APK、jar)用bsdiff
- 分区镜像(如system.img)用imgdiff
但也不是绝对的。我记得有一次,一个客户的自定义分区镜像用了非标准的格式,imgdiff识别不了,自动回退到bsdiff,结果差分包大了3倍。后来我们手动指定了算法才解决。
3.2.3 差分算法对比
| 特性 | bsdiff | imgdiff |
|---|---|---|
| 适用场景 | 通用二进制文件 | 稀疏镜像文件 |
| 内存占用 | 高(可达文件大小10倍) | 中等 |
| 差分效率 | 文件变化小时效率高 | 对空洞优化,效率更高 |
| 生成速度 | 较慢 | 较快 |
| 适用系统 | 所有系统 | Android为主 |
3.3 签名验签机制
签名是OTA升级的安全基石。没有签名,攻击者可以伪造升级包,往设备里塞恶意代码。签名验签的流程其实不复杂,但每一步都不能出错。
3.3.1 签名流程
签名说白了就是给升级包盖个章。具体步骤:
- 计算升级包的哈希值(比如SHA-256)
- 用私钥对哈希值加密,生成签名
- 把签名和公钥证书一起打包进升级包
在Android中,签名工具是signapk.jar。命令示例:
java -jar signapk.jar \
-w platform.x509.pem \
platform.pk8 \
unsigned.zip \
signed.zip
这里platform.x509.pem是公钥证书,platform.pk8是私钥。这两个文件一定要保管好,尤其是私钥。我见过有团队把私钥传到Git仓库里,结果被泄露,整个产品线都得换密钥,那叫一个惨。
3.3.2 验签流程
设备收到升级包后,会做以下事情:
- 从包中提取签名和公钥证书
- 用公钥解密签名,得到哈希值A
- 自己计算升级包的哈希值B
- 比较A和B,一致则通过,否则拒绝安装
验签是在recovery模式下进行的。设备启动recovery后,会先验证升级包签名,再执行升级。如果签名验证失败,recovery会直接报错退出,不会动系统分区。
3.3.3 常见签名问题
我在项目中遇到过不少签名相关的问题,挑几个典型的说说:
- 密钥不匹配:设备里烧录的公钥和签名用的私钥不配对。这通常发生在开发阶段,测试机用的是testkey,但包签的是releasekey。
- 证书过期:公钥证书有有效期,过期后设备会拒绝验证。我建议证书有效期设长一点(比如10年),或者用自签名证书。
- 签名算法太弱:有些老设备还在用SHA-1,现在主流是SHA-256。如果服务器用SHA-256签名,设备只支持SHA-1,就会验签失败。
openssl verify手动验证一遍签名。
3.4 升级包制作与签名流程总览
下面这张图把全量包制作、差分生成、签名验签的完整流程串起来了。你可以把它当作一个检查清单,每次做升级包时对照着走一遍。
3.5 小结
升级包制作和签名,看起来是体力活,但每个环节都有坑。全量包要注意文件完整性和签名,差分算法要根据文件类型选对工具,签名验签要管好密钥。我个人的经验是:自动化流程+人工复核。把制作和签名写成脚本,每次发布前再手动检查一遍包结构和签名有效性。这样既能提高效率,又能避免低级错误。