Skip to content

签名机制与密钥管理

追踪证书配置、APK 签名、密钥轮转到 PackageManager 消费 SigningDetails 的完整主线,并定位更新、shared UID、签名权限与回滚的边界。

基于android-17.0.0_r1
Android构建系统APKPackageManager签名身份

签名机制与密钥管理 ​

在 Android 中,“签过名”不是构建结束时贴上的一个标签。证书和私钥先由构建系统选择,随后被 SignApk 消费成 APK 签名工件;设备解析 APK 时,ApkSignatureVerifier 再把工件转换成 SigningDetails。Package Manager 保存并消费的不是私钥,而是当前证书、历史证书和每个历史证书 允许继续承担的 capability。

本文面向已经读过 lunch目标与编译变体、APK签名工具链 和 ADB安装与权限管理 的读者。你需要知道 android_app、预置 APK、安装更新和 APK 的基本结构,但不需要先学习 X.509、PKCS#8 或 HSM 实现。本文只讨论 AOSP Android 17 中“构建输入如何变成 Package Manager 的身份判断”;不展开 密码学算法、厂商密钥托管、OTA payload 算法和 Keystore 用户证书。

读完后,你应能从一个模块的 certificate 属性找到 .x509.pem 与 .pk8,解释 release 阶段 为什么还会重新签名,并从 SigningDetails 追到更新、shared UID、签名权限和 rollback 的实际 消费者。文章中的源码均来自 android-17.0.0_r1,路径相对对应 AOSP project。

1. 身份边界 ​

APK 签名身份有三个容易混淆的层次:构建输入是私钥和证书文件,APK 工件是签名方案写入的 ZIP 结构,framework 身份是 SigningDetails。前一层拥有秘密,后一层只保留可以被系统比较和授权的 结果。

SigningDetails 不等于“证书字符串”。Android 17 的真实字段包括:

字段所有者用途生效时机
mSignaturesSigningDetails当前 signer 的证书集合APK 解析成功后
mPublicKeysSigningDetails当前公钥集合构造对象时
mPastSigningCertificatesSigningDetailsv3 proof-of-rotation 的历史证书capability 比较时
mSignatureSchemeVersionSigningDetailsv1/v2/v3/v4 方案版本verifier 返回时

历史证书是否仍能承担更新、权限或 shared UID,不由“它曾经出现过”决定,而由 lineage 中记录的 capability 决定。这样可以轮换私钥,同时选择性撤销旧证书的某种能力。

2. 模块证书 ​

2.1 Soong 属性 ​

build/soong/java/app.go 把 android_app 的 certificate、lineage 和 additional_certificates 变成签名动作的输入。certificate 可以是证书名,也可以是 android_app_certificate 模块引用;没有显式值时使用产品默认证书。

源码文件:build/soong/java/app.go

go
// build/soong/java/app.go :: processMainCert
func processMainCert(m android.ModuleBase, certPropValue string,
        certificates []Certificate, ctx android.ModuleContext) (
        mainCertificate Certificate, allCertificates []Certificate) {
    if android.SrcIsModule(certPropValue) == "" {
        var mainCert Certificate
        if certPropValue != "" {
            defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
            mainCert = Certificate{
                Pem: defaultDir.Join(ctx, certPropValue+".x509.pem"),
                Key: defaultDir.Join(ctx, certPropValue+".pk8"),
            }
        } else {
            pem, key := ctx.Config().DefaultAppCertificate(ctx)
            mainCert = Certificate{Pem: pem, Key: key}
        }
        certificates = append([]Certificate{mainCert}, certificates...)
    }

    if len(certificates) > 0 {
        mainCertificate = certificates[0]
    } else {
        mainCertificate = Certificate{
            Key: android.PathForModuleOut(ctx, "missing.pk8"),
            Pem: android.PathForModuleOut(ctx, "missing.x509.pem"),
        }
    }
    // 随后检查非 platform 模块使用 system certificate 的限制。
    return mainCertificate, certificates
}

真正的调用点在 AndroidApp.generateAndroidBuildActions:

源码文件:build/soong/java/app.go

go
// build/soong/java/app.go :: generateAndroidBuildActions
a.certificate, certificates = processMainCert(
    a.ModuleBase, a.getCertString(ctx), certificates, ctx)

