先看结论与判断条件

  • 多个当前 signer 描述一个候选 APK 当下由哪些证书共同签名,lineage 描述证书随版本轮换的有序授权关系。
  • 无序证书摘要集合无法表达前任、继任、轮换顺序和目标系统,因此不能替代 lineage 证明。
  • 升级门禁必须同时核对 applicationId、versionCode、当前 signer、lineage、签名方案和历史安装基线。
  • v3.1 的轮换能力与目标 SDK、平台验证路径和 v4 关联有关,不能从一台新系统设备外推全部安装基础。
  • Play 上传密钥与应用签名密钥责任不同,上传成功不能证明设备收到的 APK 使用预期发布身份。
  • apksigner 验证成功只证明工具覆盖的签名事实,发布回执仍需绑定候选摘要、来源和不可变审核结果。

先给结论:集合与有序关系不能互换

当验签工具报告同一 APK 存在多个当前 signer 时,描述的是这个候选当下的签名者集合。证书轮换 lineage 则回答另一个问题:旧证书如何授权新证书接替,以及平台在不同版本和系统条件下如何验证升级身份。一个是并列的当前状态,一个是跨版本的有序历史,数据结构、门禁条件和失败后果都不同。

把证书 SHA-256 摘要排序后比较,只能判断两个报告是否包含相同字符串,无法说明哪个证书是前任、哪个是继任,也无法证明轮换证明有效。相反,只保存 lineage 的最后一个证书,也会丢失旧安装基础需要的历史关系。发布系统需要分别存 currentSigners 和 lineage,并把两者与签名方案、目标系统和基线 APK 绑定。

本文只解释多 signer 与 lineage 的身份模型和发布门禁,不重复讲如何创建轮换密钥,也不推断任何实际 APK 已完成轮换。没有真实候选、历史安装包、分发渠道和设备回执时,不能声称升级连续性通过、旧系统兼容或密钥管理正确。工具输出、工程判断和项目结论必须分开。

多 signer 与 lineage 的核心差异
维度多个当前 signer证书 lineage门禁要求
表达对象同一候选的当前签名者跨版本轮换关系分别保存
顺序通常作为当前集合观察顺序不可丢失拒绝排序替代
时间含义当前版本状态历史到当前的授权链绑定版本基线
升级判断需按平台规则核对用于轮换连续性真实安装测试
允许策略当前 signer 规则历史与当前规则不可共用无序白名单
主要误区把多个证书当轮换只保存最后证书保留结构与证据

应用签名身份服务于更新连续性,不等于业务安全

Android 使用应用签名建立更新身份,不同签名方案覆盖的文件区域和平台验证路径有所不同。更新还要求 applicationId 与 versionCode 等条件满足平台规则。签名验证回答候选是否由相应签名身份产生且受覆盖字节未被改动,不回答业务代码是否安全、服务端授权是否正确,也不证明 APK 已经过加固。

升级连续性是两个或更多安装状态之间的关系,不能只检查新 APK。门禁至少需要已发布基线、待发布候选、包名、版本、证书信息和目标系统。若团队只归档候选验签报告,没有保存用户实际安装基线,就无法证明某条轮换关系覆盖真实升级路径,也无法区分全新安装成功与覆盖安装成功。

多 signer 和 lineage 都应进入身份报告,但不应被压成一个 certificates 数组。报告需要明确字段语义,例如 currentSigners 表示候选当前签名者,lineage 表示有序证书节点,verifiedSchemes 表示工具观察到的方案。字段分开后,策略才能对多 signer、单 signer 加 lineage 和无 lineage 候选采用不同审核。

APK 更新身份需要同时保存的对象
对象回答的问题缺失后果证据来源
基线 APK用户从哪个身份升级无法验证连续性已发布归档
候选 APK准备交付哪个文件报告可能错配候选摘要
applicationId是否属于同一包身份升级对象不明确最终 Manifest
versionCode版本关系是否可接受升级与降级混淆最终 Manifest
currentSigners当前由谁签名多 signer 被遗漏apksigner 报告
lineage证书如何有序接替轮换历史丢失签名块与证明

多个当前 signer 是同一候选的并列身份事实

