Android:关于签名

Android的签名方案目前有三个版本:

  • v1(基于Jar签名)
  • v2(新的APK签名方案,Android 7.0引入)
  • v3(升级版的V2签名,Android 9.0引入)

首先我们新建一个Android工程,直接使用以下三种方案进行签名,生成相应的三个签名apk:

  • v1签名
  • v2签名
  • 同时拥有v1和v2签名

将签名后的APK文件,以解压形式打开,我们要关注的是META-INF文件夹里的东西

我们可以发现在“v1”和“同时拥有v1和v2”这两个方案里的META-INF文件夹下都有CERT.RSACERT.SFMANIFEST.MF这三个文件,而在“v2”方案下是没有这几个文件。

我们先从v1开始:

v1

由于v1方案使用的是基于Jar的签名,按照Oracle的 Signed JAR File 方案,会产生上面我们说到的相关的文件。也就是说它们是签名后的产物,而不是被签名的。

所以一个签名后的文件跟一个原始文件可以说是一样的,只是前者多了META—INF这个文件夹。

下面展示三个文件的相关描述:

  • MANIFEST.MF
    • 记录apk中每一个文件对应的摘要
    • 防止某个文件被篡改
  • CERT.SF
    • 记录 MANIFEST.MF 文件的摘要 和 MANIFEST.MF 中每个数据块的摘要
    • 可以在该文件的头部看到 SHA-256-Digest-Manifest:...之类的内容
    • 防止 MANIFEST.MF 被篡改
  • CERT.RSA
    • 包含了 CERT.SF 文件的签名 和 包含公钥的开发者证书
    • 可以校验 CERT.SF 是否被篡改

注:摘要方法在不同版本上有区别,早前是SHA-1,目前是SHA-256

流程

签名
  • 遍历jar文件下的所有JarEntry,对非文件夹非签名文件的文件,逐个进行SHA-256摘要,再进行Base64编码,然后一起写入MANIFEST.MF文件。
  • 对生成的MANIFEST.MF文件,先进行整个文件的SHA-256摘要和Base64编码,然后再遍历里面的每个Entry,进行SHA-256摘要和Base64编码,然后一起写进CERT.SF文件。在写进CERT.SF文件后,对该文件进行摘要,然后利用私钥签名得到签名值。
  • 将CERT.SF的签名和开发者证书写入 CERT.RSA,证书里有相应的公钥,可以用于验证签名。
校验
  • 取出CERT.RSA里的开发者证书,通过系统的根证书(CA证书)校验是否可信
  • 利用开发者证书中的公钥解密签名值得到摘要,利用同样的算法计算当前CERT.SF的摘要值,两者进行比较,判断是否有修改过
  • 使用CERT.SF检验MANIFEST.MF是否有修改过

存在的问题

由于META—INF是签名后的产物,即不在校验之列,所以该文件夹里可以用于修改,如增加自己的文件。

校验签名的时候需要对所有的条目进行一一校验,花费的时间会比较多。

v2

为了弥补v1存在的问题,故有了v2方案。

v2 是一种全文件签名方案,该方案能够发现对 APK 的受保护部分进行的所有更改,从而有助于加快验证速度并增强完整性保证。

使用 APK 签名方案 v2 进行签名时,会在 APK 文件中插入一个 APK 签名分块,该分块位于“ZIP 中央目录”部分之前并紧邻该部分。在“APK 签名分块”内,v2 签名和签名者身份信息会存储在 APK 签名方案 v2 分块中。

之所以使用这种插入独立分块的方式,也是为了保持与 v1 APK 格式向后兼容,在旧设备验证的时候,可以忽略掉这个独立块。

在这里插入图片描述

APK 签名分块

这个签名分块是为了支持 APK 签名方案 v2 而引入的一个新容器。

里面包含了多个“ID-值”对,v2签名的ID为0x7109871a。

格式
  • size of block,以字节数(不含此字段)计 (uint64)
  • 带 uint64 长度前缀的“ID-值”对序列:
    • ID (uint32)
    • value(可变长度:“ID-值”对的长度 - 4 个字节)
  • size of block,以字节数计 - 与第一个字段相同 (uint64)
  • magic“APK 签名分块 42”(16 个字节)

在解析 APK 时,首先要通过以下方法找到“ZIP 中央目录”的起始位置:在文件末尾找到“ZIP 中央目录结尾”记录,然后从该记录中读取“中央目录”的起始偏移量。通过 magic 值,可以快速确定“中央目录”前方可能是“APK 签名分块”。然后,通过 size of block 值,可以高效地找到该分块在文件中的起始位置。

APK 签名方案 v2 分块

