Android签名剖析

本篇介绍了Android中的apk的签名原理,包括老的签名方法,和新的Apk Signature Scheme V2介绍。使大家能对这一块有一个大致的了解,为快速打渠道包打下理论基础

概念介绍

网上已有多篇分析签名的类似文章,但是都有一个共同的问题,就是概念混乱,混乱的一塌糊涂。
在了解APK签名原理之前,首先澄清几个概念:

  • 消息摘要
  • MD5, SHA-0, SHA-1, SHA-256
  • 数字签名
  • 数字证书

消息摘要 -Message Digest

  • 简称摘要,请看英文翻译,是摘要,不是签名,网上几乎所有APK签名分析的文章都混淆了这两个概念。
  • 简单的说消息摘要就是在消息数据上,执行一个单向的Hash函数,生成一个固定长度的Hash值,这个Hash值即是消息摘要也称为数字指纹:
  • 消息摘要有以下特点:
    1. 通过摘要无法推算得出消息本身
    2. 如果修改了消息,那么摘要一定会变化(实际上,由于长明文生成短摘要的Hash必然会产生碰撞),所以这句话并不准确,我们可以改为:很难找到一种模式,修改了消息,而它的摘要不会变化。
  • 消息摘要的这种特性,很适合来验证数据的完整性,比如在网络传输过程中下载一个大文件BigFile,我们会同时从网络下载BigFile和BigFile.md5,BigFile.md5保存BigFile的摘要,我们在本地生成BigFile的消息摘要,和BigFile.md5比较,如果内容相同,则表示下载过程正确。
  • 注意,消息摘要只能保证消息的完整性,并不能保证消息的不可篡改性。

MD5, SHA-0, SHA-1, SHA-256

  • 这些都是摘要生成算法,和签名没有半毛钱关系。如果非要说他们和签名有关系,那就是签名是要借助于摘要技术。
  • 这里提前剧透下, SHA-256是SHA-1的升级版,现在Android签名使用的默认算法都已经升级到SHA-256了

数字签名 - Signature

  • 数字签名,百度百科对数字签名有非常清楚的介绍。我这里再罗嗦一下,不懂的去看百度百科。
  • 数字签名就是信息的发送者用自己的私钥对消息摘要加密产生一个字符串,加密算法确保别人无法伪造生成这段字符串,这段数字串也是对信息的发送者发送信息真实性的一个有效证明。
  • 数字签名是 非对称密钥加密技术 + 数字摘要技术 的结合。
  • 数字签名技术是将信息摘要用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的信息摘要,然后接收者用相同的Hash函数对收到的原文产生一个信息摘要,与解密的信息摘要做比对。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改;不同则说明信息被修改过,因此数字签名能保证信息的完整性。并且由于只有发送者才有加密摘要的私钥,所以我们可以确定信息一定是发送者发送的。

数字证书 - Certificate

  • 数字证书是一个经 证书授权中心 数字签名 的包含公开密钥拥有者信息以及公开密钥的文件。CERT.RSA包含了一个数字签名以及一个数字证书。
  • 需要注意的是Android APK中的CERT.RSA证书是自签名的,并不需要这个证书是第三方权威机构发布或者认证的,用户可以在本地机器自行生成这个自签名证书。

Apk老的签名方法

我们姑且叫它为v1吧,与后边出现的v2相对应。

签名三兄弟

  • 从我们解压出来的APK目录里面可以看到有一个META-INF目录,里面保存的三个文件就是我们签名过程中生成的东西,就是这仨货,我们叫它们三兄弟吧,把它们搞清楚你就精通签名啦:
    • MANIFEST.MF:所有文件的摘要信息,但不包括它们仨自己
    • CERT.SF:保存的是MANIFEST.MF的摘要值,以及MANIFEST.MF中每一个摘要项的摘要值
    • CERT.RSA:保存的是利用密钥对CERT.SF进行加密生成的数字签名和签名时用到的数字证书,数字证书保存的就是公钥和签名算法

