详解Androidv1、v2、v3签名(⼩结)
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.)。
在这个过程中,项⽬中的l⽂件和布局⽂件XML都会编译,然后⽣成相应的R.java,另外l会被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.)。
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⽂件进⾏签名
android获取真正的签名⼀旦APK⽂件⽣成,它必须被签名才能被安装在设备上。
在开发过程中,主要⽤到的就是两种签名的keystore。⼀种是⽤于调试的debug.keystore,它主要⽤于调试,在Eclipse或者Android Studio中直接run以后跑在⼿机上的就是使⽤的debug.keystore。
另⼀种就是⽤于发布正式版本的keystore。
7. 对签名后的APK⽂件进⾏对齐处理
如果你发布的apk是正式版的话,就必须对APK进⾏对齐处理,⽤到的⼯具是zipalign(E:\Documents\Android\sdk\build-tools\25.0.)
对齐的主要过程是将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⽂件,内容如下图: