先看结论与判断条件
- 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 是否相同。
| 对象 | 必须字段 | 支持的结论 | 不能继承的范围 |
|---|---|---|---|
| 候选身份 | 产物摘要、包名、版本、变体 | 测试结果属于哪个包 | 摘要不同的后续构建 |
| 构建输入 | 源码、依赖、工具链和参数摘要 | 复现当时构建上下文 | 未记录的默认值与环境变化 |
| 加固配置 | 规范化配置、作用域和工具版本 | 哪些控制实际进入候选 | 模板未来版本或其他模块 |
| 签名身份 | 证书摘要、签名阶段和责任人 | 安装与发布身份边界 | 调试证书到生产证书的变化 |
| 测试证据 | 设备、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 结论没有继承,不能证明配置等价或候选具备任何保护强度。
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 证据包、生产构建图、差异清单、签名责任、最终候选、重验矩阵、发布审批、灰度和回滚条件。