当前位置: 移动技术网 > 移动技术>移动开发>Android > 详解Android v1、v2、v3签名(小结)

详解Android v1、v2、v3签名(小结)

2020年03月09日  | 移动技术网移动技术  | 我要评论

android签名机制

什么是android签名

了解 https 通信的同学都知道,在消息通信时,必须至少解决两个问题:一是确保消息来源的真实性,二是确保消息不会被第三方篡改。

同理,在安装 apk 时,同样也需要确保 apk 来源的真实性,以及 apk 没有被第三方篡改。为了解决这一问题,android官方要求开发者对 apk 进行签名,而签名就是对apk进行加密的过程。要了解如何实现签名,需要了解两个基本概念:消息摘要、数字签名和数字证书。

消息摘要

消息摘要(message digest),又称数字摘要(digital digest)或数字指纹(finger print)。简单来说,消息摘要就是在消息数据上,执行一个单向的 hash 函数,生成一个固定长度的hash值,这个hash值即是消息摘要。

上面提到的的加密 hash 函数就是消息摘要算法。它有以下特征:

无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。
例如:应用 md5 算法摘要的消息有128个比特位,用 sha-1 算法摘要的消息最终有 160 比特位的输出,sha-1 的变体可以产生 192 比特位和 256 比特位的消息摘要。一般认为,摘要的最终输出越长,该摘要算法就越安全。

消息摘要看起来是「随机的」。
这些比特看上去是胡乱的杂凑在一起的。可以用大量的输入来检验其输出是否相同,一般,不同的输入会有不同的输出,而且输出的摘要消息可以通过随机性检验。但是,一个摘要并不是真正随机的,因为用相同的算法对相同的消息求两次摘要,其结果必然相同;而若是真正随机的,则无论如何都是无法重现的。因此消息摘要是「伪随机的」。

消息摘要函数是单向函数,即只能进行正向的信息摘要,而无法从摘要中恢复出任何的消息,甚至根本就找不到任何与原信息相关的信息。
当然,可以采用强力攻击的方法,即尝试每一个可能的信息,计算其摘要,看看是否与已有的摘要相同,如果这样做,最终肯定会恢复出摘要的消息。但实际上,要得到的信息可能是无穷个消息之一,所以这种强力攻击几乎是无效的。

好的摘要算法,没有人能从中找到「碰撞」。或者说,无法找到两条消息,使它们的摘要相同。
虽然「碰撞」是肯定存在的(由于长明文生成短摘要的 hash 必然会产生碰撞)。即对于给定的一个摘要,不可能找到一条信息使其摘要正好是给定的。

正是由于以上特点,消息摘要算法被广泛应用在「数字签名」领域,作为对明文的摘要算法。著名的消息摘要算法有 rsa 公司的 md5 算法和 sha-1 算法及其大量的变体。sha-256 是 sha-1 的升级版,现在 android 签名使用的默认算法都已经升级到 sha-256 了。

正是因为消息摘要具有这种特性,很适合来验证数据的完整性。比如:在网络传输过程中下载一个大文件 bigfile,我们会同时从网络下载 bigfile 和 bigfile.md5,bigfile.md5 保存 bigfile 的摘要,我们在本地生成 bigfile 的消息摘要和 bigfile.md5 比较,如果内容相同,则表示下载过程正确。

数字签名

数字签名的作用就是保证信息传输的完整性、发送者的身份认证、防止交易中的抵赖发生。数字签名技术是将摘要信息用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的摘要信息然后用hash函数对收到的原文产生一个摘要信息,与解密的摘要信息对比。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改,否则说明信息被修改过,因此数字签名能够验证信息的完整性。

如 rsa 作为数字签名方案使用时,它的使用流程如下:这种签名实际上就是用信源的私钥加密消息,加密后的消息即成了签体;而用对应的公钥进行验证,若公钥解密后的消息与原来的消息相同,则消息是完整的,否则消息不完整。

rsa正好和公钥密码用于消息保密是相反的过程。因为只有信源才拥有自己地私钥,别人无法重新加密源消息,所以即使有人截获且更改了源消息,也无法重新生成签体,因为只有用信源的私钥才能形成正确地签体。

