Skip to content

ApkChecksums 完整性校验

分析 APK checksum 类型、fs-verity、签名 digest 文件和 PackageInstaller 校验链。

AndroidPMSAPK签名

ApkChecksums 完整性校验 ​

ApkChecksums 处理的不是 APK signer 身份,而是文件内容摘要。Android 17 同时支持全文件哈希、Merkle root、v2/v3 内容 digest 和安装器签名的 digest 文件;调用方通过 optional/required mask 请求类型,工具类先复用已有来源,再计算缺失值。

1. 四类对象 ​

对象表达内容
Checksum类型位与原始摘要字节
ApkChecksumsplit 名称、Checksum、安装器身份
ApkChecksums提取、计算、验证和序列化
PackageInstallerSession接收安装器 checksum 并持久化

2. Checksum 类型 ​

源码文件:frameworks/base/core/java/android/content/pm/Checksum.java

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

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

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

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

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。

java
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. 排查顺序 ​

  1. 先确认请求 mask 是 optional 还是 required,缺失 optional 不代表失败。
  2. 查看 checksum 类型,区分 whole hash、partial digest 和 fs-verity root。
  3. 安装器提供的值检查 installer package、certificate 和 digest signature。
  4. 增量安装失败时检查 needToWait 和文件 ready 状态。
  5. APK 内容改变时分别判断 signer 是否失败、partial digest 是否变化、whole hash 是否变化。
  6. split APK 校验要检查 splitName 与每个文件的 digest 是否对应。

11. 源码阅读路线 ​

  1. Checksum / ApkChecksum:类型和来源数据模型。
  2. ApkChecksums.getChecksums:available/required 补齐算法。
  3. getAvailableApkChecksums:fs-verity、v2/v3、installer digest 来源。
  4. PackageManagerService.requestFileChecksums:对外 API 和 trusted installers。
  5. PackageInstallerSession.setChecksums:安装器输入验证与持久化。
  6. PackageInstallerSession 的 digest 文件落盘和继承路径:增量安装的完整性边界。

完整性校验的核心不是“算一个 SHA256”,而是根据请求类型选择可信来源,并区分 APK signer、部分内容 digest、全文件 hash、fs-verity root 和安装器签名。只有沿 required mask 的补齐路径阅读,才能解释某种 checksum 为什么来自现成数据、为什么需要等待,或为什么最终计算失败。