CreateAndSignAppPackage(ctx, packageFile, packageResources, jniJarFile,
    dexJarFile, certificates, apkDeps, v4SignatureFile, lineageFile,
    rotationMinSdkVersion, a.MinSdkVersion(ctx))

这里的 owner 是 Soong 的 module variant。它只决定“这个 variant 要交给签名工具的文件”,不负责 验证安装时的兼容性。processMainCert 还会把缺失依赖变成 missing.pk8 和 missing.x509.pem 占位并报告错误;这不是一种可发布的签名成功状态。另一个边界是 system certificate 限制:当 非 platform 模块从默认 system certificate 目录取证书时,EnforceSystemCertificate 和 allowlist 可能在分析阶段拒绝它。

2.2 Make 预置包 ​

预置 APK 不经过 Soong 的 android_app 属性时,build/make/core/app_prebuilt_internal.mk 用 LOCAL_CERTIFICATE 定义相同的契约,但 EXTERNAL 和 PRESIGNED 的语义不同。

源码文件:build/make/core/app_prebuilt_internal.mk

make
ifeq ($(LOCAL_CERTIFICATE),EXTERNAL)
  LOCAL_CERTIFICATE := $(DEFAULT_SYSTEM_DEV_CERTIFICATE)
  PACKAGES.$(LOCAL_MODULE).EXTERNAL_KEY := 1
endif

ifeq ($(LOCAL_CERTIFICATE),PRESIGNED)
  PACKAGES.$(LOCAL_MODULE).CERTIFICATE := PRESIGNED
  PACKAGES := $(PACKAGES) $(LOCAL_MODULE)
else
  PACKAGES.$(LOCAL_MODULE).PRIVATE_KEY := $(LOCAL_CERTIFICATE).pk8
  PACKAGES.$(LOCAL_MODULE).CERTIFICATE := $(LOCAL_CERTIFICATE).x509.pem
  $(built_module): PRIVATE_PRIVATE_KEY := $(LOCAL_CERTIFICATE).pk8
  $(built_module): PRIVATE_CERTIFICATE := $(LOCAL_CERTIFICATE).x509.pem
  $(built_module): PRIVATE_CERTIFICATE_LINEAGE := $(LOCAL_CERTIFICATE_LINEAGE)
endif

PRESIGNED 表示构建系统不重新签名;对 SDK 30 及以上的预置 APK,Make 还会避免会破坏 v2+ 签名的修改。EXTERNAL 则相反:构建期间用 dev key 让 dexpreopt 等流程可以继续,但记录 EXTERNAL_KEY,期待 release 阶段换成真正的 key。把两者都理解成“跳过签名”会导致 release 产物身份错误。

3. 签名动作 ​

Soong 收集完证书后,build/soong/java/app_builder.go 先合并未签名 APK,再调用 SignAppPackage。证书按 pem, pk8 成对传给 SignApk;lineage 和 v4 是独立输入。

源码文件:build/soong/java/app_builder.go

go
func SignAppPackage(ctx android.ModuleContext, signedApk android.WritablePath,
        unsignedApk android.Path, certificates []Certificate,
        v4SignatureFile android.WritablePath, lineageFile android.Path,
        rotationMinSdkVersion string, minSdkVersion android.ApiLevel) {
    var certificateArgs []string
    var deps android.Paths
    for _, c := range certificates {
        certificateArgs = append(certificateArgs, c.Pem.String(), c.Key.String())
        deps = append(deps, c.Pem, c.Key)
    }
    outputFiles := android.WritablePaths{signedApk}
    var flags []string
    if v4SignatureFile != nil {
        outputFiles = append(outputFiles, v4SignatureFile)
        flags = append(flags, "--enable-v4")
    }
    if lineageFile != nil {
        flags = append(flags, "--lineage", lineageFile.String())
        deps = append(deps, lineageFile)
    }
    if rotationMinSdkVersion != "" {
        flags = append(flags, "--rotation-min-sdk-version", rotationMinSdkVersion)
    }
    if minSdkVersion.GreaterThanOrEqualTo(android.UncheckedFinalApiLevel(24)) {
        flags = append(flags, "--disable-v1")
    }
    // ctx.Build(... Rule: Signapk, Input: unsignedApk, Outputs: outputFiles ...)
}

