先看结论与判断条件

  • 任何供应链结论都应从待发布 APK 的实际摘要开始,文件名、版本号、下载地址和流水线任务号都不是产物身份。
  • in-toto Statement 负责把 subject 摘要与有类型 predicate 放在同一声明中,格式本身不保证陈述真实。
  • SLSA provenance 描述构建者、构建类型、外部参数和依赖材料,不能替代 APK 签名验证或设备运行回归。
  • attestation 签名者与 Android 应用签名证书承担不同责任,二者都需要独立的信任根、轮换记录和候选绑定。
  • apksigner 验证成功只说明 APK 签名结构与证书等检查通过,不证明构建环境、密钥保管或加固强度。
  • 发布门禁应把 APK 摘要、provenance、attestation 验证、应用签名、静态检查和设备证据连接成同一证据包。

先把供应链证明拆成五种不同证据

“这个 APK 有供应链证明”不是一个足够精确的结论。评审者需要区分产物摘要、构建 provenance、attestation 封装与签名、Android 应用签名以及运行时验证。产物摘要回答证据指向哪个字节序列,provenance 回答声明中的构建过程,attestation 签名回答谁对声明负责,应用签名回答 Android 安装和升级身份,设备证据回答候选在特定环境中的行为。任何一层缺失都不能由另一层名称代替。

最常见的错配是报告和 APK 不属于同一候选。流水线先生成报告,后续渠道步骤又修改资源或重签名;归档系统只按文件名覆盖旧包;测试记录引用版本号,却没有文件摘要。最终交付看似资料齐全,实际每份证据指向不同字节。工程判断是把 SHA-256 等明确摘要作为所有记录的连接键,任何重新构建、对齐后修改、签名或渠道处理都产生新身份并重新进入受影响门禁。

证据还要标注事实等级。声明中写了某构建者和依赖材料,是可读取的声明事实;验证其签名来自组织信任根,是验证事实;确认该构建者受控、材料完整,则需要组织流程证据;说明 APK 在目标设备运行正确,还需要动态测试。报告应将这些结论分开,避免把“字段存在”写成“供应链安全已经证明”。

APK 发布证据层的职责
证据层主要回答绑定对象不能证明
文件摘要证据指向哪个字节序列具体 APK 文件文件来源和运行行为
SLSA provenance声明的构建者、参数和材料provenance subjectAndroid 签名和设备兼容
attestation 签名谁对 statement 负责序列化声明与签名身份声明内容一定真实
Android 应用签名安装、升级和发布身份APK 签名块与证书构建环境和加固强度
静态与设备证据结构、保护和运行结果同摘要候选与测试环境未覆盖设备永久安全

in-toto Statement 先解决报告与产物如何绑定

in-toto Attestation Statement v1 定义通用 statement,其中 subject 列出产物名称与摘要,predicateType 说明负载类型,predicate 承载具体声明。用于 APK 时,验证器先计算待发布文件摘要,再查找 subject 中完全相同的算法和值。若 subject 指向 AAB、未签名 APK、另一个渠道包或旧摘要,即使 predicate 内容很完整,也不能作为当前 APK 的证据。

subject 名称只帮助人阅读,真正身份来自摘要。同一文件可以有不同路径和归档名,同一名称也可能被不同构建覆盖。工程判断是声明中使用稳定、无敏感信息的逻辑名称,并要求摘要精确匹配;多个 subject 时逐个解释其关系,不能因为其中一个附件命中就推断 APK 命中。摘要算法、十六进制格式和大小写归一化规则也应固定,避免验证器产生歧义。

Statement 格式不验证 predicate 的真实性,也不规定谁可以签名。一个任何人都能生成的 JSON,即使 subject 摘要正确,也只证明写作者知道该 APK 的摘要。项目还需要可信 envelope 或等价签名、证书或公钥身份、签名者授权策略、时间和撤销状态。声明解析、摘要匹配与签名验证应是三个独立失败步骤,日志记录原因码而不泄露私钥或内部身份材料。

