先看结论与判断条件

  • PoC 的通过对象是一个明确候选和一组明确测试,不是产品名称、配置模板或未来版本;结论必须绑定产物摘要与适用范围。
  • 生产迁移先做差异清单,比较源码、依赖、变体、applicationId、资源、工具链、加固配置、签名身份和交付渠道,禁止口头判断等价。
  • CI 应记录构建者、构建类型、外部参数、依赖材料和产物 subject,让生产候选可追溯;provenance 不证明运行时安全或兼容。
  • 应用签名密钥、上传密钥、证书和 Play App Signing 责任必须分开,PoC 调试证书不能沿用为生产身份,签名成功也不是业务验收。
  • 重验范围由变化影响决定:候选字节变化至少重跑保护和兼容关键路径,签名变化补更新身份,渠道变化补派生交付与安装回执。
  • 发布 API 返回成功只证明控制面接受了 Track 状态,不证明商店审核、设备兼容或质量门禁通过,仍需独立审批和可回滚证据。

先冻结 PoC 通过的具体对象和边界

PoC 结束时最重要的产物不是一封“测试通过”邮件,而是一份不可变证据包。它至少包含 artifact_sha256、source_sha、依赖锁摘要、构建变体、applicationId、versionCode、加固工具和配置摘要、签名证书摘要、目标 ABI、设备矩阵、业务断言、保护验证、失败项与残余风险。缺少这些字段,后续无法判断生产包是否仍是被验证对象。

每条结论还要有 scope。例如“离线授权路径业务语义保持”只覆盖指定候选、设备、账号状态和测试步骤,不能推导其他模块、其他系统版本或未来代码自动通过;“目标字符串未以原形式出现”也只说明当前静态检查范围,不代表运行时不可观察或攻击被阻断。PoC 报告应同时保存 claim、evidence、boundary 和 owner。

冻结不是把 PoC 配置永久不动,而是为变化建立基线。生产团队可以调整模块范围、兼容配置、工具版本和构建方式,但每项变化必须有 change_id、原因、负责人、影响分析和重验计划。若直接把一份未版本化配置复制进 CI,数周后即使构建成功,也无法知道它与 PoC 是否相同。

PoC 证据包需要冻结的核心对象
对象必须字段支持的结论不能继承的范围
候选身份产物摘要、包名、版本、变体测试结果属于哪个包摘要不同的后续构建
构建输入源码、依赖、工具链和参数摘要复现当时构建上下文未记录的默认值与环境变化
加固配置规范化配置、作用域和工具版本哪些控制实际进入候选模板未来版本或其他模块
签名身份证书摘要、签名阶段和责任人安装与发布身份边界调试证书到生产证书的变化
测试证据设备、ABI、用例、数据、原始回执原范围内的保护和兼容观察未测设备、路径和异常状态
残余风险未覆盖项、误判、降级和接受者为何在当前边界内可继续新增业务和平台变化

用生产差异清单决定哪些结论失效

生产接入第一步是把 PoC 基线与 production intent 做字段级比较。源码提交、依赖解析、build type、product flavor、applicationId、资源、R8 规则、Native ABI、加固配置、工具链、签名和分发渠道都要进入清单。不能因为界面相同、版本名相同或配置文件名相同,就宣布生产候选与 PoC 等价。

变化影响不同。业务源码或依赖变化可能影响全部运行路径;变体和资源变化影响组件、权限和配置;加固配置或工具链变化影响保护覆盖与兼容;签名变化影响安装、更新、平台服务和渠道身份;AAB 渠道变化还会引入设备派生 split。迁移表应为每个字段预先绑定 retest_domain,避免出了问题再临时决定。

最严格且最容易解释的规则是按产物摘要继承。PoC 与生产使用完全相同的最终产物时,原结论可以在原 scope 内引用;只要摘要变化,PoC 的运行时与保护结果都不直接继承,生产候选必须取得新回执。差异分析可以缩小重验重点,但不能把新二进制重新命名为已测 PoC 包。

  • PoC 与生产目标按字段而非文件名比较
  • 源码、依赖、变体、资源和 ABI 变化有 change_id
  • 加固配置、工具版本和默认值展开后再比较
  • 签名、包名、versionCode 和渠道身份单独比较
  • 产物摘要变化时不直接继承运行时与保护结论
  • 每项差异映射到明确重验域和责任人