minSdk >= 24 时显式加入 --disable-v1,所以不能仅根据 APK 是否存在 META-INF 判断构建 配置。v4 的 .idsig 是额外输出,和 APK 不是同一个文件;v4 写入失败时,调用方必须把两者 视为一组工件重新生成,不能拿只生成了 APK 的目录继续做增量安装验证。

4. Release 映射 ​

开发构建使用 dev key 并不意味着设备最终应该信任 dev key。META/apkcerts.txt 汇总每个 APK 的证书记录,sign_target_files_apks.py 在 target-files 到 release 包的阶段读取它并应用 OPTIONS.key_map。

源码文件:build/make/tools/releasetools/sign_target_files_apks.py

python
def GetApkCerts(certmap):
    if OPTIONS.override_apk_keys is not None:
        for apk in certmap.keys():
            certmap[apk] = OPTIONS.override_apk_keys
    for apk, cert in certmap.items():
        certmap[apk] = OPTIONS.key_map.get(cert, cert)
    for apk, cert in OPTIONS.extra_apks.items():
        if not cert:
            cert = "PRESIGNED"
        certmap[apk] = OPTIONS.key_map.get(cert, cert)
    return certmap

def BuildKeyMap(misc_info, key_mapping_options):
    for s, d in key_mapping_options:
        if s is None:  # -d
            devkey = misc_info.get("default_system_dev_certificate", ".../testkey")
            devkeydir = os.path.dirname(devkey)
            OPTIONS.key_map.update({
                devkeydir + "/testkey": d + "/releasekey",
                devkeydir + "/devkey": d + "/releasekey",
                devkeydir + "/media": d + "/media",
                devkeydir + "/shared": d + "/shared",
                devkeydir + "/platform": d + "/platform",
                devkeydir + "/networkstack": d + "/networkstack",
                devkeydir + "/sdk_sandbox": d + "/sdk_sandbox",
            })
        else:
            OPTIONS.key_map[s] = d

映射只改变 release 阶段的签名输入,不改变 APK 的 package name 或 version code。它还会作用于 OTA 验证证书:ReplaceOtaKeys 按同一个 key_map 改写 META/otakeys.txt,并检查目标 .x509.pem 是否存在。APK 已重签而 OTA 验证 key 未替换,会得到“单个 APK 身份正确、整包升级信任错误”的 不一致状态。

PRESIGNED 不会被映射成 release key;-e 可以为指定 APK 增加或覆盖记录。release 失败时, 不要用“重签过的半成品”继续打包:应回到原始 target-files,修正 key map 或缺失 key,再重新 生成整个输出包。

5. Lineage 轮转 ​

APK Signature Scheme v3 的 proof-of-rotation 把旧证书和能力写进签名数据。构建端只负责把 lineage 文件传给 SignApk;设备端是否接受旧证书,取决于 SigningDetails 的 capability 检查。

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

java
@IntDef(flag = true, value = {
        CertCapabilities.INSTALLED_DATA,
        CertCapabilities.SHARED_USER_ID,
        CertCapabilities.PERMISSION,
        CertCapabilities.ROLLBACK})
public @interface CertCapabilities {
    int INSTALLED_DATA = 1;
    int SHARED_USER_ID = 2;
    int PERMISSION = 4;
    int ROLLBACK = 8;
    int AUTH = 16;
}

这里要区分两个事实:AUTH 在 Android 17 文件中仍有常量定义,且 verifier 会把它作为 rotation flags 的一部分解析;但当前 @IntDef 的公开约束和本文追踪到的 Package Manager 消费者只把 INSTALLED_DATA、SHARED_USER_ID、PERMISSION、ROLLBACK 当作正式判断维度。不能把常量存在 误写成某个独立的安装授权流程。

SigningDetails.checkCapability 的语义是:当前 signer 直接匹配时通过;如果匹配的是历史 signer, 还必须检查该历史 signer 的 capability。hasAncestorOrSelf 则只回答 lineage 关系,不自动授予 某种 capability。

轮转的 owner 是 APK 的签名数据和 framework 保存的 SigningDetails,不是某个运行时服务。轮转 真正生效发生在设备比较两个 SigningDetails 的那一刻;仅仅在 keystore 中生成新私钥,不会让 旧包自动获得更新能力。

6. 解析身份 ​