in-toto Statement 的字段与验证动作
字段验证动作通过含义失败处理
_type确认是预期 statement 类型解析器使用正确 schema拒绝未知类型
subject.name检查逻辑名称和文件角色便于识别候选不能单独放行
subject.digest与本地 APK 摘要精确比较声明绑定当前字节停止使用该声明
predicateType检查是否为允许声明类型可以选择对应验证器拒绝未知或错误类型
predicate按类型校验必需字段结构满足策略记录缺失和类型错误
envelope 签名验证可信签名者和策略声明来源满足信任要求拒绝未授权签名者

SLSA provenance 描述构建过程而非运行结论

SLSA Provenance v1.1 将产物主体与构建者、构建类型、外部参数和依赖材料等信息关联。对 APK 构建,builder 可以指向受控流水线身份,buildType 说明构建流程类别,externalParameters 记录调用者提供的配置,resolvedDependencies 记录解析后的来源材料。验证器把这些字段与组织允许策略比较,而不是看到 provenance 字样就放行。

参数与材料需要防止秘密进入声明。签名口令、访问令牌和内部敏感路径不应写入公开 provenance;可用不含秘密的配置标识、提交摘要、依赖摘要和受控构建类型表达。另一方面,过度删减也会失去审计价值。工程判断是保留会改变 APK 字节或安全属性的外部参数和材料身份,并由构建系统自动采集,避免发布者事后手工补写一份无法重现的清单。

provenance 不能说明代码没有漏洞、VMP 范围正确、签名密钥未泄露、APK 在目标设备可安装或线上商店交付相同文件。即使构建记录真实,构建脚本本身也可能有缺陷,输入材料也可能包含恶意变更。验证结果应写成“该摘要 APK 的构建声明满足列出的 builder、buildType 和材料策略”,后续仍需应用签名、静态结构、保护范围和设备回归。

provenance 字段能支持的工程判断
字段可检查的问题策略示例边界
subject产物是否为当前 APK摘要必须精确命中不说明产物质量
builder哪个构建身份执行流程只允许受控构建服务身份可能被错误授权
buildType采用哪类构建定义匹配批准的发布流程定义本身仍需审计
externalParameters调用者影响了哪些配置禁止未批准发布参数不能包含秘密值
resolvedDependencies解析了哪些材料提交和依赖摘要可追溯不证明依赖无漏洞
run details构建何时和如何执行关联流水线与时间记录不替代设备测试

attestation 签名者与 APK 签名者不是同一角色

attestation 签名者对供应链声明负责,可能是构建平台、透明日志服务或组织发布证明服务;Android 应用签名者则通过 APK 签名证书建立安装和升级身份。两者可能由同一组织管理,但私钥、证书、轮换和授权流程不同。验证器不能因为 APK 证书可信就自动信任任意 provenance,也不能因为 provenance 签名可信就忽略 APK 使用了错误的发布证书。

Sign your Android app 区分应用签名密钥、上传密钥、证书与 Play App Signing 等责任。项目必须明确当前渠道由谁执行最终应用签名,验证对象是开发者上传 APK、Play 生成的分发 APK,还是其他渠道包。供应链声明如果只绑定上传产物,就不能直接证明用户设备收到的派生产物字节相同;需要渠道回执和可验证的分发身份连接两者。

信任策略至少包含允许签名者、密钥用途、有效期、轮换链、撤销状态和声明类型。只把一个公钥硬编码到脚本会让轮换和应急处置困难,只相信证书主题字符串又容易接受错误密钥。工程判断是用版本化信任根和明确身份标识验证 attestation,单独维护 Android 签名证书谱系,并把每次变更写进候选发布证据。

两类签名身份的独立检查
维度attestation 签名Android 应用签名共同要求
签名对象statement 或 envelopeAPK 内容和签名块绑定当前候选身份
主要职责声明来源和责任安装、升级与发布身份受控私钥和授权
验证工具证明格式对应验证器apksigner 等 Android 工具失败关闭和原因记录
轮换信任根与证明签名者变化应用签名谱系或渠道流程旧身份撤销和审计
错误替代不能证明 APK 证书正确不能证明 provenance 真实两条链分别通过
渠道边界绑定声明中的 subject绑定实际分发 APK上传与分发关系可追溯