把加固步骤接入可追溯的生产构建

PoC 常由工程师在独立环境手工执行,生产则需要确定输入、受控工具、最小权限和稳定输出。CI 中要明确加固发生在编译、打包、签名之前还是之后,谁提供未签名或已签名产物,配置从何处读取,失败是否阻断,以及日志和中间文件如何保留。任何手工下载、复制或重命名都要形成可审计步骤。

SLSA Provenance v1.1 提供产物 subject、builder、buildType、externalParameters 和 resolvedDependencies 等字段,适合绑定生产候选与构建输入。加固配置摘要和工具版本应作为外部参数或材料进入证明,产物摘要作为 subject。provenance 能说明记录中的构建关系,但不能证明运行时兼容、保护强度或构建者没有越权。

in-toto Attestation Statement v1 可以把产物摘要与有类型的 predicate 绑定,避免测试报告、签名回执和发布审批贴到另一个包。生产系统应验证声明签发者、签名和 subject,再消费 predicate。格式本身不保证内容真实,也不能用一份旧声明覆盖新候选;每次发布都需要与当前产物摘要一致的证明。

生产构建阶段的输入、输出与门禁
阶段受控输入必须输出失败处理
源码构建冻结提交、依赖锁和变体未加固产物摘要与构建记录输入漂移则阻断
加固处理候选、规范化配置、工具版本加固产物摘要、日志和配置证明配置或工具不匹配则阻断
生产签名待签产物、密钥策略和签名参数签名产物、证书摘要与验证回执身份错误则禁止发布
设备回归最终签名候选、设备矩阵和用例保护、兼容和异常回执任一关键门禁失败则停止
发布审批证据包、残余风险和版本计划审批记录、范围和回滚条件缺证据不提交渠道
渠道提交已批准产物与目标 Track控制面回执和渠道状态回执不符则撤回或停止推进

签名责任要在加固边界之外明确分工

Android 发布需要区分应用签名密钥、上传密钥、证书和 Play App Signing 责任。PoC 为了便捷可能使用调试证书或临时上传身份,生产候选则必须进入企业既定签名链。迁移设计要明确加固系统接收的是未签名包、内部签名包还是商店托管前产物,并尽量避免不必要地暴露应用签名密钥。

签名证书摘要应同时出现在构建证明、安装回归、后端 allowlist、应用链接、平台 API 配置和渠道材料中。更换证书或采用签名轮换时,需要独立验证更新身份和相关服务配置。签名命令成功只说明生成了签名结构,不能确认实际使用的是预期生产证书,更不能证明保护逻辑或业务路径正确。

责任矩阵至少包含密钥 owner、签名执行者、证书验证者、构建审批人和渠道发布人,并实行最小权限与职责分离。加固配置维护者不应因工具需要就默认获得长期签名密钥;发布人员也不应绕过候选摘要核对重新打包。任何签名后再次修改都会产生新产物并使原签名或测试关系失效。

  • 应用签名密钥、上传密钥和证书用途分别登记
  • PoC 调试证书不会被当作生产身份
  • 加固系统只获得完成任务所需的最小签名权限
  • 最终证书摘要与后端、链接和渠道配置一致
  • 签名后禁止未审计的重新打包或文件替换
  • 每个最终签名候选重新绑定设备回归回执

按变化影响重建保护和兼容回归矩阵

生产重验不必机械复制 PoC 的全部探索步骤,但必须覆盖受变化影响的控制和业务路径。候选摘要变化时,至少重跑高价值保护断言、首次安装与启动、登录授权、关键数据、Native 加载、后台任务和异常恢复。新增模块、第三方 SDK 或 ABI 则扩展对应矩阵,未受影响的低风险用例可以按审批策略抽样。