ParsingPackageUtils 明确把“解析 manifest”和“验证签名”分成两个阶段。parsePackage 的注释 直接说明它不执行签名验证;调用方需要在 collectCertificates 或 getSigningDetails 阶段完成。

源码文件:frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java

java
if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
    ParseResult<SigningDetails> ret =
            getSigningDetails(input, pkg, false /*skipVerify*/);
    if (ret.isError()) {
        return input.error(ret);
    }
    pkg.setSigningDetails(ret.getResult());
} else {
    pkg.setSigningDetails(SigningDetails.UNKNOWN);
}

getSigningDetails 对 base APK 和 split APK 逐一收集并断言签名一致;skipVerify 为 true 时只 能用于调用方已经确认可信的场景。它最终调用 ApkSignatureVerifier.verify,因此 SigningDetails 是 verifier 的结果,而不是 manifest 中一个可任意填写的字段。

源码文件:frameworks/base/core/java/android/util/apk/ApkSignatureVerifier.java

java
try {
    return verifyV4Signature(input, apkPath, minSignatureSchemeVersion, verifyFull);
} catch (SignatureNotFoundException e) {
    if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V4) {
        return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                "No APK Signature Scheme v4 signature in package " + apkPath, e);
    }
}
return verifyV3AndBelowSignatures(input, apkPath, minSignatureSchemeVersion, verifyFull);

后续函数按 v3、v2、v1 继续尝试。只有 SignatureNotFoundException 表示“该方案不存在,可以 降级”;摘要不匹配、签名块损坏和证书编码错误都是失败。失败结果沿 ParseResult 返回,PMS 不会得到一个可继续安装的 SigningDetails。

7. 更新校验 ​

安装更新时,InstallPackageHelper 在进入完整 reconcile 前调用 verifySignatures 做快速检查; ReconcilePackageUtils 随后再次检查并处理 system package、shared UID 和 lineage 合并。核心 比较不是简单的字符串相等:

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java

java
boolean match = parsedSignatures.checkCapability(
        pkgSetting.getSigningDetails(),
        SigningDetails.CertCapabilities.INSTALLED_DATA)
    || pkgSetting.getSigningDetails().checkCapability(
        parsedSignatures,
        SigningDetails.CertCapabilities.ROLLBACK);

if (!match && compareRecover) {
    match = matchSignaturesRecover(packageName, pkgSetting.getSigningDetails(),
            parsedSignatures, SigningDetails.CertCapabilities.INSTALLED_DATA)
        || matchSignaturesRecover(packageName, parsedSignatures,
            pkgSetting.getSigningDetails(), SigningDetails.CertCapabilities.ROLLBACK);
}

if (!match && isRollback) {
    match = pkgSetting.getSigningDetails().hasAncestorOrSelf(parsedSignatures);
}

if (!match) {
    throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
            "Existing package " + packageName
                    + " signatures do not match newer version; ignoring!");
}

正常更新首先要求已安装数据 capability;恢复比较是兼容旧 settings 数据的显式分支;rollback 还有一条特殊规则,允许回到设备上曾安装过的祖先 signer,即使 rollback capability 没有授予。 这三条路径不能合并成“支持密钥轮转”。它们的输入、允许范围和错误码都不同。

8. Shared UID 权限 ​

shared UID 的 owner 是 SharedUserSetting 中保存的共同 SigningDetails。新包要加入 shared UID, verifySignatures 会调用 canJoinSharedUserId,并在 lineage 分叉时拒绝安装。即使某一次 capability 检查通过,hasCommonAncestor 仍可能成为第二道边界。

signature 权限则由 permission service 消费。Android 17 的 AppIdPermissionPolicy 使用 hasCommonSignerWithCapability(..., PERMISSION),并在 platform signature permission 上叠加 allowlist flag;“证书相同”不是唯一条件。

消费者关键方法历史证书所需 capability失败结果
包更新PackageManagerServiceUtils.verifySignaturesINSTALLED_DATA 或 ROLLBACKINSTALL_FAILED_UPDATE_INCOMPATIBLE
shared UIDcanJoinSharedUserIdSHARED_USER_IDINSTALL_FAILED_SHARED_USER_INCOMPATIBLE
签名权限AppIdPermissionPolicy.shouldGrantPermissionBySignaturePERMISSION不授予权限;platform 权限还受 allowlist 影响
system package 对照matchSignatureInSystemINSTALLED_DATA / ROLLBACK更新 system app 被拒绝或保留数据