apksigner 结果只覆盖 Android 签名检查

Android apksigner 可以对 APK 签名,验证签名方案覆盖和证书信息,并要求签名完成后不再修改 APK。发布门禁应对最终候选运行验证,保存工具版本、完整命令的非敏感参数、退出状态、证书摘要和 APK 摘要。若后续执行对齐、资源注入、渠道修改或重签名,旧的验证结果不再属于新字节。

apksigner verify 成功不回答 keystore 是否由正确角色保管、签名操作是否经过授权、上传密钥与应用签名密钥是否混淆、渠道是否保存了同一文件,也不回答代码是否经过预期加固。证书摘要还需与版本化发布策略比较,不能只打印出来供人目测。调试证书、过期流程身份或不在允许谱系中的证书都应失败关闭。

APK 签名验证与 provenance 摘要匹配的顺序要固定。先确定最终文件并计算摘要,再验证 APK 签名与允许证书,然后验证 attestation envelope 和 statement subject,最后检查 provenance predicate 策略。任一步产生新文件都回到第一步。这样可以避免先验证未签名 APK 的 provenance,之后签名改变字节却仍沿用旧证明。

  • apksigner 对最终候选执行而不是中间 APK
  • 证书摘要与允许发布谱系自动比较
  • 签名后不再进行任何 APK 内容修改
  • 工具版本、退出状态和候选摘要同时归档
  • 上传包与渠道分发包的身份边界写清
  • 任何重签名都重新执行受影响门禁

用只读脚本先校验 subject 摘要与必需字段

供应链验证的第一道机械门禁,是确认声明确实指向手里的 APK。脚本读取最终 APK 和已解封装、已完成签名验证的 Statement JSON,计算 SHA-256,查找 name 与 digest 同时匹配的 subject,再检查 predicateType、builder 和 buildType 是否属于允许集合。摘要不匹配、多个冲突 subject、字段缺失或未知类型都应以非零状态停止。

下面示例公开安全且只读,不包含私钥、令牌、客户路径或攻击逻辑。它故意不实现 envelope 签名、证书链、透明日志和 Android APK 签名验证,因此输入 Statement 必须由前一门禁验证来源。脚本的通过含义很窄:当前 APK 摘要存在于声明 subject,且几个 provenance 字段满足本地策略;不能据此宣布供应链、加固或运行安全全部通过。

真实流水线应把允许 builder、predicateType 和 buildType 放在受审阅策略文件中,而不是散落在命令行。输出保存 APK 摘要、声明摘要、策略版本和失败原因,禁止把整个内部 predicate 复制到公开日志。若 statement 被重新签名但内容未变,声明摘要会变化,仍应保存新 envelope 身份和验证回执。

  • 先计算本地 APK 摘要而不是信任报告值
  • subject 名称和摘要同时精确匹配
  • predicateType、builder 和 buildType 使用允许集合
  • 脚本通过不替代 envelope 签名验证
  • 脚本通过不替代 apksigner 和设备回归
  • 输出绑定策略版本、APK 与声明摘要
校验 APK 摘要是否命中 provenance subject
from pathlib import Path
import hashlib
import json
import sys

if len(sys.argv) != 3:
    raise SystemExit("usage: verify_subject.py release.apk statement.json")

apk_path = Path(sys.argv[1])
statement_path = Path(sys.argv[2])
if not apk_path.is_file() or not statement_path.is_file():
    raise SystemExit("APK and statement files are required")

statement = json.loads(statement_path.read_text(encoding="utf-8"))
expected_type = "https://in-toto.io/Statement/v1"
allowed_predicates = {"https://slsa.dev/provenance/v1"}
allowed_builders = {"https://builder.example/release"}
allowed_build_types = {"https://build.example/android-release/v1"}

if statement.get("_type") != expected_type:
    raise SystemExit("unexpected statement type")
if statement.get("predicateType") not in allowed_predicates:
    raise SystemExit("unexpected predicate type")

