APK 签名方案
APK 签名验证不是“依次尝试 v1、v2、v3、v4”这么简单。Android 17 的 ApkSignatureVerifier 根据调用方要求的最低 scheme 版本选择路径:优先尝试 v4,失败后在允许范围内降到 v3、v2、v1;安装流程随后还会把新包签名与已安装包、共享 UID、回滚能力和目标 SDK 的最低方案要求进行比较。
1. 方案职责
| 方案 | 数据位置 | 主要保护 |
|---|---|---|
| v1 JAR | META-INF ZIP 条目 | 逐条目摘要 |
| v2 | APK Signing Block | APK 内容与 ZIP 结构 |
| v3/v3.1 | APK Signing Block | v2 能力加密钥轮换/版本属性 |
| v4 | 外部 .idsig | fs-verity/增量安装辅助 |
v4 不单独替代 APK 内 v2/v3 证书;Android 17 的 v4 验证路径会读取 v2 签名信息,再验证 v4 的 fs-verity 数据。
2. 统一验证入口
源码文件:frameworks/base/core/java/android/util/apk/ApkSignatureVerifier.java
public static ParseResult<SigningDetails> verify(
ParseInput input, String apkPath,
@SignatureSchemeVersion int minSignatureSchemeVersion) {
return verifySignatures(
input, apkPath, minSignatureSchemeVersion,
true /* verifyFull */);
}
public static ParseResult<SigningDetails> unsafeGetCertsWithoutVerification(
ParseInput input, String apkPath,
int minSignatureSchemeVersion) {
return verifySignatures(
input, apkPath, minSignatureSchemeVersion,
false /* verifyFull */);
}正常入口 verifyFull=true 校验 APK 内容;unsafeGetCertsWithoutVerification 只为需要提取证书的受控场景提供较弱路径。两者都返回 SigningDetails,而不是只返回一个证书数组。
3. v4 优先
public static ParseResult<SigningDetailsWithDigests> verifySignaturesInternal(
ParseInput input, String apkPath,
@SignatureSchemeVersion int minSignatureSchemeVersion,
boolean verifyFull) {
if (minSignatureSchemeVersion
> SignatureSchemeVersion.SIGNING_BLOCK_V4) {
return input.error(
INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version "
+ minSignatureSchemeVersion
+ " or newer for package " + apkPath);
}
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);
}
}调用方要求 v4 时,找不到 v4 直接报错;只有最低版本低于 v4,才允许继续降级。SignatureNotFoundException 表示方案不存在,不等同于签名数据损坏;损坏通常由 verifier 返回解析错误。
4. v3、v2、v1 降级
if (minSignatureSchemeVersion
> SignatureSchemeVersion.SIGNING_BLOCK_V3) {
return input.error(
INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version "
+ minSignatureSchemeVersion + " or newer");
}
return verifyV3AndBelowSignatures(
input, apkPath, minSignatureSchemeVersion, verifyFull);源码文件:frameworks/base/core/java/android/util/apk/ApkSignatureVerifier.java
private static ParseResult<SigningDetailsWithDigests>
verifyV3AndBelowSignatures(
ParseInput input, String apkPath,
@SignatureSchemeVersion int minSignatureSchemeVersion,
boolean verifyFull) {
try {
return verifyV3Signature(input, apkPath, verifyFull);
} catch (SignatureNotFoundException e) {
if (minSignatureSchemeVersion
>= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
return input.error(
INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v3 signature in package "
+ apkPath, e);
}
}
if (minSignatureSchemeVersion
> SignatureSchemeVersion.SIGNING_BLOCK_V2) {
return input.error(
INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version "
+ minSignatureSchemeVersion + " or newer");
}
try {
return verifyV2Signature(input, apkPath, verifyFull);
} catch (SignatureNotFoundException e) {
if (minSignatureSchemeVersion
>= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
return input.error(
INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v2 signature in package "
+ apkPath, e);
}
}
if (minSignatureSchemeVersion
> SignatureSchemeVersion.JAR) {
return input.error(
INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version "
+ minSignatureSchemeVersion + " or newer");
}
return verifyV1Signature(input, apkPath, verifyFull);
}降级是“最低版本约束下的降级”:要求 v3 的调用不会接受只有 v2 的 APK;允许 v1 的调用才会最终尝试 JAR。这个判断由 minSignatureSchemeVersion 控制,不由设备当前 Android 版本自动放宽。
5. 安装时读取签名
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
if (request.getSigningDetails() != SigningDetails.UNKNOWN) {
parsedPackage.setSigningDetails(
request.getSigningDetails());
} else {
final ParseTypeImpl input =
ParseTypeImpl.forDefaultParsing();
final ParseResult<SigningDetails> result =
ParsingPackageUtils.getSigningDetails(
input, parsedPackage,
false /* skipVerify */);
if (result.isError()) {
throw new PrepareFailure(
"Failed collect during installPackageLI",
result.getException());
}
parsedPackage.setSigningDetails(result.getResult());
}安装会话如果已提供签名详情则复用,否则从 APK 解析。skipVerify=false 表示收集阶段仍要求正常签名验证;结果写入 parsedPackage,后续更新兼容性检查消费该对象。
6. Instant App 要求
if (instantApp && parsedPackage.getSigningDetails()
.getSignatureSchemeVersion()
< SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2) {
Slog.w(TAG, "Instant app package "
+ parsedPackage.getPackageName()
+ " is not signed with at least APK Signature Scheme v2");
throw new PrepareFailure(
INSTALL_FAILED_SESSION_INVALID,
"Instant app package must be signed with APK Signature Scheme v2 or greater");
}Instant App 至少要求 v2。即使普通 APK 可以因兼容性降级到 v1,Instant App 安装路径也会在签名方案版本不足时直接失败。
7. 更新包的签名能力
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
SigningDetails parsed = parsedPackage.getSigningDetails();
SigningDetails old = oldPackageState.getSigningDetails();
if (!parsed.checkCapability(
old, SigningDetails.CertCapabilities.INSTALLED_DATA)
&& !old.checkCapability(
parsed, SigningDetails.CertCapabilities.ROLLBACK)) {
if (!isRollback || !old.hasAncestorOrSelf(parsed)) {
throw new PrepareFailure(
INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"New package has a different signature: "
+ pkgName);
}
}更新不是简单比较当前证书字节。INSTALLED_DATA 能力允许新签名继续使用旧安装数据;ROLLBACK 能力允许回滚路径使用另一方向的签名能力。两者都不满足时,普通更新失败;回滚还要满足旧签名 lineage 条件。
8. 签名权限能力
源码文件:frameworks/base/services/core/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
val hasCommonSigner =
sourceSigningDetails?.hasCommonSignerWithCapability(
packageSigningDetails,
SigningDetails.CertCapabilities.PERMISSION,
) == true ||
packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
platformSigningDetails.checkCapability(
packageSigningDetails,
SigningDetails.CertCapabilities.PERMISSION,
)签名权限使用 PERMISSION capability,而更新使用 INSTALLED_DATA/ROLLBACK。同一套 SigningDetails 支持不同安全目的,不能把“能更新旧包”推导成“能获得对方 signature 权限”。
9. v4 与增量安装
v4 验证器的输出包含签名者和 content digests,供增量安装/fs-verity 使用;APK 本身仍需要可验证的 v2/v3 签名信息。安装会话还会验证 .idsig 或 digest signature 文件,签名不一致时抛出 INSTALL_FAILED_INVALID_APK。
Certificate[] ignored = ApkChecksums.verifySignature(
checksums, signature);这里校验的是 checksum 文件签名,不是替代 APK signer。排查增量安装失败时,应分别检查 APK scheme、.idsig/digest 文件和安装会话保存的 SigningDetails。
10. 排查顺序
- 先确认调用方要求的最低签名方案,避免把“方案不存在”误认为签名损坏。
- 检查 v4 外部文件和 APK 内 v2/v3 是否同时存在且一致。
- 更新失败时比较
INSTALLED_DATA、ROLLBACKcapability,而不是只比较当前证书文本。 - Instant App 安装至少检查 v2。
- signature 权限问题检查
PERMISSIONcapability 与平台签名 lineage。 - 增量安装问题分离 APK 签名、checksum 签名和
.idsig验证。
11. 源码阅读路线
ApkSignatureVerifier.verify/verifySignaturesInternal:方案选择和降级边界。verifyV4Signature、verifyV3Signature、verifyV2Signature、verifyV1Signature:各方案 verifier。InstallPackageHelper签名收集与更新兼容检查。SigningDetails.checkCapability:不同 capability 的安全语义。AppIdPermissionPolicy.shouldGrantPermissionBySignature:签名权限如何消费PERMISSIONcapability。PackageInstallerSession的 checksum/idsig 验证:增量安装的第二条完整性链。
APK 签名方案解决的是“文件来自谁、内容是否被改动”;SigningDetails capability 解决的是“这份签名能否继承数据、回滚或访问签名权限”。Android 17 将方案选择、更新兼容和权限授权分层处理,排查时不能用一个证书比较替代全部判断。