依赖真实 Android 运行时、组件和系统 API 的语义应通过设备端 instrumented test 验证。矩阵需要目标 API、ABI、厂商、locale、安装方式和数据状态,并以真实用户与最低支持范围选取。单一设备通过只能说明当前条件,不能继承为全矩阵结论;模拟器或静态扫描也不能替代关键设备路径。

保护验证和兼容验证要分开。保护验证回答目标结构、完整性异常和授权控制是否按威胁模型工作;兼容验证回答原有业务语义、性能边界和恢复路径是否保持。一个候选可能保护检查通过却业务崩溃,也可能兼容通过但控制没有覆盖目标资产。发布需要两类回执都绑定最终签名产物。

生产变化与必需重验项映射
变化主要风险最低重验不可继承结论
源码或依赖变化业务与生成代码改变受影响业务、保护目标和完整兼容矩阵PoC 运行时与保护结果
加固配置变化保护范围和兼容语义改变配置覆盖、目标资产、启动和异常路径旧配置保护结论
工具链版本变化默认值、变换和输出改变配置等价、产物差分和设备回归旧工具候选结论
签名身份变化安装更新和平台服务身份改变签名、更新、链接和后端配置PoC 安装身份
APK 转 AAB 渠道设备派生 split 和交付改变bundle 派生、测试轨道和目标设备安装单 APK 交付结论
产物摘要不变测试对象保持相同核对证据 scope 和时效未覆盖设备、路径和残余风险

发布审批要区分控制面成功和质量通过

生产发布前的证据包应包括最终候选摘要、构建 provenance、配置与工具版本、签名证书、保护回执、兼容矩阵、已知问题、残余风险、目标 Track、灰度条件和回滚方案。审批人签的是这个具体对象和范围,而不是“加固已接入”的永久状态。任何证据变化都应重新计算审批摘要。

Google Play Developer API track update 可以提交期望的 Track 状态,并以返回的 Track 作为控制面回执。该 API 成功说明请求被接受并返回资源,不证明商店审核完成、目标用户已获得版本、设备兼容或业务质量门禁通过。发布系统需要把 API 回执、商店状态、设备安装和业务监控分别保存。

回滚也要在提交前定义。停止继续分发、回退业务开关、发布修复版本和恢复旧加固配置是不同动作,能否执行取决于渠道、签名和客户端状态。不能假设 Track 变更会卸载已安装版本,也不能在出现问题后用未经验证的旧包覆盖。回滚候选同样需要合法身份和范围内证据。

  • 审批对象绑定最终候选摘要与证据包摘要
  • 保护、兼容、签名和渠道回执分别保存
  • Track API 成功不会被写成质量或审核通过
  • 灰度进入、暂停、继续和修复触发条件预先定义
  • 回滚候选保持合法签名与更新身份
  • 已安装版本与继续分发状态分别处置

用只读脚本映射 PoC 与生产候选差异

下面的 Python 脚本读取一个脱敏迁移文件,其中包含 poc 与 production 两组候选字段。它比较源码、依赖、变体、applicationId、加固配置、工具链、签名证书、渠道和产物摘要,并把变化映射到 must_retest。脚本还检查 production provenance subject 是否等于生产产物摘要,避免证明绑定到错误候选。

当生产产物摘要与 PoC 不同,脚本会把所有 poc_claims 标入 not_inherited;摘要相同时,结论仍只保留原 scope,不会扩张到未测设备或路径。任何字段缺失、摘要格式错误、claim 缺少 evidence_receipt 或 production subject 错配都会直接失败。它不调用构建、签名或发布接口,也不修改输入文件。

差异映射是重验计划的起点,不是自动放行器。真实生产流程还需由责任人确认变化影响、执行设备回归、验证签名和批准残余风险。代码只阻止最明显的证据错配,并公开列出哪些 PoC 结论没有继承,不能证明配置等价或候选具备任何保护强度。

比较 PoC 与生产候选并列出必须重验项
import json
import sys
from pathlib import Path