9. 失败与恢复 ​

签名链的失败点按 owner 分布,而不是都归到 apksigner:

阶段典型失败谁发现可恢复动作
Soong/Makekey 文件不存在、system certificate 不在 allowlist构建分析修正 module/product 配置和依赖
SignApklineage、ZIP 区段或私钥读取失败签名规则删除输出,基于 unsigned APK 重签
releasekey map 缺目标 key、PRESIGNED 状态不一致sign_target_files_apks.py回到原 target-files 重新映射
解析方案缺失、摘要不匹配、证书编码错误ApkSignatureVerifier用原输入重新签名;不能把篡改当降级
更新lineage capability 不满足verifySignatures使用兼容旧身份的签名或明确 rollback 流程
shared UIDlineage 分叉或 capability 被撤销PMS不能通过重试安装绕过,需统一签名身份

取消或中断时,构建系统不会把半成品变成可用 APK;尤其 .idsig 和 APK 必须成对清理。设备 安装失败时,PMS 抛出异常并保留既有 PackageSetting,不会因为一次失败更新自动替换旧的 SigningDetails。这就是为什么恢复动作应从原始输入重新生成,而不是修改已签名 ZIP。

10. 反向验证 ​

10.1 签名验证 ​

frameworks/base/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java 的 mergeLineageWith_* 系列测试构造根证书、后继证书和分叉 lineage,断言祖先、后继、无共同根和 capability 修改时的返回对象。它证明的是 SigningDetails 的关系算法,不证明真实 APK 已由 SignApk 写入对应 proof-of-rotation。

10.2 输入验证 ​

build/make/tools/releasetools/test_sign_target_files_apks.py 的 test_ReplaceCerts_duplicateEntries 先构造两个证书,再让 key map 把其中一个替换为另一个,断言 ReplaceCerts 抛出 AssertionError;test_CheckApkAndApexKeysAvailable_invalidApexKeys 则断言 APEX 容器和 payload 不能只有一边是 PRESIGNED。这些测试证明 release 映射的输入约束,不证明目标设备接受该包。

可以在本地做不写设备的源码验证:

bash
git -C frameworks/base show android-17.0.0_r1:core/java/android/content/pm/SigningDetails.java \
  | rg -n 'INSTALLED_DATA|SHARED_USER_ID|PERMISSION|ROLLBACK|AUTH'
git -C frameworks/base show android-17.0.0_r1:services/core/java/com/android/server/pm/PackageManagerServiceUtils.java \
  | rg -n -C 3 'verifySignatures|checkCapabilityRecover|INSTALL_FAILED_'

这两个命令只能验证固定 tag 中的符号和失败分支;它们不能证明你的产品配置、私钥权限、实际 target-files 或设备 OTA 签名。因此,真实 release 仍需对 META/apkcerts.txt、META/otakeys.txt 和最终 APK 使用 apksigner verify 做独立检查。

11. 源码导航 ​

按下面顺序阅读,可以从构建输入一路走到 framework 消费者:

  1. build/soong/java/app.go:processMainCert、Lineage、Additional_certificates。
  2. build/soong/java/app_builder.go:CreateAndSignAppPackage、SignAppPackage。
  3. build/make/core/app_prebuilt_internal.mk:EXTERNAL、PRESIGNED 和预置 APK 证书依赖。
  4. build/make/tools/releasetools/sign_target_files_apks.py:BuildKeyMap、GetApkCerts、OTA key 替换。
  5. frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java:getSigningDetails。
  6. frameworks/base/core/java/android/util/apk/ApkSignatureVerifier.java:v4 到 v1 的降级入口。
  7. frameworks/base/core/java/android/content/pm/SigningDetails.java:字段、lineage 和 capability。
  8. frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java:更新与 shared UID 结果。
  9. frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt:签名权限消费者。

读者可以用一个具体模块复述这条链:它从哪个 certificate 属性取到哪两个文件,release 阶段是否 发生映射,设备解析后哪个 SigningDetails 字段承载历史证书,最后哪个消费者决定安装成功、权限 授予或回滚接受。只要其中一跳无法在上述源码中定位,说明仍把“签名工具”“证书身份”和“策略 消费”混在了一起。