同样信宿只要验证用信源的公钥解密的消息是否与明文消息相同,就可以知道消息是否被更改过,而且可以认证消息是否是确实来自意定的信源,还可以使信源不能否认曾经发送的消息。所以 这样可以完成数字签名的功能。

但这种方案过于单纯,它仅可以保证消息的完整性,而无法确保消息的保密性。而且这种方案要对所有的消息进行加密操作,这在消息的长度比较大时,效率是非常低的,主要原因在于公钥体制的加解密过程的低效性。所以这种方案一般不可取。

几乎所有的数字签名方案都要和快速高效的摘要算法(hash 函数)一起使用,当公钥算法与摘要算法结合起来使用时,便构成了一种有效地数字签名方案。

签名证书

通过数字签名技术,确实可以解决可靠通信的问题。一旦验签通过,接收者就能确信该消息是期望的发送者发送的,而发送者也不能否认曾经发送过该消息。

大家有没有注意到,前面讲的数字签名方法,有一个前提,就是消息的接收者必须事先得到正确的公钥。如果一开始公钥就被别人篡改了,那坏人就会被你当成好人,而真正的消息发送者给你发的消息会被你视作无效的。而且,很多时候根本就不具备事先沟通公钥的信息通道。

那么如何保证公钥的安全可信呢?这就要靠数字证书来解决了。

数字证书是一个经证书授权(certificate authentication)中心数字签名的包含公钥拥有者信息以及公钥的文件。数字证书的格式普遍采用的是 x.509 v3 国际标准,一个标准的 x.509 数字证书通常包含以下内容:

证书的发布机构(issuer):该证书是由哪个机构(ca 中心)颁发的。

证书的有效期(validity):证书的有效期,或者说使用期限。过了该日期,证书就失效了。

证书所有人的公钥(public-key):该证书所有人想要公布出去的公钥。

证书所有人的名称(subject):这个证书是发给谁的,或者说证书的所有者,一般是某个人或者某个公司名称、机构的名称、公司网站的网址等。

证书所使用的签名算法(signature algorithm):这个数字证书的数字签名所使用的加密算法,这样就可以使用证书发布机构的证书里面的公钥,根据这个算法对指纹进行解密。

证书发行者对证书的数字签名(thumbprint):数字证书的hash 值(指纹),用于保证数字证书的完整性,确保证书没有被修改过。

数字证书的原理就是在证书发布时,ca 机构会根据签名算法(signature algorithm)对整个证书计算其 hash 值(指纹)并和证书放在一起,使用者打开证书时,自己也根据签名算法计算一下证书的 hash 值(指纹),如果和证书中记录的指纹对的上,就说明证书没有被修改过。

可以看出,数字证书本身也用到了数字签名技术,只不过签名的内容是整个证书(里面包含了证书所有者的公钥以及其他一些内容)。与普通数字签名不同的是,数字证书的签名者不是随随便便一个普通机构,而是 ca 机构。

总结一下,数字签名和签名验证的大体流程如下图所示:

在这里插入图片描述

android打包流程

整个android的打包流程如下图所示:

在这里插入图片描述

编译打包步骤:

1,打包资源文件,生成r.java文件
打包资源的工具是aapt(the android asset packaing tool)(e:\documents\android\sdk\build-tools\25.0.0\aapt.exe)。

在这个过程中,项目中的androidmanifest.xml文件和布局文件xml都会编译,然后生成相应的r.java,另外androidmanifest.xml会被aapt编译成二进制。

存放在app的res目录下的资源,该类资源在app打包前大多会被编译,变成二进制文件,并会为每个该类文件赋予一个resource id。对于该类资源的访问,应用层代码则是通过resource id进行访问的。android应用在编译过程中aapt工具会对资源文件进行编译,并生成一个resource.arsc文件,resource.arsc文件相当于一个文件索引表,记录了很多跟资源相关的信息。

2. 处理aidl文件,生成相应的java文件
这一过程中使用到的工具是aidl(android interface definition language),即android接口描述语言(e:\documents\android\sdk\build-tools\25.0.0\aidl.exe)。

aidl工具解析接口定义文件然后生成相应的java代码接口供程序调用。如果在项目没有使用到aidl文件,则可以跳过这一步。

