13、OTA包签名:payload签名、metadata签名、公钥验证
说到OTA升级的安全性,签名验证是最后一道防线。我刚开始接触Android OTA时,总觉得签名就是个校验过程,没什么好讲的。直到有一次在项目中,因为签名验证失败导致整批设备变砖,我才意识到——嗯,这里面的门道比想象中深得多。
说白了,OTA包的签名机制就是为了回答三个问题:这个包是谁发的?内容有没有被篡改?我能不能信任它?今天我们就来拆解一下,Android是怎么通过payload签名、metadata签名和公钥验证来回答这三个问题的。
13.1 签名的整体架构
一个完整的OTA包,签名并不是只签一次就完事了。它分两层:
- payload签名:对实际的升级数据(payload.bin)进行签名
- metadata签名:对升级包的元数据(properties文件、manifest等)进行签名
这两层签名各自独立,又相互关联。我习惯把payload签名看作是"内容担保",metadata签名看作是"身份担保"。两者缺一不可。
核心逻辑:metadata中包含了payload的哈希值,所以只要metadata签名是有效的,payload的完整性也就得到了保证。这是一种"链式信任"的设计思路。
13.2 Payload签名详解
payload签名,说白了就是对升级数据本身做的签名。在A/B分区架构中,payload.bin可能非常大,几个GB都很常见。这么大的文件,直接签名显然不现实。
Android的做法是:先对payload计算哈希,再对这个哈希值签名。具体流程如下:
- 计算payload.bin的SHA-256哈希值
- 用私钥对这个哈希值进行RSA签名
- 将签名结果写入payload.bin的尾部(称为signature blob)
我的经验:我曾经遇到过一个问题,payload签名验证总是失败,排查了半天才发现是签名时用的哈希算法和验证时用的不一致。一个用了SHA-256,另一个用了SHA-512。这种低级错误,一旦踩坑就很难忘。
在代码层面,payload签名的生成和验证主要涉及以下几个关键步骤:
// 生成payload签名(服务端)
// 伪代码示例
PayloadSigner signer = new PayloadSigner(privateKey);
byte[] payloadHash = computeSha256(payloadBin);
byte[] signature = signer.sign(payloadHash);
// 将signature追加到payload.bin末尾
// 验证payload签名(设备端)
// 伪代码示例
PayloadVerifier verifier = new PayloadVerifier(publicKey);
byte[] extractedHash = extractHashFromPayload(payloadBin);
byte[] extractedSignature = extractSignatureFromPayload(payloadBin);
boolean isValid = verifier.verify(extractedHash, extractedSignature);
13.3 Metadata签名详解
metadata签名,很多人容易忽略它。其实它才是整个OTA包的"身份证"。metadata中包含了什么?
- 升级包的版本信息
- payload的哈希值
- 设备兼容性信息
- 升级策略(是否强制、是否回滚等)
你想想看,如果metadata被篡改了,哪怕payload本身是合法的,设备也可能升级到错误的版本。所以metadata签名的重要性,一点都不比payload签名低。
注意:metadata签名和payload签名使用的是不同的密钥对。也就是说,生成metadata签名的私钥和生成payload签名的私钥是分开的。这样做的好处是,即使某个密钥泄露,也不会影响另一个签名的安全性。
metadata签名的流程:
- 将metadata内容序列化为字节数组
- 计算该字节数组的哈希值
- 用metadata私钥进行签名
- 将签名结果写入OTA包的头部区域
13.4 公钥验证机制
公钥验证,是整个签名体系的最后一道关卡。设备端在验证签名时,需要用到公钥。这个公钥从哪里来?
Android的做法是:将公钥编译进bootloader或者recovery分区。也就是说,公钥是固化在设备固件中的,无法通过OTA升级来修改。这样做的好处很明显——即使攻击者伪造了整个OTA包,也无法绕过公钥验证,因为公钥是硬件级别的信任根。
| 验证阶段 | 验证内容 | 使用的公钥 |
|---|---|---|
| 第一阶段 | 验证metadata签名 | metadata公钥(固化在bootloader) |
| 第二阶段 | 验证payload签名 | payload公钥(固化在recovery) |
| 第三阶段 | 验证payload哈希与metadata中记录的是否一致 | 无需公钥,直接比对哈希值 |
我个人习惯把公钥验证看作是"三明治"结构:上下两层是签名验证,中间一层是哈希比对。三层都通过了,这个OTA包才算真正可信。
13.5 避坑指南
我在项目中踩过不少签名的坑,这里分享几个典型的:
- 密钥管理混乱:我曾经见过一个项目,开发环境和生产环境用了同一套签名密钥。结果开发人员不小心把私钥提交到了Git仓库...嗯,后果可想而知。
- 签名算法不匹配:服务端用RSA-2048签名,设备端却只支持RSA-1024验证。这种问题在联调时最容易暴露。
- 公钥更新问题:如果公钥需要更新,怎么办?Android的解决方案是:通过系统镜像更新来替换公钥。但这意味着,你无法通过OTA来修复公钥本身的问题——这是一个"先有鸡还是先有蛋"的困境。
我的建议:在项目初期就把签名密钥的生命周期管理规划好。包括密钥的生成、分发、轮换、撤销等环节。不要等到上线了才发现密钥管理一团糟。
13.6 验证流程总结
整个OTA包的签名验证流程,可以概括为以下几步:
- 设备读取OTA包的metadata区域
- 用固化在bootloader中的公钥验证metadata签名
- 如果metadata签名有效,从中提取payload的哈希值
- 读取payload.bin,计算其哈希值,与metadata中的哈希值比对
- 用固化在recovery中的公钥验证payload签名
- 所有验证通过,开始执行升级
你看,每一步都是环环相扣的。任何一个环节失败,升级都会被中止。这种设计虽然看起来繁琐,但正是这种"层层把关"的机制,保证了OTA升级的安全性。
说白了,签名验证就是一场"信任链"的传递。从硬件固化的公钥开始,一层层验证下去,直到确认整个OTA包都是可信的。我在做系统安全设计时,经常引用这个思路——信任不能凭空产生,必须有一个可信的起点。