apk_digest = hashlib.sha256(apk_path.read_bytes()).hexdigest()
subjects = statement.get("subject")
if not isinstance(subjects, list) or not subjects:
    raise SystemExit("statement subject is missing")

matches = []
for subject in subjects:
    name = subject.get("name")
    digest = subject.get("digest", {}).get("sha256")
    if name == apk_path.name and digest == apk_digest:
        matches.append(subject)

if len(matches) != 1:
    raise SystemExit(f"expected one matching APK subject, found {len(matches)}")

predicate = statement.get("predicate")
if not isinstance(predicate, dict):
    raise SystemExit("provenance predicate is missing")
builder = predicate.get("runDetails", {}).get("builder", {}).get("id")
build_type = predicate.get("buildDefinition", {}).get("buildType")
if builder not in allowed_builders:
    raise SystemExit(f"builder is not approved: {builder!r}")
if build_type not in allowed_build_types:
    raise SystemExit(f"build type is not approved: {build_type!r}")

print(f"verified subject sha256 {apk_digest}")

供应链证明不能替代加固与运行时证据

OWASP MASVS-RESILIENCE 把抗逆向与抗篡改作为移动端纵深防御控制。provenance 可以说明某个构建流程声称使用了加固步骤或参数,但不能直接证明最终 APK 中哪些方法受到处理、保护在运行时有效、性能满足预算或目标设备兼容。加固验收仍需针对同一摘要候选执行静态结构检查、保护清单核对和设备端关键路径回归。

同样,供应链证明也不能替代服务端授权。一个来源清晰且签名正确的 APK 仍运行在用户可控制环境,账号、权益、模型访问和高风险操作必须由服务端验证。provenance 能帮助追溯代码如何进入产物,无法让客户端成为绝对可信执行环境。报告应避免使用“可信构建所以无法篡改”或“有证明所以没有漏洞”这类超出证据的结论。

设备证据必须说明系统版本、ABI、设备类型、安装或升级路径、签名身份、候选摘要和业务入口。单一设备通过只覆盖该组合,模拟器结果不能自动代表真实硬件,静态扫描也不能代表运行时。工程判断是把供应链证明放在发布身份链的前半段,把加固、兼容与业务验证放在后半段,最终放行需要所有必需门禁指向同一候选。

常见越界结论及所缺证据
越界结论现有证明最多说明仍需证据合格写法
构建可追溯所以无漏洞声明了构建过程代码审查、测试和漏洞响应来源满足列出策略
provenance 写了加固所以强度达标参数或步骤被声明最终包静态与运行验证加固结果待同包验收
APK 签名通过所以构建可信签名结构和证书可验证构建者、材料和授权记录签名身份符合发布策略
attestation 有签名所以内容真实声明来自某签名者签名者授权和事实核验签名者满足信任策略
一台设备通过所以全部兼容该设备组合运行通过目标 API、ABI 和厂商矩阵只声明已覆盖组合
客户端可信所以无需服务端授权发布身份与来源可追溯账号、资源和风险校验服务端继续最终授权

发布证据包要支持独立复核和撤销

NIST SP 800-218 SSDF 将来源、构建、验证、变更和供应链风险纳入安全软件开发实践。落到 APK 发布,证据包至少包含最终 APK 摘要、版本与渠道、provenance、attestation 验证回执、builder 策略、应用签名证书摘要、apksigner 结果、源码与依赖材料身份、保护配置、静态门禁和设备矩阵。每项记录使用明确路径和摘要,评审者能独立重算关键值。

撤销与轮换不能遗漏。attestation 签名者失陷、构建服务授权错误、应用签名密钥轮换或依赖材料撤回时,团队需要知道哪些 APK 和发布结论受影响。索引应支持从签名者、builder、依赖摘要或证书反查所有候选,并记录暂停发布、撤销信任、重新构建和用户处置。仅归档孤立 JSON 会让事件发生时无法界定范围。