多 signer 报告首先要求逐个保存证书摘要,不能只取第一项。数组顺序若来自工具展示,不应被擅自解释成轮换先后;真正需要审核的是签名方案是否支持该结构、每个 signer 是否属于批准身份,以及基线与候选在目标平台上是否满足更新规则。任何一个 signer 未知,都不能通过把集合排序后与宽泛白名单相交来放行。

允许策略要明确是全量匹配还是任一匹配。对多 signer 候选,如果策略只要求至少一个摘要在允许列表,另一个未批准 signer 可能被忽略;如果策略错误地要求和历史 lineage 全量相等,又会把不同身份模型混为一体。更稳妥的设计是先根据报告类型选择规则,再对 currentSigners 做精确集合审核,并单独处理 lineage。

多 signer 还需要和最终 APK 绑定。签名前后任何字节修改都可能使相应签名失效,apksigner 文档明确要求签名后不再修改 APK。渠道工具、对齐、资源注入或重新打包若改变文件,就产生新的候选身份。门禁应在最终交付对象上重新验签,不能复用母包或中间包的 signer 报告。

lineage 是不能打乱的轮换授权历史

lineage 的价值在于表达证书接替关系,而不是收集所有曾出现过的证书。前任与继任位置会影响平台如何理解轮换,顺序丢失后就无法判断候选是否沿批准路径演进。CI 导入 lineage 时应验证每个节点唯一、序号连续、最后节点与候选当前身份关系符合预期,并保留原始证明摘要。

轮换历史也不能被简化为旧证书和新证书两个字段。多次轮换时,中间节点承担连续性证据;不同平台版本可能使用不同验证路径,分发渠道也可能对历史安装基础有额外约束。门禁需要保存完整节点序列、轮换目标条件和代表设备升级结果,不能只在最新系统上验证一次。

v3.1 文档描述了签名轮换、最低轮换 SDK 与 v4 的关联,这说明轮换结论必须带平台范围。该文档不证明某个项目的 lineage 已正确生成,也不保证所有渠道都交付相同派生 APK。项目应把候选、lineage、目标 SDK、签名方案和分发回执作为一组不可拆分证据。

lineage 节点审计的最小字段
字段用途错误示例门禁动作
sequence保存证书先后排序后重新编号要求连续且不可变
certificateSha256标识证书只存短指纹保存完整规范摘要
role区分前任与当前所有节点都写 current按关系生成
targetSdkContext限定轮换路径遗漏系统范围绑定测试矩阵
proofDigest绑定原始轮换证明只存文本列表保存不可变摘要
candidateDigest绑定最终 APK使用中间包摘要最终验签后登记

系统版本与分发渠道决定需要验证的升级矩阵

轮换不是一个与系统版本无关的布尔能力。v3.1 和相关签名方案在不同平台版本上有特定验证行为,旧设备可能依赖不同签名表示。文章不在没有设备回执时给出通用兼容结论。实际门禁应从应用 minSdk、目标用户系统分布和分发方式选择代表基线,分别验证全新安装、旧证书版本升级和轮换后继续升级。

Google Play 更新还要求应用身份和版本条件满足规则。商店接受上传不等于用户设备已完成升级,API 或控制台回执也不能替代最终派生 APK 身份。若使用 Play App Signing,团队需要区分上传候选的证书与设备交付包的应用签名证书,并获取能绑定发布版本的真实回执。

渠道差异必须显式记录。企业分发、应用商店和本地安装可能使用不同交付流程,历史用户的基线证书也可能不同。一个渠道的 lineage 通过不能外推另一个渠道。矩阵应包含渠道、基线 APK、候选 APK、系统版本、安装动作、验签结果和失败原因,避免只保存最终页面截图。

升级连续性测试矩阵
场景基线候选核心断言
全新安装无已安装应用当前发布候选候选可按预期安装
旧证书升级轮换前正式版本轮换后候选历史关系被接受
轮换后升级上一轮换版本新候选当前身份连续
多 signer 基线多个当前 signer 包目标候选平台规则与策略一致
旧系统路径目标最低系统代表同一候选旧验证路径明确
商店派生包真实已发布版本设备交付版本商店身份和版本绑定