REQUIRED_CANDIDATE = {
    "artifact_sha256",
    "source_sha256",
    "dependency_lock_sha256",
    "variant",
    "application_id",
    "hardening_config_sha256",
    "toolchain_id",
    "signing_cert_sha256",
    "delivery_channel",
}
RETEST = {
    "source_sha256": ["business", "protection", "compatibility"],
    "dependency_lock_sha256": ["business", "protection", "compatibility"],
    "variant": ["install", "resources", "components", "compatibility"],
    "application_id": ["install", "platform-services", "backend-identity"],
    "hardening_config_sha256": ["protection", "startup", "exception-paths"],
    "toolchain_id": ["reproducibility", "protection", "compatibility"],
    "signing_cert_sha256": ["signing", "update", "app-links"],
    "delivery_channel": ["delivery", "derived-artifacts", "install"],
    "artifact_sha256": ["final-candidate-protection", "final-candidate-compatibility"],
}

def stop(message):
    raise SystemExit(f"invalid PoC transition: {message}")

def require_candidate(value, name):
    if not isinstance(value, dict):
        stop(f"{name} must be an object")
    missing = sorted(REQUIRED_CANDIDATE.difference(value))
    if missing:
        stop(f"{name} missing {missing}")
    for field in ("artifact_sha256", "source_sha256", "dependency_lock_sha256", "hardening_config_sha256", "signing_cert_sha256"):
        if not isinstance(value[field], str) or len(value[field]) != 64:
            stop(f"{name}.{field} is not a SHA-256 string")
    return value

if len(sys.argv) != 2:
    raise SystemExit("usage: python transition_gate.py MAPPING_JSON")
input_path = Path(sys.argv[1]).resolve()
if not input_path.is_file():
    stop(f"file not found: {input_path}")
payload = json.loads(input_path.read_text(encoding="utf-8"))
poc = require_candidate(payload.get("poc"), "poc")
production = require_candidate(payload.get("production"), "production")
claims = payload.get("poc_claims")
if not isinstance(claims, list) or not claims:
    stop("poc_claims must be a non-empty list")
for index, claim in enumerate(claims):
    if not isinstance(claim, dict) or not claim.get("claim_id") or not claim.get("evidence_receipt") or not claim.get("scope"):
        stop(f"poc_claims[{index}] is incomplete")
subject = payload.get("production_provenance_subject_sha256")
if subject != production["artifact_sha256"]:
    stop("production provenance subject does not match artifact")
changes = []
required_retests = set()
for field in sorted(REQUIRED_CANDIDATE):
    if poc[field] != production[field]:
        changes.append({"field": field, "poc": poc[field], "production": production[field]})
        required_retests.update(RETEST[field])
same_artifact = poc["artifact_sha256"] == production["artifact_sha256"]
not_inherited = [] if same_artifact else [claim["claim_id"] for claim in claims]
output = {
    "same_artifact": same_artifact,
    "changes": changes,
    "must_retest": sorted(required_retests),
    "not_inherited": sorted(not_inherited),
    "inherited_scope_expansion": False,
    "gate": "RETEST_REQUIRED" if changes else "VERIFY_ORIGINAL_SCOPE",
}
print(json.dumps(output, ensure_ascii=False, indent=2, sort_keys=True))

生产接入完成后仍要持续重验和维护证据

一次生产接入完成,不代表后续版本永久继承。每次源码、依赖、平台 SDK、加固工具、配置、签名或渠道变化,都要重新执行差异映射并更新证据包。NIST SP 800-218 SSDF 强调来源、构建、验证和变更记录,适合作为持续维护框架,但具体重验范围仍由业务风险与变化影响决定。

运营阶段应监控安装失败、启动异常、崩溃、ANR、关键授权失败和渠道状态,并绑定版本、候选摘要、设备和时间。线上信号用于发现问题,不能替代发布前回归,也不能从单个异常推导加固根因。发生回归时,先冻结当前生产候选和回执,再比较上一候选的源码、配置、工具与签名差异。

