ApkChecksums 完整性校验
ApkChecksums 处理的不是 APK signer 身份,而是文件内容摘要。Android 17 同时支持全文件哈希、Merkle root、v2/v3 内容 digest 和安装器签名的 digest 文件;调用方通过 optional/required mask 请求类型,工具类先复用已有来源,再计算缺失值。
1. 四类对象
| 对象 | 表达内容 |
|---|---|
Checksum | 类型位与原始摘要字节 |
ApkChecksum | split 名称、Checksum、安装器身份 |
ApkChecksums | 提取、计算、验证和序列化 |
PackageInstallerSession | 接收安装器 checksum 并持久化 |
2. Checksum 类型
源码文件:frameworks/base/core/java/android/content/pm/Checksum.java
public static final int TYPE_WHOLE_MERKLE_ROOT_4K_SHA256 = 0x00000001;
public static final int TYPE_WHOLE_MD5 = 0x00000002;
public static final int TYPE_WHOLE_SHA1 = 0x00000004;
public static final int TYPE_WHOLE_SHA256 = 0x00000008;
public static final int TYPE_WHOLE_SHA512 = 0x00000010;
public static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256 = 0x00000020;
public static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512 = 0x00000040;
private final int mType;
private final byte[] mValue;类型是 bit mask,可组合请求。WHOLE 类型覆盖整个 APK 文件;PARTIAL 类型来自 v2/v3 内容 digest,只覆盖签名保护的数据区域;4K Merkle root 与 fs-verity 对齐。
3. ApkChecksum 来源
源码文件:frameworks/base/core/java/android/content/pm/ApkChecksum.java
private final String mSplitName;
private final Checksum mChecksum;
private final String mInstallerPackageName;
private final Certificate mInstallerCertificate;
public int getType() {
return mChecksum.getType();
}
public byte[] getValue() {
return mChecksum.getValue();
}平台自己计算的 checksum 通常没有 installer 信息;安装器提供的 digest 可以携带 package name 和 certificate,调用方据此判断来源是否可信。base APK 的 splitName 为 null,split APK 用名称区分文件。
4. 获取入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApkChecksums.java
static List<ApkChecksum> getChecksums(
List<Pair<String, String>> filesToChecksum,
int optional, int required,
String installerPkgName,
Certificate[] trustedInstallers,
Injector injector) {
List<ApkChecksum> allChecksums = new ArrayList<>();
for (Pair<String, String> fileEntry : filesToChecksum) {
File file = new File(fileEntry.first);
String splitName = fileEntry.second;
int allTypes = optional | required;
ApkChecksum[] available = getAvailableApkChecksums(
splitName, file, allTypes, required,
installerPkgName, trustedInstallers, injector);
int availableTypes = 0;
for (ApkChecksum checksum : available) {
availableTypes |= checksum.getType();
allChecksums.add(checksum);
}
int remaining = required & ~availableTypes;
if (remaining != 0 && !needToWait(file, remaining, injector)) {
ApkChecksum[] computed = getRequiredApkChecksums(
splitName, file, remaining,
null, null, allTypes);
Collections.addAll(allChecksums, computed);
}
}
return allChecksums;
}算法是 available 优先、required 补齐:optional 缺失可以忽略,required 缺失必须从文件或签名块计算;增量文件尚未就绪时可能等待,而不是立即计算失败。
5. 已有来源的优先级
getAvailableApkChecksums 会优先读取 fs-verity/v4 的 whole Merkle root,再读取 v2/v3 签名块中的 partial digest,最后读取安装器提供的 .digests 文件。每个来源都只填充请求 mask 中尚未满足的类型。
6. 对外请求
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public void requestFileChecksums(
File file, String installerPackageName,
int optional, int required,
List trustedInstallers,
IOnChecksumsReadyListener listener) {
Certificate[] trustedCerts =
ApkChecksums.getTrustedInstallerCertificates(
installerPackageName, trustedInstallers);
List<Pair<String, File>> filesToChecksum =
new ArrayList<>(1);
filesToChecksum.add(Pair.create(null, file));
ApkChecksums.getChecksums(
filesToChecksum, optional, required,
installerPackageName, trustedCerts,
new ApkChecksums.Injector(...));
}包级请求会把 base APK 和可选 split APK 转成文件列表,再异步回调 OnChecksumsReadyListener。trusted installers 用于验证安装器提供的 digest,不能只相信 .digests 文件名。
7. 安装器输入
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java
public void setChecksums(String name,
Checksum[] checksums, byte[] signature) {
if (signature != null && signature.length != 0) {
Certificate[] ignored =
ApkChecksums.verifySignature(checksums, signature);
}
for (Checksum checksum : checksums) {
if (checksum.getValue().length
> Checksum.MAX_CHECKSUM_SIZE_BYTES) {
throw new IllegalArgumentException(
"checksum too large");
}
}
assertPreparedAndNotCommittedOrDestroyedLocked("addChecksums");
if (mChecksums.containsKey(name)) {
throw new IllegalStateException(
"Checksums already set for " + name);
}
mChecksums.put(name, new PerFileChecksum(
checksums, signature));
}安装器提供 checksum 时,session 先验证整体签名,再限制单个 checksum 大小,并禁止 commit 后重复写入。checksum 在安装时写入 .digests,签名写入对应的 digest signature 文件。
8. Partial 与 whole
PARTIAL Merkle root 来自 v2/v3 content digest,覆盖 APK 签名保护的数据区域;WHOLE SHA-256/SHA-512 覆盖整个文件,包括 APK Signing Block。两者不能互相替代:同一个 APK 可以拥有不同类型的值,且签名块变化会影响 whole hash 而不一定改变 partial content digest。
byte[] fsHash = extractHashFromFS(file);
if (fsHash != null) {
result.add(new ApkChecksum(
splitName,
new Checksum(
Checksum.TYPE_WHOLE_MERKLE_ROOT_4K_SHA256,
fsHash),
null, null));
}fs-verity 根哈希优先来自内核 ioctl 或 v4 .fsv_sig,平台无需重新读取整文件计算。无法获取时,required 类型才进入用户空间计算路径。
9. 增量安装等待
needToWait(file, remaining, injector) 是增量/流式安装的边界:文件尚未完整可读时,计算 whole hash 可能没有意义。ApkChecksums 通过 IncrementalManager 判断文件是否 ready,等待完成后再补齐 required checksum;这与 APK signer 验证的失败语义不同。
10. 排查顺序
- 先确认请求 mask 是 optional 还是 required,缺失 optional 不代表失败。
- 查看 checksum 类型,区分 whole hash、partial digest 和 fs-verity root。
- 安装器提供的值检查 installer package、certificate 和 digest signature。
- 增量安装失败时检查
needToWait和文件 ready 状态。 - APK 内容改变时分别判断 signer 是否失败、partial digest 是否变化、whole hash 是否变化。
- split APK 校验要检查 splitName 与每个文件的 digest 是否对应。
11. 源码阅读路线
Checksum/ApkChecksum:类型和来源数据模型。ApkChecksums.getChecksums:available/required 补齐算法。getAvailableApkChecksums:fs-verity、v2/v3、installer digest 来源。PackageManagerService.requestFileChecksums:对外 API 和 trusted installers。PackageInstallerSession.setChecksums:安装器输入验证与持久化。PackageInstallerSession的 digest 文件落盘和继承路径:增量安装的完整性边界。
完整性校验的核心不是“算一个 SHA256”,而是根据请求类型选择可信来源,并区分 APK signer、部分内容 digest、全文件 hash、fs-verity root 和安装器签名。只有沿 required mask 的补齐路径阅读,才能解释某种 checksum 为什么来自现成数据、为什么需要等待,或为什么最终计算失败。