签名工具

  • jarsigner
    • JDK中有keytool和jarsigner两个工具
    • keytool用于产生加密用的key
    • jarsigner用于对APK进行签名
    • 这两个文件都可以在JAVA的bin目录下找到
  • signapk
    • signapk是android提供的APK签名工具,使用方法如下:
      signapk [-w] publickey.x509[.pem] privatekey.pk8 input.jar output.jar  
      
      • publickey.x509.pem包含证书和证书链,证书和证书链中包含了公钥和加密算法
      • privatekey.pk8是私钥
      • input.jar是需要签名的jar
      • output.jar是签名结果
    • signapk的实现在android/build/tools/signapk/SignApk.Java中,可自行查看源码,后边会从代码的角度来分析一下,是如何生成签名三兄弟的。
  • IDE
    • Android Studio中可以创建keystore和使用已有的keystore
    • 在菜单栏–>build–>Generate Signed APK中即可进行操作
  • jarsign和signapk
    • 关于这两个工具开始的时候很容易混淆,他们俩到底有什么区别吗?
    • 其实很好理解,jarsign和signapk两个工具都可以进行Android中的签名,他们俩的签名算法没什么区别,它们的区别在于签名时使用的文件不一样
    • jarsign是Java本生自带的一个工具,它可以对jar进行签名的。它签名时使用的是keystore文件
    • 而signapk是后面专门为了Android应用程序apk进行签名的工具。它签名时使用的是pk8,x509.pem文件

签名过程

  1. 下面分析signapk的源码,来过一遍签名的过程。源码路径在android/build/tools/signapk/SignApk.Java
  2. 生成三兄弟的具体过程如下
    • 生成MANIFEST.MF
      • 遍历inputJar中的每一个文件,利用SHA1算法生成这些文件的信息摘要
      • 生成MAINFEST.MF文件,这个文件包含了input jar包内所有文件内容的摘要值。
      • 注意,不会生成META-INF目录的三个文件的摘要值(有的快速产生渠道包的方案即利用的这一点) MANIFEST.MF、CERT.SF、CERT.RSA
    • 生成CERT.SF CERT.SF中保存的是MANIFEST.MF的摘要值,以及MANIFEST.MF中每一个摘要项的摘要值。
    • 生成CERT.RSA
      • 这个文件保存了签名和公钥证书。签名的生成一定会有私钥参与,签名用到的信息摘要就是CERT.SF内容
      • 最终保存在CERT.RSA中的是CERT.SF的数字签名,签名使用privateKey生成的,签名算法会在publicKey中定义。同时还会把publicKey存放在CERT.RSA中,也就是说CERT.RSA包含了签名和签名用到的证书。并且要求这个证书是自签名的。
    • 最后就生成签名三兄弟啦!
  3. 验签
    • Android系统在验证的时候会根据数字证书提供的公钥和解密算法进行解密
    • 然后拿解密出来的摘要信息(hash序列)与手机本地文件生成的摘要信息进行比对,如果之前APK被篡改或所用签名与发布时不一致,就会导致解密出来的摘要信息与本地不一致,从而达到验证的目的。

APK Signature Scheme v2

在 Android 7.0 Nougat 中引入了全新的 APK Signature Scheme v2,以下以新的签名方案来称呼。所以,作为Android开发者,还得再学习一项新技能。

原因

为什么谷歌要做这个事情呢?第一点毋庸置疑,肯定是处于安全性的考虑,之前的校验方式开发者可以在打包之后对apk做很多处理,第二为了性能考虑,安装校验的时候不需要再解压缩校验,从而提升安装速度(说句玩笑话,个人感觉没什么鸟用,也不需要关系)

V2带来了什么变化?

  1. 由于在 v1 中仅验证未解压的文件内容,因此,在 APK 签署后可进行许多修改 - 可以移动甚至重新压缩文件。事实上,编译过程中要用到的 zipalign 工具就是这么做的,它用于根据正确的字节限制调整 ZIP 条目,以改进运行时性能。而且我们也可以利用这个东西,在打包之后修改META-INF目录下面的内容,或者修改Zip的注释来实现多渠道的打包,在v1签名中都可以校验通过
  2. v2 签名将验证归档中的所有字节,而不是单个 ZIP 条目,因此,在签署后无法再运行 zipalign。正因如此,现在,在编译过程中,Google将压缩、调整和签署合并成一步完成。
  3. 如有任何自定义任务篡改 APK 文件或对其进行后处理(无论以任何方式),那么v2 签名会有作废的风险,从而导致您的 APK 与 Android 7.0 及更高版本不兼容。

应对变化

  1. 如果我们选择手动签名(比如使用命令行)那么 Android SDK 中提供了一个名为 apksigner 的新工具,该工具可同时提供 v1 和 v2 APK 签署与验证。请注意,如果您使用 v2 签名,则在运行 apksigner之前,必须先运行 zipalign。
  2. 来自 JDK 的 jarsigner 工具与 Android v2 签名不兼容,因此,如果您要保留 v2 签名,您不能用它来重新签署 APK。
  3. 如果我们还想使用之前的打包方式,不做修改,那么Google也是为我们提供了配置方法的用来关闭v2签名:
    v1SigningEnabled false
    v2SigningEnabled false
    