企业最终需要的是一条能重复运行的生产链:输入可追溯、配置可复现、密钥责任清楚、候选不可混淆、重验与审批有回执、异常能够停止推进。需要规划实际 PoC 到生产迁移时,可通过御盾中央平台提交脱敏 PoC 证据包、生产构建图、签名责任、目标矩阵和发布渠道,先形成差异清单,再确定接入步骤。

  • 每次变化重新生成 PoC 到生产差异映射
  • 生产候选、证明、签名、测试和审批摘要互相绑定
  • 线上异常按版本、设备和时间保存,不直接归因
  • 工具与配置升级拥有独立重验和回滚计划
  • 密钥、配置、发布和审批职责保持分离
  • 未继承结论和残余风险持续由明确 owner 管理

事实依据与适用边界

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

本文判断事实或工程依据适用限制
安全发布应保留来源、构建、验证和变更证据,并管理供应链风险。NIST SP 800-218 SSDF 把来源追溯、构建完整性、验证、维护和供应链实践纳入安全开发框架。SSDF 是组织级实践框架,不定义某个 App 加固产品功能,也不提供具体生产重验范围。
构建证明应绑定产物主体、构建者、构建类型、外部参数和依赖材料。SLSA Provenance v1.1 定义 subject、builder、buildType、externalParameters 和 resolvedDependencies 等字段。provenance 只能证明记录中的构建关系,不能单独证明运行时安全、保护强度或设备兼容。
供应链声明应把产物摘要与有类型的声明负载绑定。in-toto Attestation Statement v1 使用 subject 摘要与 predicateType、predicate 组织声明。声明格式不保证签发者可信或内容真实,仍需验证签名、subject 和质量门禁。
Android 发布需要区分应用签名密钥、上传密钥、证书和 Play App Signing 责任。Sign your Android app 说明应用签名、上传密钥、证书与 Play App Signing 的关系。文档不能确认某个实际包使用了预期生产证书,签名成功也不证明业务或保护验证通过。
依赖真实 Android 运行时、组件和系统 API 的行为应通过设备端测试验证。Android instrumented tests 说明 instrumented test 在 Android 环境中运行并可访问真实框架和组件。单一设备通过不能代表完整 API、ABI 和厂商矩阵,也不能继承到摘要不同的生产候选。
发布系统可以通过受授权的 Track 更新提交期望状态并获得返回资源。Google Play Developer API track update 描述 edits.tracks.update 的请求与 Track 返回对象。API 更新成功不证明商店审核、用户分发、设备兼容或业务质量门禁通过。
PoC 结论只绑定具体候选和测试 scope,不能自动继承到摘要不同的生产产物。工程判断:二进制、配置、签名或构建输入变化会改变被测对象,旧回执无法证明新对象具有相同行为。差异分析可以确定重验重点,但不能在没有项目回执时声明生产候选已通过保护或兼容验证。
生产迁移应把构建、签名、设备回归、审批和渠道回执分别取证。工程判断:各阶段回答的控制问题不同,任一成功状态都不能替代其他阶段的证据。具体 CI、密钥托管、设备矩阵、灰度和回滚策略需由企业环境与渠道规则确认。

工程常见问题

PoC 已通过,生产包只换签名还需要重新测试吗?

需要按影响重验。签名变化会影响安装、更新、平台服务和渠道身份,最终签名产物也应重新绑定关键设备与业务回执。

把 PoC 配置文件直接复制到 CI 就算完成生产接入吗?

不算。还要展开默认值、固定工具版本、记录输入输出、配置密钥责任、生成 provenance,并对最终候选重新执行门禁。

哪些 PoC 结论可以继承到生产候选?

只有产物摘要相同的对象,才可在原设备、用例和证据 scope 内引用原结论;任何范围扩张仍需新的验证。

构建 provenance 已验证,是否能说明加固效果有效?

不能。provenance 说明记录的构建关系,保护效果和业务兼容仍需针对最终候选的静态、运行时和设备回执。

Google Play Track API 返回成功是否代表版本已可安全发布?

不代表。它是控制面回执,商店审核、真实分发、设备兼容、业务质量和风险审批仍需分别确认。

准备 PoC 转生产评审需要哪些材料?

准备 PoC 证据包、生产构建图、差异清单、签名责任、最终候选、重验矩阵、发布审批、灰度和回滚条件。

想用自己的 App 验证?

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

继续阅读: 御盾 VMP 接入前准备清单