Play 上传密钥与应用签名密钥不能混入同一白名单

Android 发布文档区分应用签名密钥、上传密钥和证书责任。上传密钥用于向商店证明上传者身份,应用签名密钥用于设备实际安装包的发布身份。若 CI 只验证上传 AAB 或上传 APK 的证书,就不能据此宣称用户收到的派生 APK 使用同一证书。策略字段应明确 artifactRole,避免上传身份和发布身份混用。

密钥允许列表也要按环境和渠道分层。开发、内部测试、上传和生产应用签名证书不能放在一个无角色数组中,再用任意命中放行。每个允许项需要角色、渠道、包名、有效版本范围和审核来源。多 signer 候选必须满足该角色下的完整规则,lineage 则需要符合批准的有序关系。

Play App Signing 责任分离不改变候选取证要求。团队仍需保存上传对象摘要、商店发布版本、设备交付身份和升级回执。文档说明机制,不能确认某个实际包使用正确生产证书。只有从最终交付对象取得的证据,才能进入设备侧 currentSigners 和 lineage 审核。

用结构化检查拒绝把无序集合冒充 lineage

CI 不应直接解析一段面向人的控制台文本并依赖行顺序。更可靠的流程是固定 apksigner 版本,将验证结果转换为结构化 JSON,保存包名、versionCode、候选摘要、当前 signer 和带 sequence 的 lineage 节点。转换器本身需要测试,原始输出和结构化报告都要归档。

下面的 Python 示例读取脱敏身份报告,验证摘要格式、currentSigners 唯一性和 lineage 序号连续性。若输入只有 unorderedCertificates,脚本明确拒绝把它当成 lineage;若有多个当前 signer 与 lineage 同时出现,脚本保留两种结构并要求人工确认,不自行推断谁是继任证书。它不读取私钥、不签名也不修改 APK。

输出中的 identityModel 只描述报告结构。multiple-current-signers 表示当前集合包含多个证书,ordered-lineage 表示存在有效顺序字段,single-current-signer 表示当前集合只有一个证书。任何模型仍需和 apksigner 验证、基线 APK、目标系统与安装回执结合,不能凭 JSON 通过就登记升级连续性。

当前 signer 与有序 lineage 身份检查器
from pathlib import Path
import json
import re
import sys

if len(sys.argv) != 2:
    raise SystemExit("usage: inspect_signing_identity.py report.json")

report_path = Path(sys.argv[1]).resolve()
if not report_path.is_file():
    raise SystemExit(f"signing report missing: {report_path.name}")

report = json.loads(report_path.read_text(encoding="utf-8"))
package_name = report.get("package_name")
version_code = report.get("version_code")
candidate_sha256 = report.get("candidate_sha256")
if not isinstance(package_name, str) or not package_name:
    raise SystemExit("package_name is required")
if not isinstance(version_code, int) or version_code < 1:
    raise SystemExit("version_code must be a positive integer")

digest_pattern = re.compile(r"^[0-9a-f]{64}$")
def checked_digest(value, field):
    normalized = str(value or "").lower().replace(":", "")
    if not digest_pattern.fullmatch(normalized):
        raise SystemExit(f"{field} must be a full SHA-256 certificate digest")
    return normalized

candidate_digest = checked_digest(candidate_sha256, "candidate_sha256")
current = report.get("current_signers")
if not isinstance(current, list) or not current:
    raise SystemExit("current_signers must be a non-empty array")
current_signers = [checked_digest(value, "current_signers") for value in current]
if len(set(current_signers)) != len(current_signers):
    raise SystemExit("current_signers contains duplicate certificates")

if report.get("unordered_certificates") and not report.get("lineage"):
    raise SystemExit("an unordered certificate collection is not a lineage")

raw_lineage = report.get("lineage") or []
if not isinstance(raw_lineage, list):
    raise SystemExit("lineage must be an ordered array")
lineage = []
for index, node in enumerate(raw_lineage):
    if not isinstance(node, dict):
        raise SystemExit(f"lineage node {index} is not an object")
    sequence = node.get("sequence")
    if sequence != index:
        raise SystemExit(f"lineage node {index} has a missing or reordered sequence")
    lineage.append({"sequence": sequence, "certificate_sha256": checked_digest(node.get("certificate_sha256"), "lineage certificate")})
