先看结论与判断条件
- 文件摘要是字节指纹,必须先从可信记录获得期望值;攻击者同时替换 APK 和摘要时,单独比对仍会通过。
- Android 签名验证还检查签名方案覆盖、文件完整性和签名证书,证书身份参与应用更新关系,责任不同于摘要。
- 同一签名证书可以签署多个不同 APK,同一业务版本也可能产生不同字节,因此摘要和证书必须作为两个字段共同保存。
- APK Signature Scheme v2 覆盖 APK 文件主体并处理降级保护,但不覆盖 APK 之外单独下载的模型、配置或资源。
- apksigner 验证成功只说明工具对当前文件的签名判断,不证明密钥托管、渠道归档、构建来源或业务代码安全。
- 供应链声明和 provenance 应引用当前 APK 摘要;门禁仍需核对声明签名者、构建者、证书允许列表及最终归档文件。
摘要证明字节相同,签名验证证明另一组事实
SHA-256 把 APK 文件映射为固定长度摘要。对同一组字节重复计算会得到同一结果,任何字节变化通常都会产生不同摘要。它非常适合给候选包编号、比较传输前后文件和绑定测试报告。但摘要本身没有发布者身份,拿到一份 APK 和旁边同来源的摘要文件,无法仅凭二者相等判断谁认可了该 APK。
Android 签名验证处理的是签名数据、证书和签名方案覆盖。验证器要确认当前 APK 的受保护区域没有在签名后被修改,并解析哪个证书参与签名。Android 还使用应用签名建立安装与更新身份。因此,摘要回答“是否是这组字节”,签名回答“这些字节是否满足特定签名规则并对应某个签名者”。
两者也不能互相推导。同一个证书可以签署不同版本和不同内容的 APK,所以证书相同不表示文件摘要相同;同一 APK 摘要在任何机器上计算都一致,但没有可信证书或可信外部记录时,不能说明文件来自预期发布方。工程验收应把期望摘要、实际摘要、签名方案结果和证书摘要分栏保存。
| 检查对象 | 能回答 | 不能回答 | 典型输入 |
|---|---|---|---|
| 文件 SHA-256 | 当前字节是否匹配已知值 | 谁发布并认可该值 | APK 与可信摘要 |
| 签名完整性 | 签名覆盖区域是否被修改 | 业务代码是否安全 | APK 与签名结构 |
| 签名证书 | 文件由哪个证书身份签署 | 密钥管理是否合规 | 证书摘要允许列表 |
| 签名方案 | 目标方案是否验证通过 | 外部资产是否完整 | 平台规则和目标版本 |
| 归档记录 | 回执是否对应同一候选 | 候选运行是否正确 | 版本、摘要、证书和时间 |
摘要只有连接到可信期望值才有验收意义
计算实际摘要很容易,困难在于期望摘要从哪里来。若 APK 和 expected.sha256 来自同一个未受保护下载目录,目录被替换时两个文件可以一起变化,比较依然成功。期望值应来自受控发布记录、签名供应链声明或不可混淆的候选归档,并且记录本身要有访问控制、审计与身份验证。
摘要清单需要绑定文件名之外的候选信息,包括应用标识、版本、构建编号、签名流程阶段、生成时间和归档位置。文件名容易被复用,版本字段也可能因重建保持不变;真正消除歧义的是文件摘要。若重建、重新对齐、重新压缩或重新签名导致字节变化,即使源码提交和版本号未变,也应视为新的候选身份。
摘要比较适合早失败。下载后、扫描前、测试前、交付前都可以重新计算,并拒绝不匹配文件。但通过摘要门禁后仍要继续签名验证,因为期望摘要记录可能绑定了错误签名候选,或者归档流程把调试身份和发布身份混在一起。摘要把调查范围缩小到某份字节,不替代签名者判断。
| 字段 | 用途 | 常见失误 | 校验动作 |
|---|---|---|---|
| 实际 SHA-256 | 标识当前字节 | 从日志复制了旧文件摘要 | 对待验 APK 现场计算 |
| 期望 SHA-256 | 声明允许的候选 | 与 APK 放在同一未保护目录 | 从受控发布记录读取 |
| 应用与版本 | 连接业务发布 | 仅依赖可复用文件名 | 与包内信息及流水线记录对照 |
| 生成阶段 | 区分未签名与已签名文件 | 对签名前文件留最终摘要 | 只登记最终候选 |
| 归档身份 | 定位原始文件 | 测试后重新复制或改名 | 保存不可混淆路径和回执 |
Android 应用签名还建立安装与更新身份
AOSP app signing 说明 Android 使用应用签名建立应用更新身份,并存在面向不同平台版本的签名方案。系统判断新 APK 是否可以作为已安装应用的更新时,会考虑签名关系,而不会查询项目保存的 SHA-256 文本。即便两个 APK 来自同一源码,只要签名身份不满足更新规则,也不能用摘要相近或版本相同弥补。
验收不能只记录证书主题名称。证书显示名称可重复或填写不严谨,适合比对的是签名证书的密码学摘要及其经过审批的身份记录。若工具报告多个签名者或多种方案,记录要完整保留,策略明确接受哪些证书和方案。本文不展开证书轮换流程,只强调当前候选必须与当前允许身份相匹配。
签名有效也不等于业务代码安全。合法发布密钥可以签署包含缺陷的 APK,构建环境也可能在签名前引入错误内容。签名验证证明当前文件满足签名完整性和签名者身份规则,代码审计、恶意行为分析、加固验证与设备回归仍需要独立证据。把签名通过写成“APK 安全”会扩大结论。
| 事实 | 来源 | 通过含义 | 仍需证据 |
|---|---|---|---|
| 签名验证结果 | apksigner 或平台验证器 | 签名结构和完整性满足规则 | 允许证书匹配 |
| 签名方案结果 | 工具逐方案输出 | 目标方案在当前文件有效 | 目标平台覆盖策略 |
| 证书 SHA-256 | 签名者证书解析 | 识别当前签名身份 | 证书允许记录来源 |
| 签名者数量 | 工具证书输出 | 知道文件包含哪些签名者 | 多签名者策略是否允许 |
| 更新身份关系 | 平台规则与发布记录 | 候选满足预期更新关系 | 真实设备升级回归 |
v2 签名覆盖 APK 主体,但不保护外部文件
APK Signature Scheme v2 通过 APK Signing Block 保护 APK 文件主体,并包含防止降级到较弱方案的机制。与只检查 ZIP 条目列表的自制脚本相比,标准验证器理解签名块、内容摘要和平台验证规则。发布门禁应调用可信 Android 构建工具中的验证器,而不是从 META-INF 是否存在若干文件来猜测签名状态。
Android apksigner 文档明确提醒签名后再修改 APK 会使签名失效。对齐、压缩、注入渠道字段或替换资源都应在签名前完成。若交付流程在签名后执行任何可能改写文件的步骤,必须重新验证摘要和签名;不能因为改动看似与代码无关就沿用旧回执。最终摘要应在所有字节修改和签名结束后计算。
v2 的覆盖边界是 APK。应用首次启动后下载的模型、规则、补丁或配置不在该 APK 签名保护范围内,它们需要自己的摘要、签名和更新策略。工程判断是按交付容器建立清单:APK 使用 Android 签名验证,外部资产使用独立的可信元数据和完整性机制,不能把 APK 验证结果扩展到运行期下载内容。
| 对象 | 适用验证 | 何时计算摘要 | 不能沿用 |
|---|---|---|---|
| 最终 APK | Android 签名方案与文件摘要 | 签名及所有修改完成后 | 签名前中间包回执 |
| APK 内资源 | 由 APK 签名覆盖并随包核对 | 包含在最终 APK 摘要中 | 单独旧资源摘要 |
| 外部模型 | 独立签名或可信更新元数据 | 下载完成且激活前 | APK 签名结论 |
| 远程配置 | 服务身份、签名与版本策略 | 接收后应用前 | 安装包证书结论 |
| 测试报告 | 绑定 APK 摘要和签名回执 | 测试开始与归档时 | 同版本其他候选结果 |
apksigner 回执要同时看方案和证书
Android apksigner 可以签名 APK,也可以验证签名方案覆盖并打印证书。验收侧应使用 verify、verbose 和 print-certs 等验证输出,检查命令返回状态、总体 Verified 结果、所需方案是否通过以及证书 SHA-256 是否位于允许集合。工具版本、命令参数和输入 APK 摘要应一起保存,避免回执脱离上下文。
只看命令退出成功仍可能漏掉策略问题。工具可以正确验证一个由非预期证书签署的 APK,或者文件对某些方案有效却不满足项目的目标平台策略。验证工具负责报告事实,发布策略负责决定是否接受。两者之间应有结构化解析层,禁止人工从长日志中只截取一行“Verified”。
工具验证成功也不证明 keystore 管理、签名服务访问、渠道上传和归档流程正确。签名密钥是否处于受控环境、谁批准了签名、最终上传文件是否仍为同一摘要,属于流程证据。项目证据尚未接入时,可以给出检查字段和门禁方法,不能声称某个真实候选已经通过商业签名验收。
- 保存 apksigner 版本与完整验证参数
- 验证命令返回非零时立即阻断
- 解析总体结果和每个签名方案状态
- 提取全部签名证书 SHA-256
- 证书摘要与受控允许集合比对
- 验证后的 APK 摘要再次绑定归档记录
attestation 与 provenance 把摘要连接到构建声明
in-toto Attestation Statement v1 通过 subject digest 把产物与有类型的 predicate 绑定。APK 的 SHA-256 可以作为 subject,predicate 则承载扫描、测试或签名审批等声明。这样做能降低报告引用同版本其他文件的风险,但 statement 格式不保证内容真实,仍要验证声明签名者和策略。
SLSA Provenance v1.1 把 subject、builder、buildType、外部参数和依赖材料组织进构建证明。它可以说明某份 APK 摘要与某次受控构建之间的关系,却不替代 APK 自身的 Android 签名验证。两种身份可以不同:构建证明由流水线身份签署,APK 由应用发布证书签署,门禁要分别核对。
正确的证据链是相互引用而不是彼此取代。provenance 的 subject 摘要匹配当前 APK,apksigner 确认 APK 签名与证书,允许列表确认当前证书身份,归档记录再确认交付的是同一摘要。任一环节错配都应停止放行。没有真实声明和签名回执时,只能把该项标为未接入。
| 证据 | 绑定对象 | 主要判断 | 边界 |
|---|---|---|---|
| 文件摘要 | 最终 APK 字节 | 报告是否对应当前文件 | 不识别签名者 |
| Android 签名 | APK 受保护内容和证书 | 完整性与签名身份 | 不证明构建来源 |
| in-toto statement | subject 摘要与 typed predicate | 声明对应哪个产物 | 格式不保证声明真实 |
| SLSA provenance | 产物与构建过程 | 谁用什么流程和材料构建 | 不证明运行时安全 |
| 归档回执 | 最终交付文件 | 交付与验收是否同一候选 | 不代替设备回归 |
按 SSDF 把摘要和签名结果纳入发布证据
NIST SP 800-218 SSDF 提倡在安全软件开发中保留来源、构建、验证和变更证据,并管理供应链风险。项目可以据此定义发布记录:候选如何生成,摘要何时计算,谁验证签名,允许证书从何处批准,测试和扫描绑定哪个摘要,最终交付由谁确认。SSDF 是组织级实践,不替团队选择 Android 签名策略。
失败状态必须具体。摘要不匹配表示当前字节不是期望候选;签名无效表示完整性或签名结构不满足验证;证书不允许表示签名者身份不符合当前策略;provenance 错配表示构建声明对应其他文件。不同失败分配给不同负责人,避免所有问题都被归为“打包失败”而无法追踪。
例外也不能只写一个批准人。若因为遗留渠道或紧急修复采用不同证书或方案,记录要绑定精确 APK 摘要、适用范围、业务理由、到期触发器和补充验证。本文不提供统一例外规则,也不声称御盾产品能力。真实放行结论必须来自同一候选的可复核回执。
| 失败 | 直接含义 | 优先责任 | 禁止的捷径 |
|---|---|---|---|
| SHA-256 不匹配 | 文件不是登记候选 | 构建与归档负责人 | 更新期望摘要迎合未知文件 |
| 签名验证失败 | 签名结构或完整性不成立 | 签名与打包负责人 | 只看文件摘要继续发布 |
| 证书不允许 | 签名者不在当前身份策略 | 发布审批负责人 | 用证书名称代替摘要 |
| 证明 subject 错配 | 声明对应其他文件 | 供应链证据负责人 | 复制同版本旧报告 |
| 最终归档变化 | 验证后文件被替换 | 交付负责人 | 只凭文件名确认 |
用只读脚本联合校验摘要、签名和证书
下面的 Python 示例读取待验 APK、允许清单和 apksigner 可执行文件。它先对 APK 计算 SHA-256,与清单中的 apkSha256 比对,再以只读方式运行 apksigner verify,要求返回成功并包含总体验证结果,最后从证书输出提取 SHA-256 摘要并与 allowedCertSha256 集合比较。任一输入不满足都会返回非零状态。
示例没有签名、修改或重新打包 APK,也不会接触 keystore。允许清单必须来自受控发布记录,不能由待验 APK 所在目录临时生成。实际环境还应锁定 apksigner 工具来源、保存工具版本与完整输出,并按目标平台策略解析各方案结果。脚本展示联合责任,不取代正式 Android 工具和审批系统。
准备 APK 软件加固评估时,可整理最终候选、SHA-256、apksigner 完整回执、允许证书摘要、签名方案策略、构建证明和设备回归范围,再通过御盾中央平台提交申请。所有结论应绑定同一候选摘要;未提供真实证据时,不声称签名通过、兼容通过、攻击阻断或客户项目结果。
- 期望摘要来自受控发布记录
- 实际摘要对最终 APK 现场计算
- apksigner 总体和方案结果均保存
- 全部证书摘要与允许集合比较
- 证明 subject 与 APK 摘要一致
- 归档交付前再次确认文件未变化
- 申请评估时提交同一候选的完整回执
from pathlib import Path
import hashlib
import json
import re
import subprocess
import sys
if len(sys.argv) != 4:
raise SystemExit(2)
apk_path = Path(sys.argv[1])
policy_path = Path(sys.argv[2])
apksigner_path = Path(sys.argv[3])
if not apk_path.is_file() or not policy_path.is_file() or not apksigner_path.is_file():
raise SystemExit(2)
policy = json.loads(policy_path.read_text(encoding="utf-8"))
expected_apk_digest = str(policy.get("apkSha256", "")).lower()
allowed_cert_digests = {str(value).lower() for value in policy.get("allowedCertSha256", [])}
actual_apk_digest = hashlib.sha256(apk_path.read_bytes()).hexdigest()
if len(expected_apk_digest) != 64 or actual_apk_digest != expected_apk_digest:
raise SystemExit(2)
if not allowed_cert_digests:
raise SystemExit(2)
completed = subprocess.run([str(apksigner_path), "verify", "--verbose", "--print-certs", str(apk_path)], capture_output=True, text=True, check=False)
verification_output = completed.stdout + "\n" + completed.stderr
if completed.returncode != 0 or re.search(r"^Verified$", verification_output, re.MULTILINE) is None:
raise SystemExit(2)
reported = re.findall(r"certificate SHA-256 digest:\s*([0-9a-fA-F:]{64,95})", verification_output)
cert_digests = {value.replace(":", "").lower() for value in reported}
if not cert_digests or cert_digests.isdisjoint(allowed_cert_digests):
raise SystemExit(2)
result = {"status": "verified", "apkSha256": actual_apk_digest, "matchedCertSha256": sorted(cert_digests & allowed_cert_digests), "toolExitCode": completed.returncode}
print(json.dumps(result, ensure_ascii=False, indent=2))事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| apksigner 可以验证 APK 的签名方案覆盖并打印签名证书,签名后不应再修改 APK。 | Android apksigner 说明签名、verify、方案验证和证书输出的工具行为。 | 工具验证成功不证明 keystore 管理、渠道流程、允许证书审批或最终归档正确。 |
| Android 使用应用签名建立更新身份,并由不同签名方案服务不同平台规则。 | AOSP app signing 描述 Android 应用签名、更新身份和签名方案。 | 签名有效只证明相应完整性和签名者身份,不证明业务代码安全或运行兼容。 |
| v2 通过 APK Signing Block 保护 APK 文件主体并处理向较弱方案降级的风险。 | APK Signature Scheme v2 描述签名块、内容摘要和防降级属性。 | v2 不覆盖 APK 之外独立下载的模型、配置、补丁或其他外部资产。 |
| 供应链声明可以用 subject digest 把 typed predicate 绑定到当前 APK。 | in-toto Attestation Statement v1 定义 subject、predicateType 与 predicate 的通用结构。 | 声明格式正确不保证内容真实,仍需可信签名者、策略和当前候选核验。 |
| 构建证明可以把 APK 产物与 builder、buildType、外部参数和依赖材料关联。 | SLSA Provenance v1.1 定义 provenance 的构建与产物字段。 | provenance 不替代 Android APK 签名,也不单独证明运行时安全或业务正确。 |
| 安全发布应保留来源、构建、验证和变更证据并管理供应链风险。 | NIST SP 800-218 SSDF 提供组织级安全软件开发与供应链实践。 | SSDF 不定义具体 APK 签名方案、证书允许列表或某个加固产品能力。 |
| APK 文件摘要和签名证书摘要必须作为不同字段共同进入候选记录。 | 工程判断:文件摘要绑定字节,证书摘要绑定签名身份,两者回答不同验收问题。 | 联合记录提高可追溯性,仍不替代代码审计、设备回归和密钥治理。 |
| 任何签名后字节修改都应触发摘要与签名重新验证。 | 工程判断:最终归档必须与验证回执保持同一字节身份,才能避免候选错配。 | 具体修改是否发生和当前候选是否通过,必须由项目归档与工具回执确认。 |
工程常见问题
APK 的 SHA-256 与官方公布值一致,为什么还要验证签名?
摘要一致只说明你拿到的字节与该期望值相同,还要确认期望值来源可信,并验证 Android 签名方案和证书身份,才能建立发布者与更新身份判断。
apksigner verify 成功后还需要比较证书摘要吗?
需要。工具可能正确验证一个由非预期证书签署的 APK。门禁应提取全部签名证书 SHA-256,并与当前受控允许集合比较。
签名证书相同是否说明两个 APK 完全一样?
不说明。同一证书可以签署多个不同版本和不同内容,必须分别计算文件 SHA-256。证书相同回答签名身份,文件摘要相同才回答字节身份。
v2 签名验证通过能否证明下载模型也未被修改?
不能。v2 覆盖 APK 文件主体,不覆盖安装后独立下载的模型和配置。外部资产需要自己的签名、摘要、版本与更新策略。
provenance 已绑定 APK 摘要,是否可以省略 apksigner?
不能。provenance 说明构建声明与产物的关系,Android 签名验证说明 APK 完整性和应用签名身份,二者需要分别验证并相互引用。
申请 APK 加固评估前需要准备哪些签名材料?
准备最终 APK、SHA-256、apksigner 完整输出、允许证书摘要、签名方案策略、构建证明和设备回归范围,再通过御盾中央平台提交申请。