先看结论与判断条件

  • v4 idsig 是依赖特定 APK 内容生成的独立伴随文件,必须和对应 APK 作为一对证据管理。
  • v4 依赖 APK 内已有的 v2 或 v3 签名;idsig 存在不能替代 APK 内部方案与证书验证。
  • 绑定清单至少记录 APK 与 idsig 的 SHA-256、字节数、工具版本、证书摘要、候选角色和生成时间。
  • 签名后任何 APK 修改都会产生新候选,旧 idsig 与旧验签回执不得通过改名继续复用。
  • base、split、测试包和渠道包要按实际安装会话分别登记,不能用一个 idsig 代表整个版本集合。
  • 门禁通过只证明工件配对与已记录验证结果一致,不证明业务安全、设备兼容或商店最终交付内容。

先给结论:APK 与 idsig 必须形成不可拆分的证据对

APK Signature Scheme v4 为增量安装场景生成独立 idsig。它不是可以按版本名称随意复用的通用签名文件,而是由具体 APK 内容与签名信息派生的伴随工件。发布系统必须把 apkSha256 与 idsigSha256 放进同一记录,并用候选标识锁定两者,不能只检查目录里是否存在同名后缀。

最小回执还要说明是谁、用哪个 apksigner 版本、基于哪个已签名 APK 生成,APK 内部 v2 或 v3 验证结果是什么,以及这对工件准备走哪条安装路径。idsig 单独存在只能证明流水线留下了一个文件,不能证明它属于当前候选,更不能替代 APK 的签名证书和更新身份。

门禁应以失败关闭为原则。APK 缺失、idsig 缺失、路径重复、摘要与批准清单不符、同一 idsig 被多个候选引用、生成后 APK 又被修改,任何一项都阻止进入增量安装步骤。修复动作是重新定位最终 APK、重新生成 idsig、重新验签和保存新回执,不是改文件名或覆盖摘要字段。

APK 与 idsig 成对回执的必需字段
字段回答的问题失败示例不能单独证明
apkSha256idsig 绑定哪个 APK 字节候选重签后摘要变化签名方案有效
idsigSha256伴随文件身份是什么同名文件被覆盖属于当前 APK
toolVersion由哪个工具规则生成环境升级未登记工具没有缺陷
signerDigest批准的签名身份是谁测试证书混入密钥治理完整
artifactRolebase、split 或测试对象上传包替代交付包安装会话正确
installPath在哪条路径使用普通安装误用回执设备已经兼容

理解 v4 的职责:支持增量安装,不接管 APK 内部身份

AOSP 的 v4 方案说明 idsig 独立于 APK 保存,并用于支持增量安装所需的数据验证。其设计依赖配套的 v2 或 v3 签名,因此验收不能只运行一次 v4 生成命令。流水线要先确认 APK 内部签名方案和证书符合项目策略,再为该准确文件产生伴随 idsig。

v4 与 v1 的条目摘要模型、v2 或 v3 的 APK Signing Block 角色不同。idsig 的存在不意味着 APK 本体可以没有内部签名,也不意味着所有系统版本都走 v4 路径。报告应分开记录 v2、v3、v4 的 present、verified、failed 或 not-required 状态,并注明目标系统与安装方式。

把 idsig 称为发布包身份证会造成错误推论。Android 的更新身份仍建立在应用签名、applicationId、versionCode 和平台规则上;idsig 只是特定安装路径的伴随验证材料。即使 idsig 配对正确,包名、版本、证书、split 集合或用户设备状态不满足要求,安装与更新仍可能失败。

签名层与证据职责
工件位置主要职责验收回执
v2 或 v3APK 内部签名块APK 内容与签名身份apksigner 原始输出
v4独立 idsig增量安装数据验证支持成对摘要与生成日志
应用身份包名、证书与版本更新资格判断manifest 和证书摘要
安装会话base 与 split 集合一次提交的原子边界会话工件清单
候选归档发布存储冻结准确字节不可变对象标识
设备验证目标设备真实安装与升级行为设备和系统矩阵

