调试与发布签名
调试签名和发布签名的核心差异,不是 keystore 文件名或证书主体,而是 APK 的 SigningDetails 是否仍被 PackageManager 视为同一个包的可信更新者。Android 17 对签名切换没有“开发模式自动放行”的通用后门:普通更新仍要满足 lineage capability;debuggable 和 testOnly 只影响特定安装策略。
1. 三种状态
| 状态 | 来源 | 影响 |
|---|---|---|
| signer | APK v1/v2/v3/v4 验证 | 身份、更新、shared UID、签名权限 |
FLAG_DEBUGGABLE | manifest/application | 调试器、部分测试策略 |
FLAG_TEST_ONLY | manifest/application | 安装命令是否要求 -t |
调试构建通常同时使用 debug key、debuggable=true 和 testOnly=true,但三者不是同一个字段。换一把 key 不会自动设置 FLAG_DEBUGGABLE;把 debuggable 设为 true 也不会让新证书通过更新签名检查。
2. 安装时收集签名
源码文件: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,随后由更新、shared UID 和权限路径消费。
3. 更新签名门禁
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
SigningDetails parsedPkgSigningDetails =
parsedPackage.getSigningDetails();
SigningDetails oldPkgSigningDetails =
oldPackageState.getSigningDetails();
if (!parsedPkgSigningDetails.checkCapability(
oldPkgSigningDetails,
SigningDetails.CertCapabilities.INSTALLED_DATA)
&& !oldPkgSigningDetails.checkCapability(
parsedPkgSigningDetails,
SigningDetails.CertCapabilities.ROLLBACK)) {
if (!isRollback || !oldPkgSigningDetails.hasAncestorOrSelf(
parsedPkgSigningDetails)) {
throw new PrepareFailure(
INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"New package has a different signature: "
+ pkgName11);
}
}新发布签名替换调试签名时,若没有 v3 lineage,INSTALLED_DATA 和 ROLLBACK 都失败,安装返回 INSTALL_FAILED_UPDATE_INCOMPATIBLE。卸载旧包再安装可以获得新 appId 状态,但会失去原应用的数据连续性;这不是签名检查被绕过,而是更新关系被切断。
4. 回滚方向
普通更新要求“新 signer 信任旧 signer 的已安装数据”;回滚要求“旧 signer 允许回到新 signer”,因此代码反向调用 old.checkCapability(new, ROLLBACK)。此外,rollback 场景还要求旧 signer 的 lineage 包含待回滚 signer,防止任意历史 APK 被当作回滚包。
5. debuggable 作用
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
// On debuggable platform builds, downgrades are permitted even for
// non-debuggable packages to make testing easier.
if (Build.IS_DEBUGGABLE) {
// 允许测试环境使用更宽松的降级策略
}Android 17 将平台是否可调试用于部分 downgrade 策略;它不改变 verifySignatures 的 signer capability 门禁。即使设备是 userdebug,换成另一把 debug key 也不能直接覆盖已安装的 release-signed 包。
6. testOnly 限制
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
if (parsedPackage.isTestOnly()
&& (installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) {
throw new PrepareFailure(
INSTALL_FAILED_TEST_ONLY,
"Failed to install test-only apk. Did you forget to add -t?");
}testOnly 检查的是安装 flag INSTALL_ALLOW_TEST,通常对应 adb install -t。它解决的是“测试 APK 是否允许安装”,不是“证书是否可信”;加 -t 不会让不同签名的更新通过。
7. shared UID
boolean capabilityGranted =
packageSigningDetails.checkCapability(
sharedUserSigningDetails,
SigningDetails.CertCapabilities.SHARED_USER_ID)
|| sharedUserSigningDetails.checkCapability(
packageSigningDetails,
SigningDetails.CertCapabilities.SHARED_USER_ID);调试版和发布版若声明相同 sharedUserId,必须满足 shared UID signer/capability 关系。即使包名相同,签名切换也不能直接继续使用 UID 1000 或其他 shared UID;失败通常表现为 shared user incompatible,而非普通 update incompatible。
8. 数据迁移的边界
签名相同或 lineage 允许 INSTALLED_DATA 时,更新沿用原 appId 和数据目录。签名切换失败时,三个可行路径含义不同:
- 使用 v3 key rotation 发布,保留 lineage 和需要的 capability,继续更新。
- 卸载旧包后重新安装,获得新签名身份,但数据通常不再自动关联。
- 在测试设备清除应用数据后安装新包;这是环境清理,不是 PMS 签名豁免。
FLAG_DEBUGGABLE 不会把旧数据迁移给不兼容 signer;数据连续性由 SigningDetails.checkCapability(INSTALLED_DATA) 决定。
9. 排查清单
- 先用
SigningDetails确认当前 signer、scheme version 和 past lineage。 - 更新失败看
INSTALLED_DATA,回滚看ROLLBACK,shared UID 看SHARED_USER_ID。 INSTALL_FAILED_TEST_ONLY检查-t,不要把它和签名错误混淆。- userdebug/debuggable 设备仍执行签名兼容检查。
- release → debug 或 debug → release 直接覆盖失败时,确认是否真的存在 v3 rotation lineage。
- 签名切换后数据丢失,区分“更新被拒绝”和“卸载后重装导致的新 appId/数据目录”。
10. 源码阅读路线
InstallPackageHelper的签名收集代码。verifySignatures与更新/回滚 capability 判断。SigningDetails.checkCapability和hasAncestorOrSelf。PackageManagerServiceUtils.canJoinSharedUserId。InstallPackageHelper的testOnly和 downgrade 分支。PackageSetting的getSigningDetails/setSigningDetails,确认安装成功后签名如何进入持久状态。
调试签名与发布签名的真正分界在 signer 身份和数据连续性:debuggable 影响调试/测试策略,testOnly 影响安装入口,只有 SigningDetails lineage capability 才决定新 APK 能否替换旧 APK 并继续使用原数据。