3. 编译项目源代码,生成class文件
项目中所有的java代码,包括r.java和.aidl文件,都会变java编译器(javac)编译成.class文件,生成的class文件位于工程中的bin/classes目录下。

4. 转换所有的class文件,生成classes.dex文件
dx工具生成可供android系统dalvik虚拟机执行的classes.dex文件,该工具位于(e:\documents\android\sdk\build-tools\25.0.0\dx.bat)。

任何第三方的libraries和.class文件都会被转换成.dex文件。dx工具的主要工作是将java字节码转成成dalvik字节码、压缩常量池、消除冗余信息等。

5. 打包生成apk文件
所有没有编译的资源,如images、assets目录下资源(该类文件是一些原始文件,app打包时并不会对其进行编译,而是直接打包到app中,对于这一类资源文件的访问,应用层代码需要通过文件名对其进行访问);编译过的资源和.dex文件都会被apkbuilder工具打包到最终的.apk文件中。

打包的工具apkbuilder位于 android-sdk/tools目录下。apkbuilder为一个脚本文件,实际调用的是(e:\documents\android\sdk\tools\lib)文件中的com.android.sdklib.build.apkbuildermain类。

6. 对apk文件进行签名
一旦apk文件生成,它必须被签名才能被安装在设备上。

在开发过程中,主要用到的就是两种签名的keystore。一种是用于调试的debug.keystore,它主要用于调试,在eclipse或者android studio中直接run以后跑在手机上的就是使用的debug.keystore。

另一种就是用于发布正式版本的keystore。

7. 对签名后的apk文件进行对齐处理
如果你发布的apk是正式版的话,就必须对apk进行对齐处理,用到的工具是zipalign(e:\documents\android\sdk\build-tools\25.0.0\zipalign.exe)

对齐的主要过程是将apk包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用就是减少运行时内存的使用。

从上图可以看到,签名发生在打包过程中的倒数第二步,而且签名针对的是已经存在的apk包,并不会影响我们写的代码。事实也确实是如此,android的签名,大致的签名原理就是对未签名的apk里面的所有文件计算hash,然后保存起来(manifest.mf),然后在对这些hash计算hash保存起来(cert.sf),然后在计算hash,然后再通过我们上面生成的keystore里面的私钥进行加密并保存(cert.rsa)。

android签名方案

android 系统从诞生到现在的1.0版本,一共经历了三代应用签名方案,分别是v1、v2和v3方案。

  • v1 方案:基于 jar 签名。
  • v2 方案:apk 签名方案 v2,在 android 7.0 引入。
  • v3 方案:apk 签名方案v3,在 android 9.0 引入。

其中,v1 到 v2 是颠覆性的,主要是为了解决 jar 签名方案的安全性问题,而到了 v3 方案,其实结构上并没有太大的调整,可以理解为 v2 签名方案的升级版。

v1 到 v2 方案的升级,对开发者影响是最大的,就是渠道签署的问题。v2的签名也是为了让不同渠道、市场的安装包有所区别,携带渠道的唯一标识,也即是我们俗称的渠道包。好在各大厂都开源了自己的签渠道方案,例如:walle(美团)、vasdolly(腾讯)都是非常优秀的方案。

v1签名

签名工具

android 应用的签名工具有两种:jarsigner 和 apksigner。它们的签名算法没什么区别,主要是签名使用的文件不同。它们的区别如下:

jarsigner:jdk 自带的签名工具,可以对 jar 进行签名。使用 keystore 文件进行签名。生成的签名文件默认使用 keystore 的别名命名。

apksigner:android sdk 提供的专门用于 android 应用的签名工具。使用 pk8、x509.pem 文件进行签名。其中 pk8 是私钥文件,x509.pem 是含有公钥的文件。生成的签名文件统一使用“cert”命名。

既然这两个工具都是给 apk 签名的,那么 keystore 文件和 pk8,x509.pem 他们之间是不是有什么联系呢?答案是肯定的,他们之间是可以转化的,这里就不再分析它们是如何进行转化,网上的例子很多。

还有一个需要注意的知识点,如果我们查看一个keystore 文件的内容,会发现里面包含有一个 md5 和 sha1 摘要,这个就是 keystore 文件中私钥的数据摘要,这个信息也是我们在申请很多开发平台账号时需要填入的信息。