先冻结最终 APK,再生成 idsig 和成对摘要

流水线顺序必须固定为构建、必要后处理、对齐、内部签名、apksigner 验证、候选摘要冻结、v4 idsig 生成、成对摘要登记和安装验证。apksigner 文档明确签名后不能再修改 APK;任何注入、重压缩、资源改写或重新签名都会改变候选,旧 idsig 与旧验证输出同时失效。

生成任务要从内容寻址的候选位置读取 APK,而不是从 latest.apk、工作区通配符或可被覆盖的临时目录读取。输入路径在执行前解析为明确文件,记录文件大小和 SHA-256;生成完成后再次计算 APK 摘要,确认生成过程期间输入没有变化,再计算 idsig 摘要并原子写入配对清单。

原子性不是把两个文件放进同一目录就结束。配对清单应先写临时文件,包含完整摘要和工具回执,完成校验后一次重命名为正式状态;下游只能读取已完成记录。若进程中断留下 APK 或 idsig 单件,恢复逻辑应重新验证原始候选,而不是根据时间戳猜测哪两个文件属于同一轮。

文件名、时间戳和 versionName 都不能代替内容绑定

同名 APK 在重构建、重签名或渠道处理后可能拥有完全不同字节,idsig 文件名也可以被人工复制。仅比较 basename、mtime 或目录位置无法证明配对。门禁必须以加密摘要和不可变候选 ID 为权威,文件名只用于可读展示,时间戳只用于审计排序。

versionName 是面向用户的字符串,不能承担唯一性;versionCode 是平台更新判断的一部分,也不能区分同一版本码下的不同构建。配对记录应同时保存 applicationId、versionCode、artifactRole 和 APK 摘要,任何字段冲突都进入人工调查,不能因为版本号相同就复用 idsig。

重复使用检测至少覆盖两种情况:同一个 idsig 路径被多个候选记录引用,以及相同 idsig 摘要出现在不同 APK 摘要的记录中。前者往往是流水线覆盖或软链接错误,后者需要确认是否发生错误复用。门禁输出差异即可,不能直接写成攻击已发生或平台一定接受错误组合。

常见错配信号与处置
信号观察事实错误做法正确处置
同名不同 APK 摘要候选字节已变化保留旧 idsig重新生成并验签
同 idsig 路径多次引用记录共享可变文件只改清单名称复制到不可变对象并重算
同 idsig 摘要配不同 APK伴随工件疑似复用按版本号放行阻止并调查来源
APK 生成前后摘要变化并发修改或后处理采用最后一次摘要废弃整轮产物
证书摘要不符合策略签名身份不匹配只看 v4 成功拒绝候选
工具版本未登记生成规则不可复算沿用历史结论固定工具并重建

base、split 与安装会话需要逐项配对和整体约束

一个版本可能包含 base APK 与多个 split APK。PackageInstaller 对一次 split 安装会话要求包名、versionCode 和签名证书等保持一致。发布清单不能用 base 的 idsig 代表所有 split,也不能把测试设备只安装 base 的成功回执外推到完整配置集合。每个实际参与增量安装的 APK 都要有独立配对记录。

会话级门禁先逐项验证 apkSha256、idsigSha256 和内部签名,再验证集合约束,包括唯一 base、没有重复 split name、applicationId 一致、versionCode 一致、签名者符合策略以及所需配置完整。逐项都通过但集合混入另一个版本的 split,仍然必须失败。

如果商店服务在服务器端生成设备定向 split,本地上传工件与最终交付对象的角色不同。没有平台侧导出与抽样证据时,不能声称本地 idsig 覆盖了商店产生的所有对象。报告应明确 artifactRole 是本地测试、直接分发还是平台交付,并只对实际观察到的工件下结论。

更新验收还要核对包名、版本和签名身份

