先看结论与判断条件
- 先按源码、未签名二进制、符号、映射、配置和测试数据分别定级,再讨论它们能否进入外部处理环境。
- 生产签名私钥不应因为选择云端处理而自然进入上传包;签名职责、上传密钥和应用签名密钥要分开画边界。
- 本地化部署只有在网络、主机、身份、存储、日志、更新和运维责任都有人持续执行时才形成有效控制。
- 云端交付不能只凭传输加密作判断,还要核对落盘、副本、队列、缓存、故障诊断、人员访问和删除回执。
- 无论处理地点在哪里,交付报告都要用产物摘要绑定构建参数、材料、加固配置、签名身份和验证范围。
- 退出能力是采购前的安全条件,需要提前约定导出格式、撤权路径、残留数据确认、证据接收人和未完成事项。
先把交付模式拆成可验证的控制事实
云端处理通常意味着团队把待处理制品和必要配置送入供应方管理的计算环境,再取回加固产物与报告;本地化交付通常意味着处理组件运行在客户管理的网络或主机中。这个定义只描述计算位置,没有自动回答谁能读到输入、谁维护主机、更新从哪里进入、日志留在哪里,也没有证明生产签名材料是否参与。安全评审应把这些问题逐项记录,而不是给部署标签预设结论。
同为云端方案,有的只接收未签名二进制,有的还要求符号、映射和策略文件;有的任务结束后立即清理工作区,有的为了故障诊断保留受控副本。同为本地方案,也可能由客户自行运维,或保留供应方远程支持通道。真正的边界由数据流、管理面、人员权限和证据流共同决定,采购名称不能替代架构图和责任表。
评估会议先要求方案方画出四条线:输入从哪里进入,处理过程在哪里产生中间数据,产物与报告从哪里交付,管理和支持人员从哪里访问。每条线要有身份、协议、存储、保留、审计和异常处置字段。没有答案的地方标为未知,并进入风险决定;不能用默认安全或行业惯例把空白项直接判为通过。
| 对象 | 云端评审问题 | 本地化评审问题 | 共同回执 |
|---|---|---|---|
| 处理主机 | 租户与运行区怎样隔离 | 谁负责基线和补丁 | 主机身份与版本 |
| 管理入口 | 支持人员怎样获权 | 远程通道是否存在 | 审批与访问日志 |
| 任务数据 | 副本和缓存何时清理 | 落盘目录怎样限制 | 保留和删除状态 |
| 组件更新 | 服务端版本怎样固定 | 离线包从哪里验证 | 版本与来源摘要 |
| 故障诊断 | 日志会包含哪些字段 | 日志由谁读取导出 | 脱敏规则与接收人 |
| 退出动作 | 账号和数据怎样撤销 | 组件与支持权怎样回收 | 退出检查单 |
按真实输入分类,不把所有文件都叫作安装包
加固任务的输入可能包括源码、编译后的中间产物、APK、AAB、原生库、符号文件、混淆映射、资源清单、保护策略和兼容测试样本。它们的泄露后果不同,不能只用是否包含源码做二分判断。符号和映射可能暴露内部结构,策略文件可能揭示重要模块,测试样本可能携带真实账号或业务数据。每类输入都要确定业务所有者、敏感级别、允许地点和最小必要字段。
OWASP MASVS-PRIVACY-1 要求最小化敏感数据访问,并把第三方 SDK 的数据行为纳入责任边界。把这项原则用于交付评审时,重点不是宣称某种部署天然合规,而是追问任务是否真的需要真实用户数据、线上令牌、生产接口配置或完整诊断日志。若用合成样本、脱敏配置或独立测试租户就能完成兼容验证,就不应把更敏感的数据随制品上传或复制。
输入清单还要区分可重新生成与不可替代材料。普通构建产物可以从受控源码重建,生产签名身份、尚未公开的符号映射和事故样本则可能需要更严格的授权。对每个对象记录文件摘要、来源、接收方、任务编号、允许用途和删除条件,便能在云端和本地方案之间比较同一组事实,而不是比较两份口号。
| 输入对象 | 主要风险 | 最小化动作 | 交付边界问题 |
|---|---|---|---|
| 未签名制品 | 代码与资源暴露 | 删除无关变体 | 允许进入哪个处理区 |
| 符号与映射 | 内部结构暴露 | 按诊断需要提供 | 谁能读取和导出 |
| 保护策略 | 关键模块暴露 | 拆分公开与敏感参数 | 配置是否单独加密 |
| 测试样本 | 业务数据泄露 | 使用合成或脱敏数据 | 日志是否回显内容 |
| 生产配置 | 服务边界泄露 | 替换为测试配置 | 是否确有处理必要 |
| 签名材料 | 发布身份失控 | 不随任务输入传递 | 签名发生在何处 |
生产密钥与加固处理必须分层决策
加固工具可能处理未签名制品,也可能在交付链中靠近签名步骤,但靠近不等于必须接触生产私钥。Android 官方发布文档区分应用签名密钥、上传密钥、证书和 Play App Signing 的责任;评审要把每一种身份映射到明确持有者与执行位置。若流程只需要验证签名身份,应传递证书指纹或验签结果,而不是为了方便把可签名的私钥复制到处理环境。
Android Keystore 可以限制密钥用途,并在可用设备上使用硬件保护,不过该能力主要约束设备或应用中的密钥使用。它不能证明构建服务器上的发布密钥已被隔离,也不能保护密钥使用后产生的明文或已签名产物。选择本地环境时仍要确认签名服务的调用权限、审计与双人审批;选择云端环境时应优先让加固产物回到原有签名边界再完成生产签名。
方案评审必须明确失败条件:要求上传生产私钥、把可复用凭据写入保护配置、让处理人员获得无限期签名权限,或无法说明签名发生在哪个受控系统,都不能仅靠保密协议放行。确有特殊流程时,需要单独的密钥风险评审、受限调用接口、轮换和撤销计划,并用真实签名回执确认候选身份,不能把流程图当成执行证据。
| 材料或动作 | 建议位置 | 可交付证据 | 阻断条件 |
|---|---|---|---|
| 证书指纹 | 任务元数据 | 期望身份清单 | 身份来源不明 |
| 上传密钥 | 受控发布系统 | 调用和审批记录 | 随制品长期保存 |
| 应用签名私钥 | 专用密钥边界 | 签名回执 | 要求外发私钥 |
| 未签名产物 | 加固处理环境 | 输入输出摘要 | 来源无法确认 |
| 最终签名产物 | 发布验证区 | 验签与渠道回执 | 证书不匹配 |
| 撤销与轮换 | 密钥治理流程 | 负责人和完成状态 | 无应急路径 |
云端处理要审计传输之外的落盘和人员路径
TLS 只能保护传输链路的一部分,无法回答制品进入服务后会复制多少份、队列怎样持久化、缓存是否包含解包内容、备份是否覆盖任务目录,以及故障工单能否附带原始日志。云端评审至少要取得处理区域、租户隔离、工作区寿命、存储加密、访问审批、日志字段和清理回执的说明。若这些字段无法核验,应降低输入敏感度或改变处理方式,而不是把已加密上传当作完整结论。
人员访问路径应区分例行运维、故障支持和安全响应。例行运维不应默认读取客户输入;故障支持需要工单、范围、时限和复核;安全响应可能需要保全证据,不能与普通任务清理规则互相覆盖。团队还要问清子处理方、对象存储、消息队列和监控平台是否进入数据路径,因为任务页面只展示一个服务名称,并不代表实际处理方只有一个。
云端方案的优势通常来自集中维护、快速调度和较少的客户基础设施负担,但这些是运营特征,不是安全证明。若组织已经有清晰的数据分级、外发审批和制品追踪,且输入能够降敏,云端处理可能更容易落地;若输入禁止离开隔离区,或无法接受供应方管理面的残余访问,则需把限制写成硬性准入项。结论必须基于数据和控制事实。
本地化交付的安全性取决于持续运维而非物理位置
把处理组件放进内网能减少制品跨组织传输,但也把主机加固、身份接入、漏洞修复、容量、备份、日志和高可用责任转移给本地团队。若主机长期不更新、所有管理员共享账号、工作目录无人清理,物理位置不会自动带来更强保护。评审时要把每项控制分配给具体角色,并确认该角色有权限、工具和窗口执行,而不是只写客户负责。
本地环境还需解释安装包和规则更新怎样进入隔离网。在线更新可能引入持续外连,离线更新则需要来源验证、签名检查、版本回滚控制和兼容验证。更新中断时,团队应知道当前组件还能处理哪些版本、怎样回到已知可用状态,以及旧规则是否继续接收安全支持。没有更新路径的孤立设备不等于稳定,它可能只是把风险推迟到下一次业务升级。
远程支持是最容易被忽略的边界。需要确认通道是否默认开启、由谁发起、授权持续多久、供应方能看到哪些文件、会话是否记录,以及紧急情况下谁能终止。若组织要求完全离线,就应准备不包含敏感制品的诊断包、明确导出字段,并让支持结论回到本地证据链。若保留远程能力,则它必须像其他特权入口一样纳入审批与审计。
| 控制项 | 本地责任 | 供应协作 | 验收回执 |
|---|---|---|---|
| 主机基线 | 配置与持续检查 | 给出依赖要求 | 基线结果 |
| 组件更新 | 安排窗口和回滚 | 发布签名更新包 | 版本和来源 |
| 身份权限 | 接入企业身份系统 | 定义最小角色 | 授权清单 |
| 日志处置 | 存储脱敏和审计 | 说明必要字段 | 字段样本 |
| 远程支持 | 逐次授权和终止 | 限定动作与时限 | 会话记录 |
| 备份恢复 | 执行恢复演练 | 说明可恢复对象 | 恢复结果 |
用产物证明把输入、处理和输出连成同一条链
NIST SP 800-218 SSDF 把来源、构建、验证、变更和供应链风险纳入安全软件开发实践。落到加固交付上,至少要证明哪个输入进入了哪个已识别的处理版本,使用了哪份批准配置,输出文件摘要是什么,验证覆盖哪些设备或场景,以及哪些结论仍未验证。云端任务号或本地主机日志只能作为链条中的一环,不能单独证明最终候选身份。
SLSA Provenance v1.1 用产物主体、构建者、构建类型、外部参数和依赖材料描述构建证明。加固步骤不一定等同普通编译,但该模型提醒团队把产物摘要与执行上下文绑定。in-toto Attestation Statement v1 又要求用 subject 摘要和有类型的 predicate 组织声明。两者都不保证声明天然真实,因此还需要可信签名者、策略门禁和对候选文件的再次计算。
评审云端与本地方案时,应要求两边产出同等可读的证据集合。若云端能给出不可混淆的输入输出摘要而本地只留终端截图,不能因为后者位于内网就忽略证据缺口;反过来,若云端报告无法识别处理版本、参数和材料,集中平台的精美界面也不能补足可追溯性。证据标准先统一,部署差异才具有可比较性。
| 证明字段 | 要回答的问题 | 最低核验 | 不能证明 |
|---|---|---|---|
| subject 摘要 | 产物究竟是哪一个 | 重新计算摘要 | 运行时安全 |
| builder 身份 | 谁执行处理 | 验证签名身份 | 人员无误操作 |
| 构建类型 | 采用什么流程 | 匹配批准类型 | 配置内容正确 |
| 外部参数 | 任务接受哪些选项 | 与审批单比对 | 隐藏参数不存在 |
| materials | 输入和依赖是什么 | 检查来源与摘要 | 所有依赖可信 |
| 验证回执 | 测了哪些边界 | 绑定同一候选 | 未测场景通过 |
日志、临时文件与删除回执要按数据流逐点核对
处理环境会产生上传副本、解包目录、编译缓存、任务队列、诊断日志、崩溃转储和交付制品。评审不能只问保留多久,还要问每类对象由谁创建、存在哪里、谁能读取、什么事件会延长保留、删除是否覆盖副本与索引。若安全事件触发证据保全,清理动作可能依法规或组织决定暂停;如果没有保全需求,则超期副本也不能因排障方便无限延长。
日志字段需用样本验证。文件名、包名、方法名、路径、租户标识和错误上下文都可能暴露项目结构,完整命令行还可能意外带入凭据。可用任务编号和摘要替代原始内容的地方应尽量替代,支持人员只读取解决问题所需的片段。云端方案要说明平台日志和子系统日志,本地方案要说明操作系统、代理和集中采集器,避免只看应用日志。
删除回执应明确对象范围和状态,而不是只显示任务已结束。组织需要知道哪些活动数据已清理,哪些审计材料按批准政策保留,哪些备份等待轮换,哪些事项因调查暂缓。回执不能证明存储介质不存在任何物理残留,但能让责任人确认逻辑访问、服务副本和保留例外都有明确记录,从而避免把不可验证的绝对删除写进合同或验收结论。
用可执行规则检查交付矩阵里的硬性缺口
下面的 Python 示例读取一份不含密钥和内部地址的 JSON 决策矩阵,检查每个方案是否声明输入数据级别、处理边界、签名位置、日志策略、供应链证明和退出方案。它会阻止把生产私钥列为可外发输入,也会拒绝高敏感数据在没有批准边界时进入托管环境。代码只验证清单完整性和显式规则,不连接服务、不上传制品,也不替代架构评审。
矩阵中的 controls 是公开安全的控制标识,不存放访问令牌、私钥内容或真实项目路径。评审者可以把云端和本地候选分别写成记录,在同一规则下运行,再把失败项交给责任人补证。这样能避免方案名称影响判断,也能防止某次会议口头同意覆盖既定红线。通过只表示输入满足这些静态规则,并不表示供应方陈述已经独立核实。
实际项目可在不改变核心规则的前提下增加区域、合规、可用性和恢复字段,但每项新增规则都应说明证据来源与例外审批人。若某个方案确实需要特殊密钥流程,不能删除检查来取得通过,而应把它留作失败项并走单独风险决定。最终选择还要结合真实制品敏感度、组织运维能力和合同边界,代码结果只是结构化讨论的入口。
import json
import sys
from pathlib import Path
REQUIRED_FIELDS = {
'name',
'mode',
'dataClass',
'processingBoundary',
'signingLocation',
'logsPolicy',
'provenance',
'exitPlan',
'controls',
}
ALLOWED_MODES = {'managed-cloud', 'on-premises'}
ALLOWED_DATA = {'public', 'internal', 'confidential', 'restricted'}
SENSITIVE_FIELDS = {'privateKey', 'token', 'password', 'customerData'}
def fail(message):
raise SystemExit(message)
def validate_option(option, index):
if not isinstance(option, dict):
fail(f'option {index} is not an object')
leaked = SENSITIVE_FIELDS.intersection(option)
if leaked:
fail(f'option {index} contains forbidden fields: {sorted(leaked)}')
missing = sorted(REQUIRED_FIELDS - option.keys())
if missing:
fail(f'option {index} lacks fields: {missing}')
if option['mode'] not in ALLOWED_MODES:
fail(f"option {index} has invalid mode")
if option['dataClass'] not in ALLOWED_DATA:
fail(f"option {index} has invalid data class")
controls = option['controls']
if not isinstance(controls, list) or not controls:
fail(f'option {index} has no controls')
if len(controls) != len(set(controls)):
fail(f'option {index} repeats a control')
boundary = str(option['processingBoundary']).strip()
if not boundary:
fail(f'option {index} has no processing boundary')
signing = str(option['signingLocation']).strip().lower()
if signing in {'external-private-key', 'uploaded-private-key'}:
fail(f'option {index} externalizes a production private key')
if option['mode'] == 'managed-cloud':
if option['dataClass'] == 'restricted' and 'external-processing-approved' not in controls:
fail(f'option {index} sends restricted data without approval')
if 'tenant-isolation-reviewed' not in controls:
fail(f'option {index} lacks tenant isolation review')
if option['mode'] == 'on-premises':
if 'host-baseline-owned' not in controls:
fail(f'option {index} has no host baseline owner')
if 'update-path-verified' not in controls:
fail(f'option {index} has no verified update path')
for field in ('logsPolicy', 'provenance', 'exitPlan'):
if not str(option[field]).strip():
fail(f'option {index} has an empty {field}')
return {'name': option['name'], 'mode': option['mode'], 'status': 'pass'}
if len(sys.argv) != 2:
fail('usage: validate_delivery.py decision.json')
source = Path(sys.argv[1])
if not source.is_file():
raise SystemExit('decision file is missing')
data = json.loads(source.read_text(encoding='utf-8'))
options = data.get('options')
if not isinstance(options, list) or not options:
fail('options array is missing')
results = [validate_option(option, index) for index, option in enumerate(options)]
print(json.dumps({'checked': len(results), 'results': results}, ensure_ascii=False))用退出能力和责任闭环完成最终选择
最终决策表应把业务约束、控制证据、责任人和未决项放在一起。云端候选如果能接受的数据级别更低,就记录需要脱敏或拆分的输入;本地候选如果运维能力不足,就记录补齐主机、身份、更新和恢复责任的成本与时限。决策不是简单计算勾选数量,而是先检查不可接受的红线,再比较可补救缺口,最后由有权承担剩余风险的人批准。
退出计划必须在签约或部署前写清。云端需要确认任务数据、账号、接口授权、支持工单和保留副本怎样处置;本地需要确认许可证、组件、离线更新材料、远程支持身份和本地证据怎样移交或回收。两种模式都要保留足够的产物摘要、配置身份和验证回执,让团队更换处理方式后仍能解释历史候选,而不是被某个控制台或专有报告格式锁住。
准备评估时,可先整理输入分类表、签名流程、网络边界、人员角色、日志样本、来源证明要求和退出清单,再参考本站的软件加固交付报告阅读方法核对证据字段。需要进一步确认项目边界,可携带这组材料通过御盾中央平台提交申请。实际部署形态、处理区域、性能和兼容结论必须以项目确认与同一候选回执为准,本文不替任何方案预先背书。
| 决策项 | 云端候选 | 本地候选 | 批准条件 |
|---|---|---|---|
| 数据边界 | 外发级别与降敏 | 内网处理与复制 | 所有输入有所有者 |
| 密钥边界 | 签名回到受控区 | 签名服务隔离 | 私钥不随任务流转 |
| 运维责任 | 服务方控制回执 | 客户持续执行 | 角色和时限明确 |
| 证据能力 | 任务和产物证明 | 本地日志和证明 | 绑定同一候选 |
| 故障支持 | 受限支持流程 | 诊断包或临时通道 | 访问可撤销 |
| 退出能力 | 清理与账号撤销 | 组件与支持回收 | 证据已移交 |
事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| 移动端交付评审应最小化敏感数据访问,并把第三方组件的数据行为纳入责任边界。 | OWASP MASVS-PRIVACY-1 明确要求最小化敏感数据访问并审视第三方 SDK 的数据使用。 | 该控制不替代具体业务的数据分类、同意记录、合同判断或合规审查。 |
| 安全交付应记录来源、构建、验证、变更和供应链风险处置,而不是只保留最终文件。 | NIST SP 800-218 SSDF 提供组织级安全软件开发实践及供应链风险方向。 | SSDF 不规定某个 App 加固产品功能,也不决定云端或本地方案的唯一答案。 |
| 产物证明可以把制品主体与构建者、构建类型、参数和依赖材料联系起来。 | SLSA Provenance v1.1 定义 provenance 模型及其主要字段语义。 | provenance 记录不单独证明运行安全、配置正确、陈述真实或兼容验证通过。 |
| 供应链声明应使用摘要标识产物,并用明确类型组织声明负载。 | in-toto Attestation Statement v1 定义 subject、predicateType 与 predicate 的结构。 | 格式正确不能替代可信签名者、声明策略、产物复算和项目门禁。 |
| Android 设备上的应用密钥可以受用途约束,并在可用条件下使用硬件保护。 | Android Keystore 说明密钥用途限制和可用设备上的硬件保护机制。 | Keystore 不证明构建端发布私钥已隔离,也不保护密钥使用后暴露的明文。 |
| Android 发布链需要区分应用签名密钥、上传密钥、证书和托管签名责任。 | Sign your Android app 明确描述应用签名、上传密钥与 Play App Signing 的关系。 | 官方文档不能确认某个实际候选使用了正确生产证书,也不能证明加固有效。 |
| 云端与本地化交付应按同一组数据、密钥、人员、证明和退出字段比较。 | 工程判断:统一字段能把部署名称还原为可核验的控制事实,并暴露未知项。 | 具体阈值由组织风险偏好、适用规则、合同和真实架构决定,不能照表自动采购。 |
| 处理位置改变后,输入敏感度和持续运维责任都可能成为决定性约束。 | 工程判断:外部处理扩大组织边界,本地处理则增加主机、更新、身份与恢复责任。 | 该判断不表示任何部署模式天然更安全,仍需以具体控制证据和项目确认作结论。 |
工程常见问题
源码不上传,只传 APK,云端加固就一定没有数据风险吗?
不能这样判断。APK、原生库、资源、符号、映射、保护策略和诊断日志仍可能暴露代码结构或业务信息,需要分别定级并核对落盘、访问和删除边界。
本地化部署是否天然满足保密要求?
不天然满足。它减少跨组织传输,却需要本地团队持续承担主机、身份、更新、日志、备份、恢复和远程支持控制,任何缺口都可能削弱物理位置带来的好处。
生产签名私钥能否交给加固处理环境?
不应把外发私钥设为默认流程。优先让未签名产物完成处理,再回到原有受控签名边界;特殊需求应单独评审调用限制、审批、轮换、撤销和签名回执。
传输使用 TLS,是否足以证明云端交付安全?
不够。还要核对处理区、租户隔离、存储副本、任务队列、缓存、备份、日志、人员支持路径、保留例外和清理回执。TLS 只覆盖传输链路的一部分。
供应链 provenance 能证明加固产物已经安全有效吗?
不能。它可以绑定产物主体与构建上下文,但运行时安全、兼容范围、配置正确性和声明真实性仍需可信签名、策略门禁、文件复算和实际验证。
向御盾中央平台提交交付边界评估前需要准备什么?
准备输入分类、禁止外发项、签名流程、处理网络、人员角色、日志样本、组件更新、产物证明要求、故障支持和退出清单,并明确哪些事实已经核验、哪些仍待确认。