【Android】多渠道打包与签名机制

【Android】多渠道打包与签名机制

多渠道打包

我们在发布APP时,往往需要生成多个渠道包,以上传到不同的应用市场。

在这里插入图片描述

而每个渠道包中,都可以包含各自的渠道信息,当APP和后台交互或进行数据上报时,就可以带上各自的渠道信息,通过这种方式,我们就能统计到每个分发市场的下载数,用户数等关键信息。

多渠道打包的方式

ProductFlavor

作为Android开发人,我们肯定知道,Android默认为我们提供了这样的一个Gradle插件库:

classpath "com.android.tools.build:gradle:4.0.1"

然后我们会在module中的build.gradle中引入具体的插件,比如:

apply plugin: 'com.android.application'

而ProductFlavor,则是这个插件中的一个配置,或者说是这个插件提供的一个API。

那么怎么使用ProductFlavor进行多渠道打包呢?

其实很简单,我们只需要在module中的build.gradle中,写如下代码

apply plugin: 'com.android.application'
android {
    
    
    // ...
    flavorDimensions "default"
    file("channel.txt").readLines().each {
    
     channel ->
        // 基于channel,使用productFlavors.create方法,创建一个productFlavor(变种)
        productFlavors.create(channel, {
    
    
            dimension "default"
            // 填充AndroidManifest中的my_channel的值
            manifestPlaceholders = [my_channel: channel]
        })
    }
}
// ...

上面的代码是groovy代码,我来解释一下它的大致意思:

1、首先使用了相对路径,找到与build.gradle的同一级目录下的channel.txt文件

2、读取channel.txt文件中的每一行内容,并用channel变量表示

3、基于channel变量,使用productFlavors.create方法,创建一个productFlavor(也叫做变种)

4、对于每个变种的AndroidManifest,填充AndroidManifest中的my_channel的值

为了填充AndroidManifest中的my_channel的值,我们还需要在AndroidManifest.xml中写如下代码:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ycx.demo">

    <application
        // ...
        <meta-data
            android:name="CHANNEL_VALUE"
            android:value="${my_channel}" />
    </application>

</manifest>

这样,每次在构建app的时候,就会将AndroidManifest中的my_channel替换成对应变种的channel值。

channel.txt文件,我们可以这样写:

在这里插入图片描述

通过以上操作,我们app就具备生成多渠道包的能力了,我们点击AS的Build Variants按钮,就会看到如下变种信息:

在这里插入图片描述

可见,有6个变种可选。但是channel.txt文件明明只写了 3 个,为什么是 6 个变种呢?

那是因为默认情况下,我们一个App默认就会包含两个变种,分别是DebugRelease

所以我们使用productFlavors.create创建了 N 个变种,那么我们应用实际上将会有N * 2个变种(N个Debug变种 + N个Release变种)

使用渠道信息

既然已经可以打包多个渠道包了,那么我们下面就来使用各个渠道的渠道信息吧!

当我们选中xiaomiDebug这个变种进行打包,那么打包出来的APK中的AndroidManifest.xml会是这样的:

在这里插入图片描述

可见,它生成了一个渠道信息,即xiaomi。

既然在AndroidManifest.xml已经可以拿到渠道信息,那么在java代码中拿到渠道信息也就轻而易举了。

有了这个渠道信息,我们就可以将这个信息上报给业务后台,从而就能统计到每个应用市场的下载数等关键信息了。

如何优化多渠道打包

使用官方提供的ProductFlavor进行多渠道打包,其实是相当耗时的,因为对于每一个渠道包,都需要进行一次完整的APK打包过程,如果一次打包耗时5分钟,那么100个包将需要500分钟,这就有点夸张了。

那么有没有更好的多渠道打包方案呢?

那是肯定的。美团的Wall,腾信的VasDolly等插件都是不错的选择。

而这些插件的都不需要在打包时将所有渠道包都完整的打包一次,它们的大致原理是往APK中的签名文件夹中添加一个记录渠道信息的文件,从而将渠道信息注入到apk中(V1签名)。

