APK 签名方案 v2

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

APK Signature Scheme v2 是一个完整的文件签名方案,它通过检测 APK 受保护部分的任何更改来提高验证速度并加强完整性保证

使用 APK 签名方案 v2 进行签名会将 APK 签名块插入到 APK 文件中,紧挨在 ZIP 中央目录部分之前。在 APK 签名块内,v2 签名和签名者身份信息存储在APK 签名方案 v2 块中。

签名前后的APK

图 1.签名前后的 APK

APK 签名方案 v2 是在 Android 7.0 (Nougat) 中引入的。要使 APK 可安装在 Android 6.0 (Marshmallow) 和更旧的设备上,应先使用JAR 签名对 APK 进行签名,然后再使用 v2 方案进行签名。

APK 签名块

为了保持与 v1 APK 格式的向后兼容性,v2 和更新的 APK 签名存储在 APK 签名块中,这是一个新容器,用于支持 APK 签名方案 v2。在 APK 文件中,APK 签名块位于 ZIP 中央目录之前,该目录位于文件末尾。

该块包含以某种方式包装的 ID-值对,以便更容易在 APK 中找到该块。 APK 的 v2 签名存储为 ID 值对,ID 为 0x7109871a。

格式

APK Signing Block 的格式如下(所有数字字段都是 little-endian):

  • size of block以字节为单位)(不包括此字段)(uint64)
  • uint64-length-prefixed ID-value 对的序列:
    • ID (uint32)
    • value (可变长度:对的长度 - 4 个字节)
  • size of block以字节为单位)——与第一个字段(uint64)相同
  • magic “APK Sig Block 42”(16 字节)

APK 通过首先找到 ZIP Central Directory 的开头来解析(通过在文件末尾找到 Central Directory 的 ZIP End of Central Directory 记录,然后从记录中读取 Central Directory 的起始偏移量)。 magic值提供了一种快速方法来确定中央目录之前的内容可能是 APK 签名块。然后size of block有效地指向文件中块的开始。

解释块时应忽略具有未知 ID 的 ID-值对。

APK 签名方案 v2 块

APK 由一个或多个签名者/身份签名,每个签名者/身份都由一个签名密钥表示。此信息存储为 APK 签名方案 v2 块。对于每个签名者,将存储以下信息:

  • (签名算法,摘要,签名)元组。存储摘要以将签名验证与 APK 内容的完整性检查分离。
  • 代表签名者身份的 X.509 证书链。
  • 作为键值对的附加属性。

对于每个签名者,APK 会使用提供的列表中支持的签名进行验证。具有未知签名算法的签名将被忽略。当遇到多个支持的签名时,由每个实现选择使用哪个签名。这使得将来能够以向后兼容的方式引入更强大的签名方法。建议的方法是验证最强的签名。

格式

APK 签名方案 v2 块存储在 ID 0x7109871a下的 APK 签名块中。

APK Signature Scheme v2 Block的格式如下(所有数值均为little-endian,所有长度前缀字段使用uint32表示长度):

  • 长度前缀signer的长度前缀序列:
    • 以长度为前缀的有signed data
      • 长度前缀digests的长度前缀序列:
      • X.509 certificates的长度前缀序列:
        • 以长度为前缀的 X.509 certificate (ASN.1 DER 形式)
      • 以长度为前缀的additional attributes的长度前缀序列:
        • ID (uint32)
        • value (可变长度:附加属性的长度 - 4 个字节)
    • 长度前缀signatures的长度前缀序列:
      • signature algorithm ID (uint32)
      • 签名signed data上的长度前缀signature
    • 长度前缀public key (SubjectPublicKeyInfo,ASN.1 DER 形式)

签名算法 ID

  • 0x0101—带有 SHA2-256 摘要、SHA2-256 MGF1、32 字节盐的 RSASSA-PSS,预告片:0xbc
  • 0x0102—带有 SHA2-512 摘要、SHA2-512 MGF1、64 字节盐的 RSASSA-PSS,预告片:0xbc
  • 0x0103—带有 SHA2-256 摘要的 RSASSA-PKCS1-v1_5。这适用于需要确定性签名的构建系统。
  • 0x0104—带有 SHA2-512 摘要的 RSASSA-PKCS1-v1_5。这适用于需要确定性签名的构建系统。
  • 0x0201 - 带有 SHA2-256 摘要的 ECDSA
  • 0x0202—带有 SHA2-512 摘要的 ECDSA
  • 0x0301 - 带有 SHA2-256 摘要的 DSA

Android平台支持以上所有签名算法。签名工具可能支持算法的一个子集。

支持的键大小和 EC 曲线:

  • RSA:1024、2048、4096、8192、16384
  • EC:NIST P-256、P-384、P-521
  • 动态搜索广告:1024、2048、3072

完整性保护的内容

为了保护 APK 内容,一个 APK 包含四个部分:

  1. ZIP 条目的内容(从偏移量 0 到 APK 签名块的开始)
  2. APK 签名块
  3. ZIP 中央目录
  4. 中央目录的 ZIP 结尾

签名后的 APK 部分

图 2.签名后的 APK 部分

APK 签名方案 v2 保护第 1、3、4 节以及第 2 节中包含的 APK 签名方案 v2 块的signed data块的完整性。

第 1、3 和 4 部分的完整性受到其内容的一个或多个摘要的保护,这些摘要存储在signed data块中,而签名数据块又受一个或多个签名保护。

第 1、3 和 4 部分的摘要计算如下,类似于两级Merkle 树。每个部分被分成连续的 1 MB(2 20字节)块。每个部分中的最后一个块可能会更短。每个块的摘要是通过字节0xa5的连接、块的字节长度 (little-endian uint32) 和块的内容来计算的。顶级摘要是根据字节0x5a的连接、块的数量(小端 uint32)以及块的摘要的连接(按照块在 APK 中出现的顺序)计算得出的。摘要以分块方式计算,以通过并行化加速计算。

APK摘要

图 3. APK 摘要

第 4 节(中央目录的 ZIP 结尾)的保护因包含 ZIP 中央目录偏移的部分而变得复杂。当 APK 签名块的大小发生变化时,偏移量也会发生变化,例如,添加新签名时。因此,在计算中央目录 ZIP 端的摘要时,必须将包含 ZIP 中央目录偏移量的字段视为包含 APK 签名块的偏移量。

回滚保护

攻击者可能会尝试在支持验证 v2 签名 APK 的 Android 平台上将 v2 签名 APK 验证为 v1 签名 APK。为了缓解这种攻击,v2 签名的 APK 和 v1 签名的 APK 必须在其 META-INF/*.SF 文件的主要部分包含 X-Android-APK-Signed 属性。该属性的值是一组以逗号分隔的 APK 签名方案 ID(此方案的 ID 为 2)。在验证 v1 签名时,APK 验证者需要拒绝没有签名的 APK,该 APK 签名方案是验证者从该集合中首选的 APK 签名方案(例如,v2 方案)。这种保护依赖于内容 META-INF/*.SF 文件受 v1 签名保护的事实。请参阅JAR 签名的 APK 验证部分。

攻击者可能会尝试从 APK 签名方案 v2 块中剥离更强的签名。为了减轻这种攻击,APK 被签名的签名算法 ID 列表存储在受每个签名保护的signed data块中。

确认

在 Android 7.0 及更高版本中,可以根据 APK 签名方案 v2+ 或 JAR 签名(v1 方案)对 APK 进行验证。旧平台忽略 v2 签名,仅验证 v1 签名。

APK签名验证流程

图 4. APK 签名验证流程(红色为新步骤)

APK 签名方案 v2 验证

  1. 找到 APK 签名块并验证:
    1. APK 签名块的两个大小字段包含相同的值。
    2. ZIP Central Directory 后紧跟 ZIP End of Central Directory 记录。
    3. 中央目录的 ZIP 结尾后面没有更多数据。
  2. 找到 APK 签名块内的第一个 APK 签名方案 v2 块。如果存在 v2 块,请继续执行步骤 3。否则,回退到使用 v1 方案验证 APK。
  3. 对于 APK 签名方案 v2 块中的每个signer
    1. signatures中选择支持的最强signature algorithm ID 。强度排序取决于每个实现/平台版本。
    2. 使用public key验证signatures signed data中的相应signature 。 (现在解析signed data是安全的。)
    3. 验证digestssignatures中签名算法 ID 的有序列表是否相同。 (这是为了防止签名剥离/添加。)
    4. 使用与签名算法使用的摘要算法相同的摘要算法计算 APK 内容的摘要。
    5. 验证计算出的摘要与来自digests的相应digest相同。
    6. 验证第一个certificatecertificates是否与public key相同。
  4. 如果至少找到一个signer并且步骤 3 对每个找到的signer都成功,则验证成功。

注意:如果第 3 步或第 4 步发生故障,则不得使用 v1 方案验证 APK。

JAR 签名的 APK 验证(v1 方案)

JAR 签名的 APK 是一个标准签名的 JAR ,它必须完全包含 META-INF/MANIFEST.MF 中列出的条目,并且所有条目都必须由同一组签名者签名。其完整性验证如下:

  1. 每个签名者由 META-INF/<signer>.SF 和 META-INF/<signer>.(RSA|DSA|EC) JAR 条目表示。
  2. <signer>.(RSA|DSA|EC) 是具有 SignedData 结构的 PKCS #7 CMS ContentInfo,其签名通过 <signer>.SF 文件进行验证。
  3. <signer>.SF 文件包含 META-INF/MANIFEST.MF 的整个文件摘要和 META-INF/MANIFEST.MF 的每个部分的摘要。验证了 MANIFEST.MF 的整个文件摘要。如果失败,则改为验证每个 MANIFEST.MF 部分的摘要。
  4. META-INF/MANIFEST.MF 为每个完整性保护的 JAR 条目包含一个相应命名的部分,其中包含条目的未压缩内容的摘要。所有这些摘要都经过验证。
  5. 如果 APK 包含未在 MANIFEST.MF 中列出且不属于 JAR 签名的 JAR 条目,则 APK 验证失败。

因此,保护​​链是 <signer>.(RSA|DSA|EC) -> <signer>.SF -> MANIFEST.MF -> 每个受完整性保护的 JAR 条目的内容。