Android 更新要求新旧版本保持 applicationId,并满足兼容签名或轮换证明以及可接受的 versionCode。idsig 成对摘要不能替代这些条件。更新测试应先保存已安装基线的包名、版本与证书,再核对新候选内部签名,最后在目标系统上执行真实更新路径并记录结果。

apksigner 可以报告方案覆盖与证书信息,流水线应保存完整原始输出、工具版本、退出状态和 APK 摘要。只截取 verified 一行会丢失签名方案、警告和 signer 细节。v4 配对清单应引用这份内部签名回执的摘要,确保 idsig 没有被绑定到未经批准的测试证书候选。

证书轮换场景更不能靠文件名推断。项目要记录允许的当前证书、轮换历史、目标系统范围和旧版本升级路径。若更新失败,需要区分 idsig 错配、内部签名不兼容、版本条件、split 会话或设备策略,不能把所有错误都归因于加固或增量安装机制。

更新与增量安装的分层门禁
门禁层关键输入通过含义仍未证明
工件配对APK 与 idsig 摘要记录引用同一对字节内部签名有效
内部验签方案与证书候选签名符合工具结果更新一定成功
更新身份包名、版本、证书满足已审查身份策略split 集合完整
会话集合base 与 splits本次工件约束一致设备均兼容
设备安装系统与安装路径该矩阵真实执行所有用户结果
业务验证启动与关键流程被测流程可用不存在安全风险

归档和缓存要使用内容地址,避免可变别名串包

发布存储应以 APK SHA-256 或不可变 release ID 组织对象,再在该对象下保存 idsig、验签回执和配对清单。latest、stable 或渠道名只作为指针,切换时必须指向完整不可变集合。下游任务拿到指针后立即解析为具体对象,不能在长任务中反复读取可能变化的别名。

缓存键至少包含 APK 摘要、apksigner 版本、v4 生成参数和签名身份策略版本。只按 versionCode 缓存 idsig 会在重复构建时返回旧工件;只按文件路径缓存会在原位覆盖后失效。缓存命中后仍应重算当前 APK 和 idsig 摘要,并与缓存记录比较。

回滚必须切换整个配对集合,而不是只恢复 APK。若历史 release 缺少 idsig 或内部签名回执,回滚到非增量安装路径并明确记录限制,不能临时拼接其他版本的伴随文件。生产事故中保持证据完整比追求复用速度更重要,否则后续无法判断设备实际收到哪一对工件。

用只读脚本生成 APK 与 idsig 的成对摘要清单

下面的 Python 示例读取一份公开安全的 pairs.json,逐条解析 APK 和 idsig 路径,拒绝缺失文件、重复路径、重复候选 ID、摘要预期不符和同一 idsig 摘要绑定不同 APK。它只读取文件并输出 JSON,不执行安装、签名、密钥访问或包内容提取,适合放在生成与发布之间。

输入清单中的 approved_apk_sha256 可以来自前一阶段冻结候选的不可变回执。脚本重新计算实际摘要,若不一致立即失败;输出包含路径名称、大小和双摘要,供后续工具版本、证书摘要与安装路径回执引用。真实流水线还应限制文件大小、固定根目录并验证清单签名或存储权限。

脚本通过只表示本次输入集合内没有发现所列配对差异,不代表 idsig 的内部语义、APK 签名、split 集合或设备安装已经通过。后续仍要运行 apksigner 验证,使用批准的增量安装工具链,并把真实命令输出绑定到相同 APK 摘要。

APK 与 idsig 只读成对摘要检查器
import hashlib
import json
import sys
from pathlib import Path

if len(sys.argv) != 2:
    raise SystemExit("usage: bind_idsig_pairs.py pairs.json")
manifest_path = Path(sys.argv[1]).resolve()
if not manifest_path.is_file():
    raise SystemExit(f"pair manifest missing: {manifest_path.name}")
with manifest_path.open("r", encoding="utf-8") as stream:
    requested_pairs = json.load(stream)
if not isinstance(requested_pairs, list) or not requested_pairs:
    raise SystemExit("pair manifest must be a non-empty JSON array")