新的签名方案就是一种扩展的zip格式

APK Signature Scheme v2其实是对zip格式进行了一个扩展,如图所示
对比zip格式

大家可以看到:

  • 新的签名方案会在ZIP文件格式的 Central Directory 区块所在文件位置的前面添加一个APK Signing Block区块,下面按照ZIP文件的格式来分析新应用签名方案签名后的APK包。
  • 整个APK(ZIP文件格式)会被分为以下四个区块:
    • Contents of ZIP entries(from offset 0 until the start of APK Signing Block)
    • APK Signing Block
    • ZIP Central Directory
    • ZIP End of Central Directory
    • 扩展后的zip格式
  • 其中:应用签名方案的签名信息会被保存在区块2(APK Signing Block)中,而区块1(Contents of ZIP entries)、区块3(ZIP Central Directory)、区块4(ZIP End of Central Directory)是受保护的,在签名后任何对区块1、3、4的修改都逃不过新的应用签名方案的检查。
  • 美团的walle为了找到一种快速打渠道包的方案,对V2的第二区块(APK Signing Block)进行了详细剖析,下面详细介绍一下第2区块(APK Signing Block)

可扩展的APK Signature Scheme v2 Block

通过上面的描述,可以看出因为APK包的区块1、3、4都是受保护的,任何修改在签名后对它们的修改,都会在安装过程中被签名校验检测失败,而区块2(APK Signing Block)是不受签名校验规则保护的。我们来看看对区块2格式的描述:

偏移 字节数 描述
@+0 8 这个Block的长度(本字段的长度不计算在内)
@+8 n 一组ID-value
@-24 8 这个Block的长度(和第一个字段一样值)
@-16 16 魔数 “APK Sig Block 42”
  • 区块2中APK Signing Block是由这几部分组成:2个用来标示这个区块长度的8字节 + 这个区块的魔数(APK Sig Block 42)+ 这个区块所承载的数据(ID-value)。
  • 我们重点来看一下这个ID-value,它由一个8字节的长度标示+4字节的ID+它的负载组成。V2的签名信息是以ID(0x7109871a)的ID-value来保存在这个区块中,不知大家有没有注意这是一组ID-value,也就是说它是可以有若干个这样的ID-value来组成。
  • Android应用在安装时新的应用签名方案是怎么进行校验的呢?笔者通过翻阅Android相关部分的源码,发现下面代码段是用来处理上面所说的ID-value的:

    public static ByteBuffer findApkSignatureSchemeV2Block(
            ByteBuffer apkSigningBlock,
            Result result) throws SignatureNotFoundException {
        checkByteOrderLittleEndian(apkSigningBlock);
        // FORMAT:
        // OFFSET       DATA TYPE  DESCRIPTION
        // * @+0  bytes uint64:    size in bytes (excluding this field)
        // * @+8  bytes pairs
        // * @-24 bytes uint64:    size in bytes (same as the one above)
        // * @-16 bytes uint128:   magic
        ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
    
        int entryCount = 0;
        while (pairs.hasRemaining()) {
            entryCount++;
            if (pairs.remaining() < 8) {
                throw new SignatureNotFoundException(
                        "Insufficient data to read size of APK Signing Block entry #" + entryCount);
            }
            long lenLong = pairs.getLong();
            if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
                throw new SignatureNotFoundException(
                        "APK Signing Block entry #" + entryCount
                                + " size out of range: " + lenLong);
            }
            int len = (int) lenLong;
            int nextEntryPos = pairs.position() + len;
            if (len > pairs.remaining()) {
                throw new SignatureNotFoundException(
                        "APK Signing Block entry #" + entryCount + " size out of range: " + len
                                + ", available: " + pairs.remaining());
            }
            int id = pairs.getInt();
            if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
                return getByteBuffer(pairs, len - 4);
            }
            result.addWarning(Issue.APK_SIG_BLOCK_UNKNOWN_ENTRY_ID, id);
            pairs.position(nextEntryPos);
        }
    
        throw new SignatureNotFoundException(
                "No APK Signature Scheme v2 block in APK Signing Block");
    }
    
  • 上述代码中关键的一个位置是 if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {return getByteBuffer(pairs, len - 4);},通过源代码可以看出Android是通过查找ID为 APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a 的ID-value,来获取APK Signature Scheme v2 Block,对这个区块中其他的ID-value选择了忽略。
  • 在APK Signature Scheme v2中没有看到对无法识别的ID,有相关处理的介绍。
  • 美团的方案就是利用这个漏洞来进行快速渠道包的:提供一个自定义的ID-value并写入该区域,从而为快速生成渠道包服务
  • 以上就是对新的应用签名方案进行的分析,并根据它所带来的文件存储格式上的变化,找到了可以利用的ID-value,然后基于这个ID-value来构建了美团新一代渠道包生成工具walle。

