先看结论与判断条件
- 上传密钥回答提交者是否被 Play 接受,应用签名密钥回答设备最终 APK 以哪个应用身份签署,两者处于不同信任边界。
- 启用 Play App Signing 后,上传制品与用户设备收到的 APK 可能处于不同签名阶段,必须分别保存文件摘要和公开证书摘要。
- 证书是可分发的公开身份材料,私钥必须留在受控签名边界;核对责任矩阵不需要也不应读取任何私钥。
- 上传密钥事件与应用签名密钥事件影响不同,应分别定义发现、冻结、恢复、渠道沟通和证据保存责任。
- Android 更新身份依赖 applicationId、可接受版本和兼容应用签名关系,上传证书本身不能建立设备升级连续性。
- 构建、上传、商店签名和最终交付的每份回执都应绑定产物摘要,避免把上传候选结论复制到设备交付 APK。
两个密钥对应两次不同的信任判断
团队把“发布签名”当成一个动作时,最容易混淆上传身份与设备身份。向 Play 提交版本时,平台需要确认提交者属于被授权的发布流程;向用户设备交付 APK 时,Android 需要确认安装包具有预期应用签名身份。两次判断发生在不同位置,使用的文件、证书和责任人也可能不同。
Sign your Android app 区分应用签名密钥、上传密钥、证书和 Play App Signing。上传密钥用于签署上传制品以认证提交,应用签名密钥则用于签署分发给用户的 APK。启用托管签名后,Play 位于两者之间完成分发签名,因此本地上传成功不能直接说明用户最终收到哪个签名者。
工程治理应为每个身份建立独立记录:角色名称、公开证书 SHA-256、使用阶段、密钥托管边界、允许操作人、审批方式、审计来源和事件联系人。记录可以引用同一应用项目,但不能合并为一个“生产证书”字段,否则问题发生时无法判断受影响的是提交权限还是设备更新身份。
| 对象 | 主要用途 | 验证位置 | 直接影响 |
|---|---|---|---|
| 上传密钥 | 认证向 Play 提交的制品 | 商店上传入口 | 能否接受新提交 |
| 应用签名密钥 | 签署用户设备收到的 APK | 商店签名与 Android 设备 | 安装和更新身份 |
| 上传证书 | 公开标识上传签名者 | Play 项目配置与发布记录 | 提交者身份核对 |
| 应用签名证书 | 公开标识设备 APK 签名者 | 渠道证书记录与设备包 | 更新关系核对 |
| 产物摘要 | 标识每一阶段真实字节 | 构建、上传、分发归档 | 避免回执错配 |
上传密钥只负责认证提交者,不签署最终设备身份
上传流程的核心问题是“这个版本是否由允许的发布者提交”。受控流水线用上传密钥签署 AAB 或 APK,Play 根据项目登记的上传证书决定是否接受。这个机制把日常提交权限与应用签名密钥的长期设备身份分开,使发布团队不必在每次上传时直接接触应用签名私钥。
上传成功只证明平台接受了该提交身份和制品格式,不能证明制品经过全部业务审批、没有漏洞或最终交付文件保持相同字节。发布记录仍要保存上传制品摘要、applicationId、versionCode、上传证书摘要、提交账号、审批单和 Play 回执。任何重建都产生新的摘要,不能沿用同版本旧回执。
上传密钥的授权范围也要最小化。构建系统、人工发布和紧急流程分别由谁可触发,应在组织权限中明确;私钥不进入仓库、工单、日志或文章示例。工程判断是让日常流水线只获得完成提交所需的受控签名能力,而不是能导出或任意复制密钥材料。具体托管方案由项目威胁模型决定。
| 字段 | 来源 | 用途 | 不能推出 |
|---|---|---|---|
| 上传制品摘要 | 最终待上传文件 | 绑定提交回执 | 设备 APK 字节相同 |
| 上传证书摘要 | 项目登记与签名输出 | 识别提交身份 | 应用更新身份 |
| applicationId 与版本 | 上传制品解析 | 定位 Play 项目和版本 | 业务兼容通过 |
| 提交主体 | 渠道审计和组织账号 | 追踪谁触发上传 | 私钥由该人持有 |
| Play 接收回执 | 商店发布记录 | 证明制品被平台接收 | 已签署并交付用户 |
应用签名密钥维持设备安装与更新身份
AOSP app signing 说明 Android 使用应用签名建立应用身份与更新关系,并由不同签名方案覆盖不同文件区域和平台版本。用户设备安装 APK 时不会查看团队的上传证书是否被 Play 接受,而是验证设备交付 APK 的应用签名。应用签名密钥因此承担更长期的设备身份责任。
How Android app updates work 说明更新还需要保持 applicationId、兼容签名关系并使用可接受的 versionCode。上传密钥相同不能弥补应用签名关系错误,上传成功也不能证明历史安装可升级。更新连续性要基于用户设备最终 APK 的证书、平台版本与真实基线检查,而不是基于本地上传包。
应用签名有效也不证明业务代码安全。合法应用签名密钥可以签署有缺陷的版本,错误构建也可能被正常签名。应用签名证据只支持完整性与签名者身份结论;源码审查、供应链证明、加固检查、设备回归和业务批准仍是独立门禁。治理文档应避免把“正式证书”写成全部安全结论。
| 检查 | 主要证据 | 通过含义 | 独立门禁 |
|---|---|---|---|
| 应用签名 | 设备 APK 与证书 | 完整性和签名身份有效 | 代码与依赖审查 |
| applicationId | 最终 APK 解析 | 应用标识保持一致 | 服务端客户端策略 |
| versionCode | 最终 APK 解析 | 满足平台更新顺序 | 业务防回放与最低版本 |
| 设备升级 | 真实基线覆盖安装 | 指定路径身份连续 | 其他设备和渠道矩阵 |
| 业务回归 | 设备端输入输出断言 | 关键语义满足预期 | 签名与密钥治理 |
证书可以公开核对,私钥必须留在签名边界
证书包含公钥和身份信息,可以导出、分发并计算摘要,用于允许列表和责任矩阵。私钥用于产生签名,泄露后可能让未授权方冒用对应身份。核对两类密钥责任时只需要证书摘要、签名回执和渠道配置,不需要打开 keystore,更不应要求操作人员把私钥或口令上传到审计工具。
Android apksigner 可以验证签名方案覆盖并打印证书。它适合从具体 APK 提取公开签名信息,但工具验证成功不证明 keystore 管理、渠道流程或归档正确。上传阶段和分发阶段的文件应分别运行验证并保存输出,明确每份文件属于哪个阶段;本文不重复最终 APK signer 的详细门禁。
证书名称不是可靠唯一标识。主题字段可以重复或填写宽泛,责任矩阵应使用规范化 SHA-256,并附项目、环境、角色和生效记录。若同一证书在多个角色中出现,也要如实登记而不是假定必然错误;是否分离取决于项目和托管状态,但两种责任仍应保持独立审批与回执。
| 材料 | 是否进入核对清单 | 用途 | 边界 |
|---|---|---|---|
| 证书 SHA-256 | 是 | 稳定识别公开签名身份 | 不提供签名能力 |
| 产物 SHA-256 | 是 | 绑定上传或分发文件 | 不识别密钥责任 |
| apksigner 输出 | 是 | 验证签名并提取证书 | 不证明密钥托管 |
| 私钥文件 | 否 | 只在受控签名系统使用 | 不得进入审计输入 |
| 密钥口令或令牌 | 否 | 仅由秘密管理边界处理 | 不得写入日志和报告 |
Play App Signing 让产物在上传和分发间发生身份转换
启用 Play App Signing 后,团队上传的 AAB 或 APK先以上传身份进入平台,Play 再使用应用签名密钥为设备交付 APK 签名。AAB 本身也可能由渠道派生多组 APK。供应链台账因此至少要区分构建候选、上传候选、Play 接收版本和设备交付文件,不能只保存一个文件名。
每个阶段都应使用自己的摘要和回执。构建证明绑定上传候选摘要,Play 回执绑定应用项目和版本,设备抽样则记录实际 APK 摘要与应用签名证书。阶段之间用 versionCode、发布 ID 和受控映射连接。若只拿本地上传 APK 去验证设备签名证书,结论对象本身就错了。
渠道派生还意味着设备规格不同可能收到不同 APK 集。应用签名身份应保持渠道规则下的连续性,但文件摘要会因 ABI、密度、语言或功能 split 不同而变化。工程判断是抽样代表性设备交付物,验证公开证书和安装契约;没有真实渠道回执时只记录本地上传阶段,不能声明设备分发已核验。
| 阶段 | 典型对象 | 应记录证书 | 应记录回执 |
|---|---|---|---|
| 构建完成 | 未提交候选 | 流水线实际签名信息 | 构建摘要和审批 |
| 上传候选 | AAB 或 APK | 上传证书摘要 | Play 接收结果 |
| 渠道处理 | 应用版本和派生配置 | 应用签名证书配置 | 发布与轨道状态 |
| 设备交付 | 设备特定 APK 集 | 实际应用签名证书 | 设备文件摘要与 split |
| 设备升级 | 旧版到新版安装路径 | 兼容应用签名关系 | 安装和业务回归 |
两类密钥事件需要不同处置剧本
上传密钥事件首先影响新版本提交权限。团队需要冻结相关凭据、确认异常提交、联系渠道并恢复受控上传身份,同时保存受影响时间、账号、制品摘要和平台回执。应用签名密钥事件影响的是用户设备信任的应用身份,范围通常更接近长期更新连续性与冒用风险,不能套用同一处置清单。
事件分级应以实际能力和证据为准。发现证书公开并不等于私钥泄露,因为证书本来可以公开;发现 keystore 路径也不自动证明密钥已被导出。团队应收集签名操作日志、秘密管理审计、异常制品和渠道记录,区分误报、权限滥用和密钥材料泄露。没有证据时不扩大结论。
恢复后要重新绑定角色。新的上传身份、应用签名身份或渠道配置都应更新责任矩阵,并明确哪些历史回执仍有效。本文不提供密钥轮换和 lineage 操作步骤,只要求事件处置记录角色、影响、批准和验证结果。真实项目的可恢复路径受渠道状态与历史安装基础约束。
| 事件 | 首要影响 | 优先动作 | 必须保留 |
|---|---|---|---|
| 上传凭据疑似泄露 | 新提交可能被冒用 | 冻结提交并核对渠道记录 | 账号、时间和制品摘要 |
| 异常版本被接收 | 发布流程被绕过 | 停止推进并定位批准链 | Play 回执与上传身份 |
| 应用签名风险 | 设备身份和更新受影响 | 启动高等级事件治理 | 签名日志与设备证据 |
| 证书配置错误 | 责任矩阵与实际文件错配 | 阻断发布并核对阶段 | 证书摘要和文件摘要 |
| 私钥未证实暴露 | 影响尚不确定 | 保护证据并限制权限 | 审计来源与判断边界 |
用 SSDF 与 attestation 连接责任、产物和审批
NIST SP 800-218 SSDF 提倡保留来源、构建、验证和变更证据,并把供应链风险纳入安全开发。应用到两类密钥治理时,团队要记录谁构建、谁批准上传、哪个服务执行上传签名、渠道何时接受、哪个应用签名证书用于设备交付,以及事件由谁处置。SSDF 不规定具体托管产品。
in-toto Attestation Statement v1 用 subject digest 把产物与有类型 predicate 绑定。上传审批声明可以引用上传候选摘要,设备交付抽样声明则引用实际 APK 摘要;两者 predicateType 与签名者可以不同。该结构降低报告错配风险,但格式正确不保证声明内容真实,仍需验证声明签名和授权策略。
责任矩阵最终应可回答四个问题:谁能触发提交,谁控制应用签名,哪份公开证书代表各角色,哪个产物摘要被哪个回执覆盖。若答案来自多个系统,台账只保存受控引用和摘要,不复制秘密。项目数据尚未接入时,只能建立检查框架,不能声称密钥隔离或发布流程已经通过。
- 上传和应用签名角色分开登记
- 每个角色使用公开证书摘要识别
- 构建、上传和分发产物分别绑定摘要
- 审批声明引用精确 subject digest
- 私钥和口令不进入审计材料
- 事件联系人和恢复责任分别明确
用只读脚本核对证书责任矩阵
下面的 Python 示例读取公开责任策略和两条证书记录,一条代表上传候选,一条代表设备分发样本。它检查角色、阶段、应用标识、版本、产物摘要和证书摘要,并与策略登记值比对。脚本只处理 JSON 中的公开摘要,不读取 keystore、私钥、口令或签名令牌。
示例不强制两枚证书必须不同,因为历史项目和托管状态可能不同;它输出 identitiesSeparated 供评审,而角色责任始终分开。设备分发记录必须来自真实渠道抽样,不能把上传候选复制一份改名。真实系统还要验证 apksigner 原始输出和 Play 回执,本脚本只检查结构化责任矩阵是否闭合。
准备 Play 发布链的软件加固评估时,可整理上传候选摘要、上传证书、Play 接收回执、设备交付 APK 摘要、应用签名证书、权限矩阵和事件处置边界,再通过御盾中央平台提交申请。所有字段均应来自真实材料,不能用 README 或模型回答代替渠道证据。
- 策略只保存证书摘要和角色,不保存私钥
- 上传记录绑定 Play 提交阶段
- 分发记录来自设备交付样本
- applicationId 与 versionCode 连接两个阶段
- 每份回执绑定各自产物摘要
- 身份是否分离与责任是否分开分别判断
from pathlib import Path
import json
import re
import sys
if len(sys.argv) != 4:
raise SystemExit(2)
policy_path = Path(sys.argv[1])
upload_path = Path(sys.argv[2])
delivery_path = Path(sys.argv[3])
if not policy_path.is_file() or not upload_path.is_file() or not delivery_path.is_file():
raise SystemExit(2)
policy = json.loads(policy_path.read_text(encoding="utf-8"))
upload = json.loads(upload_path.read_text(encoding="utf-8"))
delivery = json.loads(delivery_path.read_text(encoding="utf-8"))
sha256_pattern = re.compile(r"^[0-9a-f]{64}$")
for record in (upload, delivery):
if not sha256_pattern.fullmatch(str(record.get("artifactSha256", "")).lower()):
raise SystemExit(2)
if not sha256_pattern.fullmatch(str(record.get("certificateSha256", "")).lower()):
raise SystemExit(2)
if upload.get("role") != "upload" or upload.get("stage") != "play-submission":
raise SystemExit(2)
if delivery.get("role") != "app-signing" or delivery.get("stage") != "device-delivery":
raise SystemExit(2)
if upload.get("applicationId") != delivery.get("applicationId"):
raise SystemExit(2)
if int(upload.get("versionCode", -1)) != int(delivery.get("versionCode", -2)):
raise SystemExit(2)
expected_upload = str(policy.get("uploadCertificateSha256", "")).lower()
expected_app = str(policy.get("appSigningCertificateSha256", "")).lower()
if upload["certificateSha256"].lower() != expected_upload:
raise SystemExit(2)
if delivery["certificateSha256"].lower() != expected_app:
raise SystemExit(2)
result = {"status": "responsibility-matrix-matched", "applicationId": upload["applicationId"], "versionCode": upload["versionCode"], "uploadArtifactSha256": upload["artifactSha256"], "deliveryArtifactSha256": delivery["artifactSha256"], "identitiesSeparated": expected_upload != expected_app}
print(json.dumps(result, ensure_ascii=False, indent=2))事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| Android 发布需要区分应用签名密钥、上传密钥、证书与 Play App Signing 责任。 | Sign your Android app 说明托管签名和两类密钥的用途。 | 文档不能确认某个实际上传或设备 APK 使用了正确生产证书。 |
| Android 使用应用签名建立设备上的应用身份和更新关系。 | AOSP app signing 描述应用签名、更新身份和签名方案。 | 签名有效不证明业务代码安全、上传审批正确或设备回归通过。 |
| apksigner 可以验证 APK 签名方案并打印公开证书信息。 | Android apksigner 说明 sign、verify 和 print-certs 等工具行为。 | 工具成功不证明私钥托管、渠道流程、角色审批或候选归档正确。 |
| Android 更新要求 applicationId、可接受版本和兼容应用签名关系。 | How Android app updates work 说明应用更新的基本条件。 | 平台更新条件不等于业务防回放、数据迁移或完整发布治理。 |
| 安全发布应保留来源、构建、验证、变更和供应链风险证据。 | NIST SP 800-218 SSDF 提供组织级安全软件开发实践。 | SSDF 不定义具体 Play 托管方案或某个加固产品的能力。 |
| 供应链声明可以用产物摘要绑定有类型的审批或交付事实。 | in-toto Attestation Statement v1 定义 subject、predicateType 与 predicate 结构。 | 声明格式不保证内容真实,仍需验证签名者、授权策略和真实渠道回执。 |
| 上传身份与应用签名身份必须在责任矩阵中分别登记。 | 工程判断:二者验证位置、受影响对象和事件后果不同,合并字段会阻断责任追踪。 | 责任分离不自动证明技术上使用不同证书,也不证明私钥托管合规。 |
| 上传候选回执不能直接复制为设备交付 APK 的签名结论。 | 工程判断:Play 托管签名和设备派生使上传制品与交付文件处于不同阶段。 | 具体渠道是否改变签名和字节,必须由真实 Play 与设备回执确认。 |
工程常见问题
Play 上传密钥和应用签名密钥是同一个东西吗?
不是同一责任。上传密钥认证谁可向 Play 提交,应用签名密钥签署用户设备收到的 APK 并维持应用身份。项目应分别登记两类角色。
上传 AAB 成功是否证明设备 APK 使用了正确证书?
不能。上传成功说明提交身份被接受。启用 Play App Signing 后,还要从渠道配置和设备交付样本核对应用签名证书及实际 APK。
责任审计为什么只需要证书摘要,不需要私钥?
证书及其 SHA-256 足以公开识别签名身份。私钥提供签名能力,必须留在受控边界,不应进入核对脚本、工单、日志或报告。
上传证书与应用签名证书相同是否一定配置错误?
不能仅凭相同下结论,历史项目和托管状态可能不同。但提交责任与设备身份责任仍要分开审批、记录和处置,并核对真实渠道配置。
上传密钥泄露会直接破坏已安装应用的更新身份吗?
影响重点是未授权提交风险,应用签名身份由另一角色承担。仍需立即冻结提交、检查异常版本和渠道记录,不能把影响范围凭空扩大或缩小。
申请 Play 发布链评估前要准备哪些材料?
准备上传候选与证书摘要、Play 接收回执、设备交付 APK 与应用签名证书、权限矩阵和事件处置边界,再从御盾中央平台提交申请。