而如果我们想要修改APK中的文件,我们就必须防止破坏APK的签名,一旦我们破坏了APK的签名,APK将无法安装。

所以优化多渠道打包耗时,就转变为如下问题:

如何在不破坏APK签名的前提下,往APK中注入渠道信息?

在讲多渠道的优化之前,我们就需要先对Android的签名机制有一个深入的理解。

Android的签名机制

Android为什么需要签名 ?

如果了解 HTTPS 的通信流程,应该知道,在消息通信时,必须解决两个问题:

  • 确保消息来源的真实性
  • 确保消息不会被第三方篡改

而在安装 APK 时,同样需要确保 APK 来源的真实性,以及 APK 没有被第三方篡改。

想要了解Android中是如何实现签名的,就需要了解几个概念:消息摘要、数字签名和数字证书。

消息摘要

消息摘要就是在消息数据上,执行一个单向的 Hash 函数,生成一个固定长度的 Hash 值,这个Hash值即是消息摘要。

消息摘要有如下的特性,无论输入的消息数据有多长,计算出来的消息摘要的长度总是固定的,另外,消息摘要函数是单向函数,也就是说只能进行正向的信息摘要,无法从摘要中恢复出任何的消息。一般情况下,不会出现两条消息,它们对应的消息摘要相同。

正是由于消息摘要的以上特性,使得消息摘要算法被广泛应用在数字签名领域

常见的消息摘要算法有 MD5 算法、 SHA-1SHA-256等。目前 Android 签名使用的默认算法就是 SHA-256 。

注意,消息摘要只能保证消息的完整性,并不能保证消息的不可篡改性

那么,怎么保证消息的不可篡改性呢?

这就是得说到另一个概念了 —— 数字签名

在讲数字签名之前,我们需要再简单的了解几个相关概念:公钥密码体制对称加密算法非对称加密算法

公钥密码体制

公钥密码体制分为三个部分,公钥、私钥、加密解密算法,它的加密解密过程如下:

  • 加密:通过加密算法公钥对明文进行加密,得到密文
  • 解密:通过解密算法私钥对密文进行解密,得到明文

也就是说,公钥加密的内容,只能由私钥进行解密,如果不知道私钥,密文是无法被解密的。

在实际的使用中,有需要的人会生成一对公钥和私钥,把公钥发布出去给别人使用,自己保留私钥。

对称加密算法

在对称加密算法中,加密和解密都是使用的同一个密钥。

因此对称加密算法要保证安全性的话,密钥要做好保密,只能让使用的人知道,不能对外公开。

非对称加密算法

在非对称加密算法中,加密使用的密钥和解密使用的密钥是不相同的。

数字签名

当使用私钥进行加密,使用公钥进行解密时,这个过程就称为签名

它和加密有什么区别呢?

因为公钥是公开的,所以任何持有公钥的人都能解密私钥加密过的密文,所以这个过程并不能保证消息的安全性,但是它却能保证消息来源的准确性和不可否认性

也就是说,如果使用公钥能正常解密某一个密文,那么就能证明这段密文一定是由私钥持有者发布的,而不是其他第三方发布的,并且私钥持有者不能否认他曾经发布过该消息。故此将该过程称为签名

常规的签名方案应该是摘要算法和数字签名和加密算法结合起来使用的,它的过程是这样的:

  • 用摘要算法对数据进行摘要,得到摘要值
  • 再把摘要值用信源的私钥加密

通过以上两步得到的消息就是原始信息的数字签名

综上所述,数字签名其实就是非对称加密技术 + 消息摘要技术的结合。

数字证书

通过数字签名技术,可以解决可靠通信的问题。

但是大家有没有注意到,前面讲的数字签名方法,有一个前提,就是消息的接收者必须事先得到正确的公钥

如果一开始公钥就被别篡改了,那么这个公钥解密出来的数据将不会是信源发送的数据。

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