新的签名工具-apksigner

Android官方文档已经对apksigner的使用有比较详细的解释。下面说说实际的操作步骤:

ZipAlign

  • zip对齐,因为APK包的本质是一个zip压缩文档,经过边界对齐方式优化能使包内未压缩的数据有序的排列,从而减少应用程序运行时的内存消耗 ,通过空间换时间的方式提高执行效率(zipalign后的apk包体积增大了100KB左右)。
  • 打开cmd,把目录切换到SDK的build-tools目录下(例如 E:\SDK\build-tools\25.0.2\),执行:
    .\zipalign.exe -v -p 4 input.apk output.apk
    
  • zipalign命令选项不多,说明如下
    • -f : 输出文件覆盖源文件
    • -v : 详细的输出log
    • -p : outfile.zip should use the same page alignment for all shared object files within infile.zip
    • -c : 检查当前APK是否已经执行过Align优化。
    • 另外上面的数字4是代表按照4字节(32位)边界对齐。

apksigner

  1. 这个工具位于SDK目录的build-tools目录下。必须说明的是,v2签名方式时在Android7.0后才推出的,所以只有版本>25的SDK\build-tools\中才能找到apksigner.jar。
  2. 打开cmd,把目录切到SDK\build-tools\版本号\lib下(例如E:\SDK\build-tools\25.0.2\lib),执行:
    java -jar apksigner.jar sign
    
  3. 参数说明
    • –ks 你的jks路径 //jks签名证书路径
    • –ks-key-alias 你的alias //生成jks时指定的alias
    • –ks-pass pass:你的密码 //KeyStore密码
    • –key-pass pass:你的密码 //签署者的密码,即生成jks时指定alias对应的密码
    • –out output.apk //输出路径
    • input.apk //被签名的apk
  4. 示例
    java -jar apksigner.jar sign  --ks key.jks  --ks-key-alias releasekey  --ks-pass pass:pp123456  --key-pass pass:pp123456  --out output.apk   input.apk
    
  5. apksigner还支持另外的一些选项,详情点击这里。包括指定min-sdk版本、max-sdk版本、输出详细信息、检查apk是否已经签名等等。
  6. 例如检查apk是否已经签名:
    java -jar apksigner.jar verify -v my.apk
    或
    ./apksigner verify -v ~/Desktop/app-release.apk
    
  7. 输出的结果如下
    // 可能是这样 :
    Verifies
    Verified using v1 scheme (JAR signing): true
    Verified using v2 scheme (APK Signature Scheme v2): true
    Number of signers: 1
    // 或者可能是这样 :
    Verifies
    Verified using v1 scheme (JAR signing): true
    Verified using v2 scheme (APK Signature Scheme v2): false
    Number of signers: 1
    

总结:两步完成对apk包的v2签名

zipalign + apksigner,两步走完成对apk包的v2签名。且以上工具位于AndroidSDK目录的build-tools中。

总结

  • 至此,终于把Android中签名的方方面面总结了一下。 老的签名方案,新的签名方案,还有SHA-256
  • 下次再总结一下在V1和V2方式下的各种打渠道包的方案吧

参考文献

文章目录
  1. 1. 概念介绍
    1. 1.1. 消息摘要 -Message Digest
    2. 1.2. MD5, SHA-0, SHA-1, SHA-256
    3. 1.3. 数字签名 - Signature
    4. 1.4. 数字证书 - Certificate
  2. 2. Apk老的签名方法
    1. 2.1. 签名三兄弟
    2. 2.2. 签名工具
    3. 2.3. 签名过程
  3. 3. APK Signature Scheme v2
    1. 3.1. 原因
    2. 3.2. V2带来了什么变化?
    3. 3.3. 应对变化
    4. 3.4. 新的签名方案就是一种扩展的zip格式
    5. 3.5. 可扩展的APK Signature Scheme v2 Block
    6. 3.6. 新的签名工具-apksigner
      1. 3.6.1. ZipAlign
      2. 3.6.2. apksigner
      3. 3.6.3. 总结:两步完成对apk包的v2签名
  4. 4. 总结
  5. 5. 参考文献