签名过程

首先,我们任意选取一个签名后的 apk(sample-release.apk)进行解压,会得到如下图所示的文件。

在这里插入图片描述

可以发现,在 meta-inf 文件夹下有三个文件:manifest.mf、cert.sf、cert.rsa。它们就是签名过程中生成的文件,它们的作用如下。

manifest.mf

该文件中保存的其实就是逐一遍历 apk 中的所有条目,如果是目录就跳过,如果是一个文件,就用 sha1(或者 sha256)消息摘要算法提取出该文件的摘要然后进行 base64 编码后,作为「sha1-digest」属性的值写入到 manifest.mf 文件中的一个块中。该块有一个「name」属性, 其值就是该文件在 apk 包中的路径。

打开manifest.mf文件,文件内容如下图:

在这里插入图片描述

cert.sf

打开cert.sf文件,文件的内容如下:

在这里插入图片描述

  • sha1-digest-manifest-main-attributes:对 manifest.mf 头部的块做sha1(或者sha256)后再用 base64 编码。
  • sha1-digest-manifest:对整个 manifest.mf 文件做 sha1(或者 sha256)后再用 base64 编码。
  • sha1-digest:对 manifest.mf 的各个条目做 sha1(或者 sha256)后再用 base64 编码。

cert.rsa

把之前生成的 cert.sf 文件用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入 cert.rsa 中保存。需要注意的是,android apk 中的 cert.rsa 证书是自签名的,并不需要第三方权威机构发布或者认证的证书,用户可以在本地机器自行生成这个自签名证书。android 目前不对应用证书进行 ca 认证。

打开cert.rsa文件,内容如下图:

在这里插入图片描述

这里看到的都是二进制文件,因为 使用rsa 加密了,所以我们需要用 openssl 命令才能查看其内容,如下所示。

$ openssl pkcs7 -inform der -in /<文件存放路径>/sample-release_new/original/meta-inf/cert.rsa -text -noout -print_certs

执行上面的命令后,得到的内容如下图:

在这里插入图片描述

综上可以看出,一个完整的签名过程如下图:

在这里插入图片描述

签名校验

签名验证是发生在 apk 的安装过程中,一共分为三步:

  • 检查 apk 中包含的所有文件,对应的摘要值与 manifest.mf 文件中记录的值一致。
  • 使用证书文件(rsa 文件)检验签名文件(sf 文件)没有被修改过。
  • 使用签名文件(sf 文件)检验 mf 文件没有被修改过。

综上所述,一个完整的签名验证过程如下所示:

在这里插入图片描述

v2签名

apk 签名方案 v2 是一种全文件签名方案,该方案能够发现对 apk 的受保护部分进行的所有更改,从而有助于加快验证速度并增强完整性保证。通过前面的分析,可以发现 v1 签名有两个地方可以改进:

签名校验速度慢
校验过程中需要对apk中所有文件进行摘要计算,在 apk 资源很多、性能较差的机器上签名校验会花费较长时间,导致安装速度慢。

完整性保障不够
meta-inf 目录用来存放签名,自然此目录本身是不计入签名校验过程的,可以随意在这个目录中添加文件,比如一些快速批量打包方案就选择在这个目录中添加渠道文件。

为了解决这两个问题,在 android 7.0 版本 中引入了全新的 apk signature scheme v2。

v2的改进

由于在 v1 仅针对单个 zip 条目进行验证,因此,在 apk 签署后可进行许多修改 — 可以移动甚至重新压缩文件。事实上,编译过程中要用到的 zipalign 工具就是这么做的,它用于根据正确的字节限制调整 zip 条目,以改进运行时性能。而且我们也可以利用这个东西,在打包之后修改 meta-inf 目录下面的内容,或者修改 zip 的注释来实现多渠道的打包,在 v1 签名中都可以校验通过。

v2 签名将验证归档中的所有字节,而不是单个 zip 条目,因此,在签署后无法再运行 zipalign(必须在签名之前执行)。正因如此,现在,在编译过程中,google 将压缩、调整和签署合并成一步完成。

v2 签名