数字证书是一个经证书授权中心数字签名的包含公钥拥有者信息以及公钥的文件,它包含以下内容:

  • 证书发布机构
  • 证书有效期
  • 证书所有人的公钥
  • 证书所有人的名称
  • 证书使用的签名算法
  • 证书发行者对证书的数字签名

可以看出,数字证书本身也用到了数字签名技术,只不过签名的内容是整个证书。

与普通数字签名不同的是,数字证书的签名者不是随随便便一个普通机构,而是 CA 机构。

一般来说,这些 CA 机构的根证书已经在设备出厂前预先安装到了你的设备上了。

所以,数字证书可以保证证书里的公钥确实是这个证书所有者的,或者证书可以用来确认对方的身份。

可见,数字证书主要是用来解决公钥的安全发放问题。

Android的签名机制

如果我们熟悉apk打包流程,我们肯定知道,在打包流程的倒数第二步,会对apk进行签名,Android中对apk进行签名工具有两种:jarsignerapksigner,它们的签名算法没什么区别,主要是签名使用的文件不同。

  • jarsigner:jdk自带的签名工具,可对 jar 进行签名,使用 keystore 文件进行签名,生成的签名文件默认使用 keystore 的别名命名
  • apksignersdk:专门用于Android应用的签名工具。生成的签名文件统一使用CERT命名。

签名校验的过程

Android为我们提供了三个签名版本,分别是V1、V2、V3,其中,V2签名仅支持Android 7.0及之后版本,V3签名支持Android 9.0及以上版本。

如果是在Android 9.0以上的系统安装一个apk,会先判断apk是否使用V3签名,如果没有使用V3签名,就判断是否使用了V2签名,如果还是没有使用V2签名,则确定apk是用了V1签名,那么这时就会使用V1签名的来校验,如果校验失败就拒绝安装,如果校验成功则安装。

这是Android官方提供的一个apk校验签名的流程图:

在这里插入图片描述

由此可见,一个apk是可以同时使用三个版本的签名的,如果同时使用三个版本的签名方式,则优先使用V3的签名进行校验。

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

而在Android studio中,我们可以选择打包V2和V1签名,但是目前AS并不支持V3签名,

在这里插入图片描述

如果我们想使用V3签名的话,可以自己通过命令行的方式来自行操作apksigner工具进行V3签名,具体就不举例了。

V1签名机制

如果我们在apk中使用了V1签名,我们会发现, 在apk中,会多出一个META-INF文件夹,而META-INF文件夹中会有三个子文件:

  • MANIFEST.MF
  • CERT.SF
  • CERT.RSA

这三个文件就是V1签名的产物,下面我对它们进行简单的介绍。

1、MANIFEST.MF:对apk中每个文件都使用SHA-256算法提取出该文件的的消息摘要然后进行base64编码,得到的编码值作为SHA1-Digest属性的值写入到 MANIFEST.MF 文件中的一个块中,且每一个块都有一个Name属性, 其值就是该文件在 APK 包中的路径,如下所示:

在这里插入图片描述

2、CERT.SF:对apk中每个文件以及MANIFEST.MF文件都使用SHA-256算法提取出每个文件的的消息摘要然后进行base64编码,可以理解为SF文件其实是用于保护MANIFEST.MF文件的。

3、CERT.RSA:这里会把 CERT.SF 文件,用私钥计算出签名,然后将签名以及包含公钥信息的数字证书一同写入 CERT.RSA 中保存。

其实说白了,就是MANIFEST.MF文件是用于保护apk中所有的文件的,而CERT.S文件则是用于保护MANIFEST.MF文件CERT.RSA用于保护 CERT.SF 文件,这样一环扣一环,来保证apk的安全性。

注意,

V1签名保护的是apk中已存在的文件不被修改

也就是说,如果把apk解压后,再添加一个自己的文件,然后再压缩成apk,那么这个apk仍然是可以安装成功的,因为我们新添加的那个文件本身就不在V1签名的保护范围内。

