跳转至正文

签名验证:保障 2026 年 App 更新安全

了解签名验证如何保护 Capacitor 和 Electron 应用更新,涵盖密码学原理与 CI/CD 集成。

签名验证:保障 2026 年 App 更新安全

你的更新 pipeline 显示绿色,bundle 已部署到边缘节点,设备也在检查更新。然后有人发现生成的 JavaScript 中存在可疑变更。被入侵的 build runner、被盗的开发者凭证或被篡改的 artifact,可能在测试结束后将恶意 payload 混入发布版本。如果没有 签名验证,客户端无法可靠地区分团队构建的 bundle 和攻击者在传输或交付层篡改过的 bundle。

签名更新改变了这一决策。设备在替换当前运行的 code 之前,会使用受信任的公钥验证 bundle。如果签名不匹配,更新将保持非活动状态。这听起来很简单,但生产环境中的故障通常发生在密码学周边,而非其内部。团队往往丢失密钥轮换记录、签错了 artifact、应用了未经验证的缓存文件,或者收集了太少的遥测数据,导致无法解释哪些设备拒绝了更新。

以下章节聚焦于这些操作边缘,从信任模型和验证流程,到 CI/CD 控制、事件响应,以及签名本身无法解决的局限性。

目录

签名验证如何防止灾难性更新

某热门 Capacitor 插件的 CI/CD 流水线在发布后期遭到入侵。攻击者篡改了 live update bundle,植入了读取应用数据的 code,并通过流水线的正常分发路径发布了该产物。设备端不会看到陌生的下载域名,只会看到一个等待安装的有效更新。

如果没有客户端加密校验,应用可能在更新策略允许时立即下载并执行被篡改的 bundle。潜在影响范围包括数据泄露、凭证窃取、支付流程篡改以及业务逻辑的静默操纵。调查过程也极为痛苦。工程师必须确定分发的是哪个产物、哪些渠道接收了它、哪些设备下载了它、哪些设备应用了它,以及恶意的 code 是否在撤回发布前已执行。

示意图:签名验证如何阻止软件更新分发流水线中的恶意 code 注入。

启用签名验证的团队会面临不同的故障模式。应用下载了同样的被污染 bundle,计算预期摘要,并对照可信密钥检查附带的签名。加密校验失败,更新器拒绝激活该 bundle,并在新的 code 运行前向安全团队发送事件。

生产环境规则: 在设备验证其身份和确切字节之前,应将更新视为具有敌意。

只有当验证器在设备上运行,且在提取或激活之前执行,并且可信密钥无法被更新本身替换时,保护才有效。这使得证书和密钥管理成为更新设计的一部分,而非行政细节。使用 Capacitor 的团队应将其与 证书管理流程一起记录,包括谁可以签名、密钥存储在哪里,以及客户端如何获知授权的后继密钥。

几十年来,现代研究一直将签名验证视为一门可衡量的工程学科。首批关于离线和在线签名验证的公开研究出现在 1977 年,后续工作扩展到了包括 HMM 和 FFT 在内的方法。一项被广泛引用的比较报告称,人类专家的 误接受率约为 0.5%,误拒绝率为 7%,而外行人的 误接受率为 6.5%,误拒绝率为 26%,正如 这是签名验证研究的历史概览。这些数字涉及手写签名,而非软件更新,但它们强化了一个有用的观点:验证质量取决于验证器、其参考数据及其决策策略。

加密签名如何建立信任

把发布 bundle 想象成一个密封的信封。构建系统会计算精确字节的摘要,并使用一个 私钥 为该摘要创建数字签名。App 会包含或安全地接收对应的 公钥,它就像信封上已知的徽记。如果攻击者改动了 bundle 的哪怕一小部分,App 计算出的摘要就会不同,签名便不再通过验证。

展示从开发者创建签名到移动 App 验证的密码学签名流程图。

该流程包含四个不同环节:

  1. 哈希 将 bundle 转换为固定长度的摘要。在这一完整性校验步骤中,SHA-256 和 SHA-512 是常用的选择。
  2. 密钥生成 创建非对称密钥对。私钥用于签名,公钥用于验证。
  3. 签名 将摘要与发布元数据绑定,最好包含版本、channel、平台和受众信息。
  4. 验证 从下载的字节重新计算摘要,并检查签名是否由受信任的私钥生成。

验证器必须将签名绑定到更新器将要应用的同一 payload。如果应用后续下载 bundle 时未检查 manifest 的哈希是否与 bundle 匹配,仅对 manifest 进行签名是不够的。同样,验证 bundle 的哈希并不能确定谁授权了它,除非哈希本身经过认证。

为移动端分发选择算法