APK 由一个或多个签名者/身份签名,每个签名者/身份均由一个签名密钥来表示。该信息会以“APK 签名方案 v2 分块”的形式存储。对于每个签名者,都会存储以下信息:

  • (签名算法、摘要、签名)元组。摘要会存储起来,以便将签名验证和 APK 内容完整性检查拆开进行。
  • 表示签名者身份的 X.509 证书链。
  • 采用键值对形式的其他属性。
格式
  • 带长度前缀的 signer(带长度前缀)序列:
    • 带长度前缀的 signed data:
      • 带长度前缀的 digests(带长度前缀)序列:
        • signature algorithm ID (uint32)
          (带长度前缀)digest - 请参阅受完整性保护的内容
        • 带长度前缀的 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 个字节的盐且尾部为 0xbc 的 RSASSA-PSS 算法
  • 0x0102 - 采用 SHA2-512 摘要、SHA2-512 MGF1、64 个字节的盐且尾部为 0xbc 的 RSASSA-PSS 算法
  • 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 算法

受完整性保护的内容

根据刚才这幅图可知
在这里插入图片描述

签了名的APK包含了四部分:

  1. ZIP 条目的内容(从偏移量 0 处开始一直到“APK 签名分块”的起始位置)
  2. APK 签名分块
  3. ZIP 中央目录
  4. ZIP 中央目录结尾

APK 签名方案 v2 负责保护第 1、3、4 部分的完整性,以及第 2 部分包含的“APK 签名方案 v2 分块”中的 signed data 分块的完整性。

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

第 1、3 和 4 部分的摘要采用以下计算方式,类似于两级 Merkle 树。每个部分都会被拆分成多个大小为 1MB(220 个字节)的连续块。每个部分的最后一个块可能会短一些。每个块的摘要均通过字节 0xa5 的串联、块的长度(采用小端字节序的 uint32 值,以字节数计)和块的内容进行计算。顶级摘要通过字节 0x5a 的串联、块数(采用小端字节序的 uint32 值)以及块的摘要的连接(按照块在 APK 中显示的顺序)进行计算。

摘要以分块方式计算,以便通过并行处理来加快计算速度。在一些加密算法里就是利用对数据进行分组,以并行方式加密提高加密速度。

在这里插入图片描述

v1和v2签名方式是可以同时存在,如果同时勾选了两个的话,此时生成的签名apk文件下,会有v1所产生的META-INF文件夹,并且CERT.SF里的头部会有X-Android-APK-Signed属性,该属性的值是一组以英文逗号分隔的 APK 签名方案 ID(v2 方案的 ID 为 2):X-Android-APK-Signed: 2

有了该属性,那么意味着这个apk是有v2签名的,按照规定流程是需要验证优先验证v2,如果发现没有v2签名,而签名时该属性的值却为2,那么该应用是不能被验证通过的。

这也是为了防止一些攻击者试图通过删除v2签名,让平台将其当做v1来验证的问题。

验证

在这里插入图片描述

过程
  1. 找到“APK 签名分块”并验证以下内容:
    1. “APK 签名分块”的两个大小字段包含相同的值。
    2. “ZIP 中央目录结尾”紧跟在“ZIP 中央目录”记录后面。
    3. “ZIP 中央目录结尾”之后没有任何数据。
  2. 找到“APK 签名分块”中的第一个“APK 签名方案 v2 分块”。如果 v2 分块存在,则继续执行第 3 步。否则,回退至使用 v1 方案验证 APK。
  3. 对“APK 签名方案 v2 分块”中的每个 signer 执行以下操作:
    1. 从 signatures 中选择安全系数最高的受支持 signature algorithm ID。安全系数排序取决于各个实现/平台版本。
    2. 使用 public key 并对照 signed data 验证 signatures 中对应的 signature。(现在可以安全地解析 signed data 了。)
    3. 验证 digests 和 signatures 中的签名算法 ID 列表(有序列表)是否相同。(这是为了防止删除/添加签名。)
    4. 使用签名算法所用的同一种摘要算法计算 APK 内容的摘要。
    5. 验证计算出的摘要是否与 digests 中对应的 digest 一致。
    6. 验证 certificates 中第一个 certificate 的 SubjectPublicKeyInfo 是否与 public key 相同。
  4. 如果找到了至少一个 signer,并且对于每个找到的 signer,第 3 步都取得了成功,APK 验证将会成功。

v3

APK 签名方案 v3

参考

应用签名

Signed JAR File

Android 端 V1/V2/V3 签名的原理

Android | 他山之石,可以攻玉!一篇文章看懂 v1/v2/v3 签名机制

SignApk.java

APK 签名方案 v2

APK 签名方案 v3

APK签名机制之——V2签名机制详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值