if len({node["certificate_sha256"] for node in lineage}) != len(lineage):
    raise SystemExit("lineage contains a repeated certificate")

if len(current_signers) > 1:
    identity_model = "multiple-current-signers"
elif lineage:
    identity_model = "ordered-lineage"
else:
    identity_model = "single-current-signer"
manual_review = []
if len(current_signers) > 1 and lineage:
    manual_review.append("do not infer lineage order from the current signer set")
if lineage and lineage[-1]["certificate_sha256"] not in current_signers:
    manual_review.append("confirm the relation between the final lineage node and current signer")

print(json.dumps({"package_name": package_name, "version_code": version_code, "candidate_sha256": candidate_digest, "identity_model": identity_model, "current_signers": sorted(current_signers), "lineage": lineage, "manual_review": manual_review}, ensure_ascii=False, indent=2))

发布门禁要校验结构、角色与真实候选

发布策略首先验证报告完整性:候选摘要、包名、版本、签名方案、currentSigners、lineage、apksigner 版本和原始输出均存在。接着按渠道和 artifactRole 选择允许策略,再分别校验当前 signer 集合和 lineage 顺序。任何未知证书、重复节点、顺序缺口或候选错配都应阻止自动放行。

apksigner 可以签名和验证 APK,并报告签名方案与证书,但工具成功不证明 keystore 管理、上传角色、渠道处理和归档流程正确。门禁要确认验签发生在最终文件上,签名后没有修改,并把工具回执与文件摘要一起保存。若渠道后处理改变文件,必须重新进入全部身份检查。

审核结论应使用精确语言。可以登记候选报告包含几个当前 signer、lineage 有哪些有序节点、哪些平台路径完成了升级测试;不能写成密钥绝对安全、所有设备兼容、无法重签或攻击已阻断。没有真实候选和设备回执时,本文只提供结构与门禁方法,不提供项目通过结论。

签名身份发布门禁顺序
门禁输入失败条件通过含义
候选绑定APK 摘要与版本报告指向其他文件对象一致
工具验签apksigner 原始回执方案或证书异常工具观察有效
当前 signer精确证书集合未知、缺失或重复当前集合获批
lineage有序证书节点顺序缺口或关系未知结构符合策略
角色分离上传与应用签名身份白名单角色混用责任匹配
升级矩阵基线、候选与设备代表路径失败仅已测路径通过

证据包要支持回滚、复核与后续轮换

NIST SSDF 要求把来源、构建、验证、变更和供应链风险纳入安全开发流程。对应签名身份,证据包应包含最终 APK、文件摘要、apksigner 版本和原始输出、结构化身份报告、允许策略、lineage 证明摘要、构建来源、分发角色及升级矩阵。每个文件都应有不可变摘要,避免报告和候选分离。

发生密钥事件、渠道切换或再次轮换时,旧证据包是决策输入。团队需要知道哪些用户基线仍使用旧身份、哪些渠道由商店托管应用签名、哪些系统路径依赖历史证明。若过去只保留证书集合而丢弃顺序和角色,就无法可靠规划后续变化,也无法解释一次升级失败属于包名、版本、签名还是渠道。