那么,如果我们添加的这个文件是一个渠道信息相关的文件,那是不是就做到了在不破坏APK签名的前提下,往APK中注入渠道信息了。

V2签名机制

其实,我们可以发现, V1 签名有两个地方可以改进:

  • 签名校验速度慢:校验过程中需要对apk中所有文件进行摘要计算,在 APK 资源很多、性能较差的机器上签名校验会花费较长时间,导致安装速度慢
  • 完整性保障不够:META-INF 目录用来存放签名,所以该目录本身是不计入签名校验过程的

为了解决这两个问题,在 Android 7.0 版本中,引入了V2签名。

V1签名保护的ZIP中已存在的文件,而V2签名保护的则是整个APK的字节数据

当apk中加入一个文件,那个这个apk对应的字节数据就会发生变化了,所以V2签名就不能像V1签名那样往apk中的META-INF文件夹注入一个新文件从而进行多渠道打包了。

那么在V2签名下,如何在不破坏APK签名的前提下,往APK中注入渠道信息呢?

首先我们要知道,其实apk就是一个zip格式的压缩包,因此,apk的数据格式就跟zip格式的压缩包一样了。

按照ZIP文件格式,插入一个签名分块区域,记录签名信息。

在签名前,ZIP的数据格式分三块,分别是蓝、绿、紫这三块,而签名后,在蓝和绿之间插入了一个签名分块: APK Signing Block,如下图所示:

在这里插入图片描述

那么,这个签名分块的数据格式是怎么的呢?

在这里插入图片描述

在整个签名块中,第一个size of block数据块,它占8个字节,记录的是除自己本身之外的整个签名块的长度。

再看蓝色部分,这里记录的是id-value的键值对,这里就是用来记录渠道信息的地方了。在里的键值对中,有一个id为0x7109871a,它所对应的value,就是V2签名的签名数据。

当所有的键值对记录完后,还有一个size of block数据块,它同样占8各个字节,它记录的也是除自己本身之外的整个签名块的长度。

最后,有个magic数据块,也叫做魔数,它占16个字节,它是用于标记文件格式的,

有的人会问:文件的后缀名不是也能标记文件格式吗?

其实,文件的后缀名的本质是为了让程序更方便的识别文件的格式,从而选择不同的处理方式来处理这个文件。

但是也会有人手动去修改一个文件的后缀名的情况,比如一个jpg格式的文件,手动将其后缀修改为.png,这种操作并不能从根源上修改文件的类型,换句话说,即使手动修改为.png文件,它本质上仍是一个.jpg文件。

而真正用来标记文件格式的,是魔数。

V2签名下如何优化多渠道打包?

根据官网的描述,V2签名保护的内容是蓝、绿、紫这三块区域,以及第二部分(签名分块)中的 id为0x7109871a的数据,而对于签名分块的其他数据,是不会保证的。

所以当我们往签名分块中插入一个id-value键值对,来记录渠道信息,是不会破坏apk的签名的。

按照这个思路,就能在V2签名下,在不破坏APK签名的前提下,往APK中注入渠道信息了。

注入渠道信息流程如下:

1、解析apk,判断是否使用了V2签名,如果是则定位到V2对应的签名分块区域

2、在签名分块中添加包含渠道信息的 id-value键值对

3、拷贝原apk,并修改签名分块数据,生成带有渠道信息的apk

V3签名机制

V3签名其实跟V2签名很相似,它仍然采用检查整个压缩包的校验方式,

不同的是在 V2 签名后插入的签名块中,又添加了一个Attr块,在这个新块中,会记录我们之前的签名信息以及新的签名信息,

以密钥转轮的方案,来做签名的替换和升级。这意味着,只要旧签名证书在手,我们就可以通过它在新的 APK 文件中,更改签名。

由此可知,V3签名下优化多渠道打包的方案和V2签名是一样的。

猜你喜欢

转载自blog.csdn.net/yang553566463/article/details/122658717
今日推荐