最终结论应限定为“指定摘要 APK 的声明、attestation 签名、应用签名与列出的门禁满足当前策略”,不扩写为供应链绝对安全或加固强度证明。准备评估时,可通过御盾中央平台提交候选 APK、脱敏 provenance、签名证书摘要、构建信任策略和设备回执,先固定证据身份与验证边界。

  • APK、statement、envelope 和策略文件都有摘要
  • 构建者、证明签名者和应用证书分别登记
  • 渠道处理前后产物关系可以追溯
  • 静态与设备证据绑定最终候选
  • 签名者或依赖撤销时能反查受影响版本
  • 报告明确事实、工程判断和未覆盖范围

事实依据与适用边界

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

本文判断事实或工程依据适用限制
SLSA provenance 可以关联产物主体、构建者、构建类型、外部参数与依赖材料。SLSA Provenance v1.1 定义相关 provenance 字段及其语义。provenance 只说明记录的构建过程,不能单独证明运行时安全、加固强度或设备兼容。
in-toto Statement 可以把产物摘要与有类型的 predicate 绑定在同一声明中。in-toto Attestation Statement v1 定义 _type、subject、predicateType 和 predicate 结构。Statement 格式不保证内容真实,仍需可信签名者、签名验证和策略核对。
安全发布应保留来源、构建、验证和变更证据,并管理供应链风险。NIST SP 800-218 SSDF 提供组织级安全软件开发与发布实践框架。SSDF 不定义某个 APK 的具体证明内容,也不证明御盾或其他产品功能。
Android 发布需要区分应用签名密钥、上传密钥、证书和 Play App Signing 责任。Sign your Android app 描述 Android 应用签名、上传密钥和 Play App Signing 流程。文档不能确认某个实际 APK 使用了正确生产证书或渠道交付了同一文件。
apksigner 可以验证 APK 签名方案与证书,并要求签名后不再修改 APK。Android apksigner 描述 APK 签名、验证和签名后修改的工具边界。工具验证通过不证明 keystore 管理、签名授权、渠道归档或加固结果正确。
抗逆向与抗篡改属于纵深防御,不能由供应链声明自动替代。OWASP MASVS-RESILIENCE 将相关要求放在移动应用韧性控制范围。控制目录和 provenance 字段都不证明某个候选包达到特定防护强度。
验证供应链声明必须先计算待发布 APK 摘要并精确匹配 subject。工程判断:摘要是报告、签名和测试记录连接到具体字节序列的最小稳定身份。摘要匹配只证明声明指向当前文件,不证明声明签名可信或 predicate 事实正确。
attestation 签名身份与 Android 应用签名身份需要独立验证。工程判断:两类签名面向不同对象和责任,任一验证不能推导另一条信任链通过。允许身份、轮换、撤销和渠道关系必须由项目信任策略与真实证书回执确认。

工程常见问题

APK 有 SLSA provenance 是否表示没有被篡改?

不能直接这样判断。先要验证 attestation 签名和 APK 摘要匹配。即使证明可信,也只说明声明的构建过程,运行时篡改、漏洞和服务端授权仍需其他控制。

in-toto Statement 中写了 APK 文件名是否足够?

不够。文件名可以重复或改变,必须计算本地 APK 摘要并与 subject.digest 精确匹配,还要验证声明类型、签名者和 predicate 策略。

apksigner verify 成功能否代替 provenance 验证?

不能。apksigner 验证 Android 签名结构和证书,provenance 说明构建者、参数与材料。两条证据责任不同,都应绑定同一最终 APK。

provenance 声明用了 VMP 是否能证明保护强度?

不能。它最多证明构建声明包含某步骤或参数。最终 APK 的方法命中、保护结构、性能与设备兼容仍需同摘要候选的静态和动态证据。

Play App Signing 场景应验证上传包还是分发包?

两者角色都要写清。上传包证明开发者提交身份,用户获得的分发 APK 由渠道生成和签名,需要渠道回执和分发证书连接到实际交付。

申请 APK 供应链证明评估前需要准备什么?

准备最终 APK、provenance、attestation envelope、签名者策略、应用证书摘要、apksigner 回执和设备证据,再通过御盾中央平台提交申请。

想用自己的 App 验证?

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

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