准备签名身份评审时,可先查看[APK、DEX 与签名完整性指南](/zh-cn/apk-dex-signing-integrity/),整理最终 APK、基线版本、apksigner 报告、currentSigners、lineage 和渠道角色。需要提交实际候选,可从[御盾中央平台申请加固服务](https://www.leonadev.com/console/)。材料应脱敏,登录、价格、购买和控制台操作统一由中央平台承接。

  • currentSigners 与 lineage 使用不同字段和审核规则
  • lineage 节点顺序、证书摘要和证明摘要均被保存
  • 上传密钥、应用签名密钥和渠道角色没有混用
  • 基线与候选 APK 绑定包名、版本、签名和文件摘要
  • 旧系统、新系统与真实分发渠道分别验证升级路径
  • apksigner 原始输出和结构化报告同时归档
  • 结论只覆盖已验证候选、设备和升级路径

事实依据与适用边界

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

本文判断事实或工程依据适用限制
v3.1 描述证书轮换、最低轮换 SDK 和与 v4 的关联。APK Signature Scheme v3.1 说明相关轮换结构与平台条件。规范能力不证明某个项目 lineage 正确,也不保证所有分发渠道覆盖真实历史安装基础。
Android 使用应用签名建立更新身份,并由不同签名方案覆盖相应文件区域与平台版本。AOSP app signing 说明 Android 应用签名和签名方案的总体职责。签名有效不证明业务代码安全、服务端授权正确或 APK 已达到具体防护强度。
apksigner 可以签名、验证签名方案与证书,并要求签名后不再修改 APK。Android apksigner 说明工具的 sign、verify、证书与签名后修改边界。工具成功不证明密钥治理、渠道处理、候选归档和升级矩阵正确。
发布流程需要区分应用签名密钥、上传密钥和证书责任。Sign your Android app 说明应用签名、上传密钥和 Play App Signing 责任。文档不能确认某个实际交付 APK 使用了批准的生产证书。
应用更新需要保持包身份、可接受签名关系和版本条件。How Android app updates work 说明应用更新的身份与版本条件。平台更新规则不等于业务防重放,也不证明商店已向所有设备交付候选。
安全发布应保存来源、构建、验证和变更证据,并纳入供应链风险。NIST SP 800-218 SSDF 给出组织级安全软件开发与发布实践。SSDF 不定义具体 signer 或 lineage 字段,也不证明某个候选已通过。
多个当前 signer 与 lineage 必须采用不同数据结构和策略门禁。工程判断:无序当前集合不能表达跨版本有序授权关系,合并字段会丢失责任语义。结构分离不自动证明平台接受升级,仍需真实签名和安装证据。
lineage 节点顺序必须保持,不能通过摘要排序重新构造。工程判断:前任与继任关系依赖顺序,排序只提供集合相等而不提供授权方向。顺序完整仍不证明轮换证明真实、密钥受控或渠道交付正确。
发布证据需要绑定最终候选、基线、目标系统、渠道角色与升级回执。项目证据尚未接入:本文列出必须保存的字段,不声称当前 APK 已完成升级验收。静态报告、上传成功或全新安装不能单独证明升级连续性。
本文不提供客户、性能、兼容覆盖或攻击阻断结论。项目证据尚未接入:没有真实候选、历史基线和设备矩阵时不扩张事实。公开代码和门禁表仅用于诊断规划,不能替代密钥管理和候选验收。

工程常见问题

APK 有两个 signer 是否说明已经完成证书轮换?

不能。多个当前 signer 描述当前候选的签名者集合,轮换需要有序 lineage 和对应平台、基线及安装证据。

把所有证书摘要排序后比较可以校验 lineage 吗?

不可以。排序会丢失前任、继任和轮换方向,只能说明两个集合包含相同摘要。lineage 必须保留原始顺序和证明。

lineage 最后一个证书等于当前 signer 就足够了吗?

不够。还要验证完整节点顺序、证明、签名方案、目标系统、基线 APK、分发渠道和真实升级路径。

apksigner verify 成功是否证明生产签名配置正确?

不证明。它验证工具覆盖的签名事实,仍需核对候选身份、允许证书、密钥角色、渠道后处理和归档来源。

Play 上传密钥可以放进生产应用签名白名单吗?

不应混用角色。上传密钥证明上传者身份,设备交付包由应用签名身份负责,策略要按 artifactRole 和渠道分别维护。

新系统升级通过能否代表旧系统也兼容?

不能。不同平台可能采用不同签名验证路径,应从应用支持范围选择代表系统,并使用真实基线逐项验证。

多 signer 策略只要求一个证书命中允许列表可以吗?

通常风险很大,因为未批准 signer 可能被忽略。策略应明确要求的完整当前集合,并和 lineage 规则分开。

结构化检查器输出 ordered-lineage 是否等于升级通过?

不等于。它只说明报告含连续有序节点,仍需 apksigner、基线候选、平台安装和渠道回执共同验证。

想用自己的 App 验证?

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

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