RSA 依然熟悉且支持广泛,但通常需要更大的密钥材料和谨慎的填充选择。对于新的移动端更新协议,Ed25519 往往更具吸引力,因为其密钥和签名紧凑,且在移动处理器上的验证路径高效。当兼容性要求使得 RSA 成为必需时,RSA-PSS 也是合适的选择。选择应遵循平台加密库、支持的硬件、互操作性要求以及迁移计划,而不是从其他环境复制的基准测试。

一个有用的独立介绍是这份指南: 理解区块链中的加密签名。交易上下文与 OTA 分发不同,但关于私钥授权和公钥验证的解释可以直接迁移。

信任链与固定密钥

证书链通过中间机构将信任从根证书委托给叶子证书。该模型可以简化广泛的 PKI 操作,但应用更新客户端通常有更窄的需求:仅信任授权此更新的发布者密钥。将公钥或一组授权密钥直接嵌入应用二进制文件中是一种固定(pinning)形式。它减少了对外部证书机构的依赖,但会产生轮换问题,因为二进制文件必须已经信任替换密钥。

保持签名信封完整。您的发布元数据应标识工件、其摘要、目标通道以及密钥标识符。将此功能集成到 Capacitor 中的团队可以使用一个专注于 Capacitor 应用的令牌签名检查清单 在发布前审查密钥范围、存储和验证边界。

在移动和 Web 系统中的实际应用

签名验证出现在移动产品的多个层级中,每个层级回答不同的问题。应用商店签名帮助操作系统判断可安装包是否来自授权发布者。签名的 OTA bundle 回答 JavaScript 和资源负载是否来自更新器信任的发布权限。JWT 签名帮助 API 验证令牌是否由预期的身份服务颁发。

混淆这些层级会产生安全缺口。有效的 IPA 签名并不能自动认证后续的 web bundle。有效的 JWT 无法证明更新包是安全的。TLS 连接保护传输过程,但当 CDN、代理、缓存或构建系统成为篡改源头时,它无法替代产物签名。

上下文 签名机制 防止的故障模式 常见缺口
应用商店包 平台 code 签名及平台审核控制 重新签名或未授权的可安装包 团队假设商店签名覆盖了安装后的 web 资源
Web bundle 和服务工作者 签名交换或 SRI 风格的完整性引用 被投毒的 CDN 响应或修改后的资源 仅检查入口文件,而导入的资源保持未验证状态
API 令牌 使用受信任的公钥验证 JWT 签名,该公钥通常通过 JWKS 端点获取 伪造或篡改的 token 服务器验证了签名,但忽略了签发者、受众、过期时间或 token 用途
空中更新 在设备上验证分离式或嵌入式 bundle 签名 中间人攻击或篡改的更新注入 客户端在执行决策前下载、缓存或解包内容

对于 Web 资源,子资源完整性(SRI)可以限制浏览器接受引用资源的内容,但它并不能自动解决动态导入、Service Worker 缓存或指向攻击者选定文件的更新清单问题。实现必须定义完整的工件集并验证将要执行的字节。

JWT 部署以不同的方式失败。工程师通常发布了正确的公钥,但接受了具有错误签发者或受众的 token,或者信任了由 token 头提供的算法选择。加密签名可能是有效的,但授权决策仍然是错误的。

对于依赖频繁发布和面向客户的移动体验的产品,运营环境也很重要。正在评估的 2026 年零售应用用户参与策略 应将更新完整性视为快速实验的前提条件。只有当发布渠道、制品和接收方都绑定到同一授权决策时,快速发布才有意义。

构建完整的验证流程

生产环境的更新器应将验证作为关卡,而不是在安装附近某处运行的回调。安全的顺序是确定性的:

  1. 通过经过身份验证的传输获取清单和签名。
  2. 验证清单结构、版本策略、渠道、有效期和制品身份。
  3. 下载清单中指定的确切 bundle。
  4. 在本地计算 bundle 摘要。
  5. 使用受信任的 Ed25519 或 RSA-PSS 公钥验证签名。
  6. 将已验证的制品存储在隔离位置。
  7. 原子性地应用它,然后保留回滚路径。

一个四步图表,说明获取、签名验证和验证软件更新 bundle 的过程。

清单必须绑定所有影响决策的值。至少包括 bundle 摘要、版本、channel、平台以及密钥标识符。切勿允许下载器在验证后替换 URL、文件名或 channel。验证器应接收不可变的字节和不可变的元数据,然后返回单一的接受或拒绝结果。

简化的 TypeScript 结构如下:

type UpdateManifest = {
  version: string
  channel: string
  platform: string
  sha256: string
  signature: string
  keyId: string
}