def sha256_file(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for block in iter(lambda: stream.read(1024 * 1024), b""):
            digest.update(block)
    return digest.hexdigest()

seen_ids = set()
seen_paths = set()
idsig_bindings = {}
results = []
for index, item in enumerate(requested_pairs):
    if not isinstance(item, dict):
        raise SystemExit(f"pair {index} must be an object")
    candidate_id = str(item.get("candidate_id", "")).strip()
    apk_path = Path(str(item.get("apk", ""))).resolve()
    idsig_path = Path(str(item.get("idsig", ""))).resolve()
    if not candidate_id or candidate_id in seen_ids:
        raise SystemExit(f"missing or duplicate candidate_id at pair {index}")
    if apk_path == idsig_path or apk_path in seen_paths or idsig_path in seen_paths:
        raise SystemExit(f"reused artifact path at pair {index}")
    if not apk_path.is_file() or not idsig_path.is_file():
        raise SystemExit(f"missing APK or idsig at pair {index}")
    apk_sha256 = sha256_file(apk_path)
    idsig_sha256 = sha256_file(idsig_path)
    approved = str(item.get("approved_apk_sha256", "")).lower()
    if approved and approved != apk_sha256:
        raise SystemExit(f"APK digest differs from approval at pair {index}")
    previous_apk = idsig_bindings.get(idsig_sha256)
    if previous_apk is not None and previous_apk != apk_sha256:
        raise SystemExit(f"idsig digest is bound to different APKs at pair {index}")
    seen_ids.add(candidate_id)
    seen_paths.update((apk_path, idsig_path))
    idsig_bindings[idsig_sha256] = apk_sha256
    results.append({
        "candidate_id": candidate_id,
        "apk_name": apk_path.name,
        "apk_bytes": apk_path.stat().st_size,
        "apk_sha256": apk_sha256,
        "idsig_name": idsig_path.name,
        "idsig_bytes": idsig_path.stat().st_size,
        "idsig_sha256": idsig_sha256,
    })
print(json.dumps({"status": "paired", "pairs": results}, indent=2))

证据包和行动入口必须指向同一最终候选

NIST SP 800-218 SSDF 强调保留来源、构建、验证和变更证据。对应 v4 idsig,证据包应包含 APK 与 idsig 双摘要、apksigner 版本、生成命令参数、内部签名原始回执、证书摘要、构建来源、安装路径、目标设备和异常记录。所有材料通过同一候选 ID 关联。

公开报告必须区分事实、工程判断和限制。可以写某一 APK 与 idsig 的实际摘要、文件大小、生成工具与真实安装回执;不能从文件存在推导增量安装已成功,也不能把一次设备通过写成全平台兼容。没有项目证据时不提供客户、排名、流量、性能或攻击阻断数字。

整理候选前可先查看[APK 敏感调试资产清单](/zh-cn/articles/apk-sensitive-debug-asset-inventory/),确认归档目录没有混入非发布工件。需要提交 APK 与配套安装材料,可从[御盾中央平台申请加固服务](https://www.leonadev.com/console/)。登录、注册、价格、购买和控制台操作统一由中央平台承接,提交前移除私钥、令牌和客户数据。

  • 最终 APK 已冻结并记录 SHA-256 与字节数
  • idsig 在 APK 内部签名验证后生成并记录 SHA-256
  • 生成前后 APK 摘要一致,不存在并发后处理
  • apksigner 版本、方案结果和证书摘要已保存
  • 同一路径或 idsig 摘要没有绑定不同 APK 候选
  • base 与每个实际 split 分别建立配对记录
  • 包名、versionCode、签名身份和 artifactRole 一致
  • 真实安装回执只覆盖已测试设备、系统和路径

事实依据与适用边界

以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。

本文判断事实或工程依据适用限制
v4 使用独立 idsig 支持增量安装,并依赖配套的 v2 或 v3 签名。APK Signature Scheme v4 说明 v4 工件形态、用途和对内部签名方案的依赖。idsig 不能替代 APK 内部签名,也不是普通商店上传产物的唯一身份。
Android 使用应用签名建立更新身份,不同签名方案覆盖相应文件区域与平台范围。AOSP app signing 说明应用签名总体职责和签名方案演进。签名有效只证明完整性与签名者身份范围,不证明业务代码安全。
apksigner 可以签名、验证方案覆盖与证书,签名后 APK 不应继续修改。Android apksigner 说明 sign、verify、证书输出和签名后修改边界。工具成功不证明 keystore 治理、渠道归档或 idsig 配对正确。
split 安装会话需要 base、包名、版本与签名证书满足平台一致性约束。Android PackageInstaller 描述 full、inherit 与 split 安装会话及相关约束。API 约束不证明平台服务器产生的所有 split 已被本地抽样。
应用更新需要保持 applicationId、兼容签名或轮换证明并满足 versionCode 条件。How Android app updates work 说明 Android 应用更新的身份与版本要求。更新条件不等于业务层防重放,也不证明某个 idsig 已正确配对。
安全发布应保存来源、构建、验证、变更与供应链风险证据。NIST SP 800-218 SSDF 给出组织级安全软件开发和发布实践。SSDF 不定义 idsig 清单格式,也不能证明某个候选已通过安装。
APK 与 idsig 应以双摘要和不可变候选 ID 绑定,而不是只依赖名称或时间戳。工程判断:可变别名和时间戳无法区分同版本重复构建或签名后修改。双摘要一致不证明 idsig 内部语义或设备安装结果正确。
同一 idsig 路径或摘要绑定不同 APK 时应阻止自动发布并调查来源。工程判断:这种关系可能来自缓存键错误、覆盖、复制或候选角色混淆。发现关系异常不等于攻击已经发生,也不声称平台一定接受错配。
每个参与安装的 base 或 split APK 都需要独立配对记录和会话级集合检查。项目证据尚未接入:这是发布前必须验证的门禁,不代表当前流水线已实现。本地逐项通过不能外推为商店最终设备定向交付对象均已验证。
本文不提供性能、兼容、客户、排名、收录或攻击阻断结论。项目证据尚未接入:没有真实候选、设备矩阵、平台回执或公开运营数据。文章只给出可复核配对方法,不能作为具体产品效果证明。

工程常见问题

idsig 文件存在是否说明 APK 已支持增量安装?

不能。还要确认它绑定当前 APK,APK 内部 v2 或 v3 签名有效,并在目标系统和实际安装路径取得真实回执。

APK 文件名和 idsig 文件名一致就可以配对吗?

不可以。文件名可被覆盖或复制,必须记录 APK 与 idsig 的 SHA-256、工具版本、证书和不可变候选 ID。

重签 APK 后可以继续使用原来的 idsig 吗?

不可以。重签会改变 APK 字节与签名信息,应重新生成 idsig、重新验签并建立新的成对回执。

一个版本的所有 split 可以共用一个 idsig 吗?

不能这样管理。每个实际参与安装的 APK 要单独配对,再在会话层核对 base、包名、版本和签名身份。

双摘要相同是否足以证明安装会成功?

不足。它只证明记录指向相同字节,还需内部签名、应用身份、split 集合、系统与真实安装验证。

为什么不能只按 versionCode 缓存 idsig?

同一 versionCode 可能重复构建、重签或渠道处理,字节不同。缓存键必须包含 APK 摘要和工具及策略版本。

脚本发现相同 idsig 摘要绑定不同 APK 是否等于被攻击?

不等于。它是需要阻止发布并调查的错配信号,原因也可能是缓存、复制或归档流程错误。

商店上传包的 idsig 能代表最终用户收到的 split 吗?

不能直接外推。服务器生成的设备定向对象角色不同,只有取得平台侧工件与抽样回执后才能下对应结论。

想用自己的 App 验证?

提交候选包、目标系统和关键业务路径,申请御盾 PoC 与兼容性评估。

继续阅读: APK、DEX 与签名完整性如何一起验收