APK 签名方案 v3

Android 9 支持 APK 密钥轮替,让应用可以在 APK 更新过程中更改其签名密钥。为了使轮替切实可行,APK 必须指明新旧签名密钥之间的信任级别。为了支持密钥轮替,我们将 APK 签名方案 从 v2 更新到 v3,以允许使用新旧密钥。V3 向 APK 签名块添加了有关受支持 SDK 版本和轮替证明结构的信息。

APK 签名块

为了保持与 v1 APK 格式的向后兼容性,v2 和 v3 APK 签名存储在 APK 签名块内,该签名块位于 ZIP 中央目录之前。

v3 APK 签名块格式与 v2 相同。APK 的 v3 签名存储为 ID 值对,ID 为 0xf05368c0。

APK 签名方案 v3 块

v3 方案的设计与 v2 方案非常相似。它具有相同的通用格式,并支持相同的 签名算法 ID、密钥大小和 EC 曲线。

但是,v3 方案添加了有关受支持 SDK 版本和轮替证明结构的信息。

格式

APK 签名方案 v3 块存储在 ID 为 0xf05368c0 的 APK 签名块内。

APK 签名方案 v3 块的格式与 v2 的格式相同

  • 长度前缀的长度前缀 signer 序列
    • 长度前缀的 signed data
      • 长度前缀的长度前缀 digests 序列
        • signature algorithm ID(4 个字节)
        • digest(长度前缀)
      • X.509 certificates 的长度前缀序列
        • 长度前缀的 X.509 certificate (ASN.1 DER 格式)
      • minSDK (uint32) - 如果平台版本低于此数字,则应忽略此签名者。
      • maxSDK (uint32) - 如果平台版本高于此数字,则应忽略此签名者。
      • 长度前缀的长度前缀 additional attributes 序列
        • ID (uint32)
        • value(可变长度:附加属性的长度 - 4 个字节)
        • ID - 0x3ba06f8c
        • value - 轮替证明结构
    • minSDK (uint32) - 签名数据部分中 minSDK 值的副本 - 用于在当前平台不在范围内时跳过此签名的验证。必须与签名数据值匹配。
    • maxSDK (uint32) - 签名数据部分中 maxSDK 值的副本 - 用于在当前平台不在范围内时跳过此签名的验证。必须与签名数据值匹配。
    • 长度前缀的长度前缀 signatures 序列
      • signature algorithm ID (uint32)
      • 通过 signed data 的长度前缀 signature
    • 长度前缀的 public key (SubjectPublicKeyInfo, ASN.1 DER 格式)

轮替证明和自信任旧证书结构

轮替证明结构允许应用轮替其签名证书,而不会被与之通信的其他应用阻止。为了实现这一点,应用签名包含两个新的数据

  • 第三方断言:无论应用的先前版本在何处受信任,应用的签名证书都可以在这些位置受信任
  • 应用自身的旧签名证书,应用本身仍然信任这些证书

签名数据部分中的轮替证明属性由单链表组成,每个节点包含一个用于签署应用先前版本的签名证书。此属性旨在包含概念性的轮替证明和自信任旧证书数据结构。列表按版本排序,最旧的签名证书对应于根节点。轮替证明数据结构的构建方式是让每个节点中的证书签署列表中的下一个证书,从而使每个新密钥都具有证据,证明它应与旧密钥一样受信任。

自信任旧证书数据结构是通过向每个节点添加标志来指示其在集合中的成员资格和属性来构建的。例如,可以存在一个标志,指示给定节点处的签名证书对于获取 Android 签名权限是否受信任。此标志允许由旧证书签名的其他应用仍然被授予由使用新签名证书签名的应用定义的签名权限。由于整个轮替证明属性都位于 v3 signer 字段的签名数据部分中,因此它受到用于签署包含 APK 的密钥的保护。

此格式排除了多个签名密钥以及不同祖先签名证书到一个的收敛(到一个公共接收器的多个起始节点)。

格式

轮替证明存储在 ID 为 0x3ba06f8c 的 APK 签名方案 v3 块内。其格式为

  • 长度前缀的长度前缀 levels 序列
    • 长度前缀的 signed data(由先前的证书签名 - 如果存在)
      • 长度前缀的 X.509 certificate (ASN.1 DER 格式)
      • signature algorithm ID (uint32) - 先前级别中证书使用的算法
    • flags (uint32) - 指示此证书是否应位于自信任旧证书结构中以及用于哪些操作的标志。
    • signature algorithm ID (uint32) - 必须与下一级别签名数据部分中的算法 ID 匹配。
    • 通过上述 signed data 的长度前缀 signature

多个证书

不支持多个签名者,并且 Google Play 不会发布使用多个证书签名的应用。

验证

在 Android 9 及更高版本中,可以根据 APK 签名方案 v3、v2 方案或 v1 方案验证 APK。旧平台会忽略 v3 签名,并尝试验证 v2 签名,然后验证 v1 签名。

APK signature verification process

图 1. APK 签名验证流程

APK 签名方案 v3 验证

  1. 找到 APK 签名块并验证以下各项:
    1. APK 签名块的两个大小字段包含相同的值。
    2. ZIP 中央目录紧跟 ZIP 中央目录结尾记录。
    3. ZIP 中央目录结尾记录之后没有更多数据。
  2. 在 APK 签名块内找到第一个 APK 签名方案 v3 块。如果 v3 块存在,请继续执行步骤 3。否则,回退到使用 v2 方案验证 APK。
  3. 对于 APK 签名方案 v3 块中,min 和 max SDK 版本在当前平台范围内的每个 signer
    1. signatures 中选择最强大的受支持 signature algorithm ID。强度排序取决于每个实现/平台版本。
    2. 使用 public key 验证 signatures 中相应的 signature 是否与 signed data 匹配。(现在可以安全地解析 signed data。)
    3. 验证签名数据中的 min 和 max SDK 版本是否与为 signer 指定的版本匹配。
    4. 验证 digestssignatures 中签名算法 ID 的有序列表是否相同。(这是为了防止签名剥离/添加。)
    5. 使用与签名算法使用的摘要算法相同的摘要算法计算 APK 内容的摘要
    6. 验证计算出的摘要是否与 digests 中的相应 digest 相同。
    7. 验证 certificates 的第一个 certificate 的 SubjectPublicKeyInfo 是否与 public key 相同。
    8. 如果 signer 存在轮替证明属性,请验证该结构是否有效,并且此 signer 是否是列表中的最后一个证书。
  4. 如果当前平台范围内正好找到一个 signer 且步骤 3 对该 signer 成功,则验证成功。

验证

要测试您的设备是否正确支持 v3,请在 cts/hostsidetests/appsecurity/src/android/appsecurity/cts/ 中运行 PkgInstallSignatureVerificationTest.java CTS 测试。