async function verifyBundle(
  bundle: Uint8Array,
  manifest: UpdateManifest,
  trustedKeys: Map<string, Uint8Array>
): Promise<boolean> {
  const publicKey = trustedKeys.get(manifest.keyId)
  if (!publicKey) return false

  const digest = await sha256(bundle)
  if (!constantTimeEqual(digest, hexToBytes(manifest.sha256))) {
    return false
  }

  try {
    return await ed25519Verify(
      base64ToBytes(manifest.signature),
      digest,
      publicKey
    )
  } catch {
    return false
  }
}

该示例故意设置得较为严格。格式错误的 base64、未知的密钥标识符、摘要不匹配或签名失败都必须导致拒绝。下载过程中的超时不是应用之前部分文件的理由。请删除不完整的工件,保留最后已知良好的版本,并在有界策略下重试。

防止竞态条件和回滚错误

下载到临时路径。关闭并刷新文件,验证其完整内容,然后将其重命名到版本化的已验证存储中。激活步骤应仅引用该已验证路径。在 Capacitor 和 Electron 环境中,避免让异步下载完成事件独立于验证 Promise 触发激活。单个更新状态机应拥有诸如 downloading, verified, pending, active, rejected,以及 rolled_back.

恒定时间比较适用于敏感字节序列的比对,尤其是当攻击者能观察到重复的验证行为时。从运维角度看,更重要的是不要暴露一个捷径,仅因之前的尝试将版本标记为可用就接受该 bundle。可用性和真实性是两种独立的状态。

本指南中介绍的 Capacitor 更新的完整性检查 是保持哈希验证与激活逻辑分离的有用实现参考。请刻意测试失败分支,包括截断文件、格式错误的签名、未知密钥、过期的 manifest、重复版本以及激活过程中的进程终止。

关于自动签名验证的研究表明,在其他领域中阈值和参考数据为何重要。1994 年的一项在线研究测试了 22 个特征,选出了最佳的 10,并报告 99.5% 使用欧几里得距离方法正确分类了真实签名,同时拒绝了 86% 的伪造签名,根据 发表的统计方法研究。软件更新验证是确定性的,而非生物识别的,但这一教训依然相关:精确定义输入和决策边界。

大多数团队容易低估的密钥管理挑战

第一把签名密钥很容易。第二把密钥才是架构真正的考验。

团队可以在一天内生成密钥对,将公钥放入应用,并签署第一个 bundle。几个月后,一名工程师离职并带走了笔记本电脑的访问权限,CI 密钥泄露在构建日志中,或者签名任务需要从一台 runner 迁移到另一台。此时,“只需轮换密钥”可能意味着放弃那些尚未检查且无法识别替换密钥的设备。

一张对比图,展示简单的一次性密钥设置与复杂的生产环境现实之间的差异。

在首次发布之前,有三个问题值得在设计上重点关注:

  • 无死胡同的轮换: 在强制要求新密钥之前,先为后继密钥建立信任。通过签署的密钥过渡记录,现有的受信任密钥可以授权下一把密钥,同时应用在定义的迁移窗口内继续接受旧密钥。
  • 无假设的吊销: 应用内固定的密钥不会自动提供 OCSP 或 CRL 风格的吊销机制。客户端需要一个签署的拒绝列表、最低可接受的密钥版本,或一个在网络不可用时仍保持安全的服务器控制策略。
  • 受限的签名访问权限: CI runner 应向 vault 或 HSM 发起签名操作,而不是通过明文环境变量接收可复用的私钥。日志必须对命令输出脱敏,来自不可信分支的 pull request 不得接触生产签名凭据。

运维现状: 密钥轮换本质上是一个更新问题。如果更新机制无法安全地传递信任变更,也就无法在密钥泄露后干净地恢复。

密钥层级可以缩小影响范围。根授权机构可以授权发布密钥,而开发、预发和生产 channel 分别由不同密钥签名。阈值签名(Threshold signing)可以要求多个授权方共同参与敏感的生产发布,有助于防止单个被盗凭据生成有效更新。这些控制会增加流程开销和延迟,因此团队应根据更新的影响程度和 channel 的敏感性来应用。

对于移动端更新,“首次使用信任”(Trust on first use)机制尤其脆弱。如果第一个密钥与 bundle 通过同一个 channel 到达,控制该 channel 的攻击者就能把两者都替换掉。初始信任锚点必须随 app 二进制提供、放在受平台保护的配置中,或通过其他独立认证的路径到达。

对于 Capacitor 团队,Capgo 文档中的方案包括公钥固定(public-key pinning)和密钥轮换支持。这套 安全 OTA 更新的密钥管理指南 为规划这一生命周期提供实用参考,而不是把密钥生成当成一次性设置。

将验证集成到 CI/CD 和监控中