v2 签名会在原先 apk 块中增加了一个新的块(签名块),新的块存储了签名、摘要、签名算法、证书链和额外属性等信息,这个块有特定的格式。最终的签名apk其实就有四块:头文件区、v2签名块、中央目录、尾部。下图是v1签名和v2签名的组成。

在这里插入图片描述

整个签名块的格式如下:

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

在多个“id-值”对中,apk签名信息的 id 为 0x7109871a,包含的内容如下:
带长度前缀的 signer:

  • 带长度前缀的 signed data,包含digests序列,x.509 certificates 序列,additional attributes序列
  • 带长度前缀的 signatures(带长度前缀)序列
  • 带长度前缀的 public key(subjectpublickeyinfo,asn.1 der 形式)
  • value可能会包含多个 signer,因为android允许多个签名。

总结一下:一个签名块,可以包含多个id-value,apk的签名信息会存放在 id 为 0x7109871a的键值对里。他的内容可以包含多个签名者的签名信息,每个签名信息下包含signed data、signatures、public key,其中,signed data主要存放摘要序列、证书链、额外属性,signatures包含多个签名算法计算出来的签名值,public key表示签名者公钥,用于校验的时候验证签名的。

签名过程

首先,说一下 apk 摘要计算规则,对于每个摘要算法,计算结果如下:

  • 将 apk 中文件 zip 条目的内容、zip 中央目录、zip 中央目录结尾按照 1mb 大小分割成一些小块。
  • 计算每个小块的数据摘要,数据内容是 0xa5 + 块字节长度 + 块内容。
  • 计算整体的数据摘要,数据内容是 0x5a + 数据块的数量 + 每个数据块的摘要内容

总之,就是把 apk 按照 1m 大小分割,分别计算这些分段的摘要,最后把这些分段的摘要在进行计算得到最终的摘要也就是 apk 的摘要。然后将 apk 的摘要 + 数字证书 + 其他属性生成签名数据写入到 apk signing block 区块。

在这里插入图片描述

签名校验过程

接下来我们来看一下v2签名的校验过程,整体大概流程如下图所示。

在这里插入图片描述

其中, v2 签名机制是在 android 7.0 以及以上版本才支持的。因此对于 android 7.0 以及以上版本,在安装过程中,如果发现有 v2 签名块,则必须走 v2 签名机制,不能绕过。否则降级走 v1 签名机制。v1 和 v2 签名机制是可以同时存在的,其中对于 v1 和 v2 版本同时存在的时候,v1 版本的 meta_inf 的 .sf 文件属性当中有一个 x-android-apk-signed 属性。

x-android-apk-signed: 2

之前的渠道包生成方案是通过在 meta-inf 目录下添加空文件,用空文件的名称来作为渠道的唯一标识。但在新的应用签名方案下 meta-inf 已经被列入了保护区了,向 meta-inf 添加空文件的方案会对区块 1、3、4 都会有影响。对于这个问题,可以参考

v3签名

新版v3签名在v2的基础上,仍然采用检查整个压缩包的校验方式。不同的是在签名部分增可以添加新的证书(attr块)。在这个新块中,会记录我们之前的签名信息以及新的签名信息,以密钥转轮的方案,来做签名的替换和升级。这意味着,只要旧签名证书在手,我们就可以通过它在新的 apk 文件中,更改签名。

v3 签名新增的新块(attr)存储了所有的签名信息,由更小的 level 块,以链表的形式存储。

其中每个节点都包含用于为之前版本的应用签名的签名证书,最旧的签名证书对应根节点,系统会让每个节点中的证书为列表中下一个证书签名,从而为每个新密钥提供证据来证明它应该像旧密钥一样可信。

签名校验过程

android 的签名方案,无论怎么升级,都是要确保向下兼容。因此,在引入 v3 方案后,android 9.0 及更高版本中,可以根据 apk 签名方案,v3 -> v2 -> v1 依次尝试验证 apk。而较旧的平台会忽略 v3 签名并尝试 v2 签名,最后才去验证 v1 签名。
整个验证的过程,如下图:

在这里插入图片描述

需要注意的是,对于覆盖安装的情况,签名校验只支持升级,而不支持降级。也就是说设备上安装了一个使用 v1 签名的 apk,可以使用 v2 签名的 apk 进行覆盖安装,反之则不允许。

 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网