签名应属于发布事务的一部分。Pipeline 不应先发布 bundle,再通过单独的手动步骤附加签名。应构建不可变 artifact,计算其 digest,对确切的字节签名,用独立的验证步骤校验签名,然后将 bundle 和元数据作为一个发布单元一起发布。

实际 pipeline 会产出以下 artifact:

  • 不可变的 bundle: 客户端要下载的文件,而不是让 CDN 重新打包的目录。
  • Manifest: 版本、channel、平台、digest、密钥标识符和 rollout policy。
  • 分离签名: 对规范化 manifest 数据或精确定义的 digest 表示形式生成的签名。
  • 验证结果: 机器可读的检查,用于确认签名能通过该环境预期公钥的验证。

GitHub Actions 和 GitLab CI 虽然语法不同,但都能落实同样的模式。如果私有签名服务不可用、签名格式错误,或新下载的测试副本无法通过验证,签名 job 就应失败。部署 job 应依赖该结果,而不只是 build 成功。

不要对路径签名后又允许后续任务改动它。签名后锁定 artifact,发布前比对 digest,并确保发布的 manifest 能根据 release 记录复现。这能发现一个出奇常见的集成缺口:CI 任务签名的是一份压缩包,交付层提供的却是重新压缩或重新生成的文件。

可观测性应放在设备端

成功的签名任务只能证明 pipeline 生成了有效签名。它无法证明设备收到了预期的字节,也无法证明 App 使用了正确的 key。请记录验证结果,并附带 App 版本、更新版本、channel、平台、key identifier、结果分类和隐私安全的 release correlation ID。不要记录 bundle、token、私有元数据或用户内容。

实用的 dashboard 应区分:

  • 签名失败与下载失败,
  • digest 不匹配与 manifest 格式错误,
  • 未知 key 与策略拒绝,
  • 按 App 版本、地区、channel 及发布时长统计的失败情况。

digest 不匹配突然集中出现,可能意味着缓存损坏、交付路径被改动或 artifact 发布出错。未知 key 事件可能表明密钥轮转不完整,或有人尝试未经授权发布。验证遥测本身无法识别攻击者,但能为响应人员提供调查时间线和受影响群体。

本指南中介绍的 Capacitor OTA 更新的 CI/CD 安全指南 可以帮助团队在同一个工作流中设置签名、验证和部署关卡。关键的设计选择是所有权。安全团队不应该需要询问工程团队验证是否已执行。发布记录和客户端遥测数据应直接回答这个问题。

安全最佳实践与常见误区

对于任何可以执行 code 的更新路径,签名验证都应是硬性要求。更新器必须在提取、安装或激活之前进行验证,并且在签名、摘要、密钥或策略检查不可用时必须失败关闭。

将此作为即时审查清单:

  • 保护私钥: 将签名材料保存在 HSM 或托管保险库中。切勿将其提交到源代码控制、放入移动二进制文件或允许其进入 CI 日志。
  • 固定受信任的公钥: 将初始信任锚点存储在更新负载之外。如果支持多个密钥,请定义其用途和过渡规则。
  • 绑定完整的发布: 对标识确切 bundle、channel、平台和预期策略的规范元数据进行签名。
  • 拒绝所有验证错误: 超时、格式错误的签名、未知密钥或缺失的 manifest 并不是继续使用前一个未验证结果的邀请。
  • 测试妥协恢复: 在 staging 环境中演练密钥轮换、吊销密钥处理、回滚、中断下载以及陈旧设备场景。
  • 审查验证器变更: 要求对加密库、规范化、解析、回退行为及调试配置进行以安全为重点的 code 审查。
  • 保持调试行为隔离: 开发绕过机制必须无法通过默认标志或环境变量错误被打包进生产 build。

签名也无法证明新鲜度、接收方绑定、重放抵抗,或 payload 与提供商当前状态的兼容性。Webhook 安全指南明确区分了这一点,近期的漏洞报告也表明,畸形或空签名及规范化问题仍可能削弱狭窄的验证检查。在设计周围的授权规则时,请阅读 webhook 安全差距分析。

阈值选择在生物识别系统中也带来相关教训。一项使用 62 个参数特征 开启 来自 102 个人的 1,232 个签名 报告了依赖书写者的阈值为 2.8% 误拒率 以及 1.6% 误识率,而另一种方法报告了 2.68% 误拒率 以及 1.99% 误识率,正如 阈值选择研究所述。应用场景不同,但工作原理相同:验证者的决策策略与密码学原语同样重要。

对于需要把已签名的 Live Update bundle、设备端验证、发布控制、回滚保护和分发可观测性放在同一系统中的 Capacitor 和 Electron 团队,Capgo 可以作为实现方案之一。访问 Capgo 以评估其更新工作流是否满足你的密钥管理、CI/CD 和监控需求。

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。