【Android】多渠道打包与签名机制
【Android】多渠道打包與簽名機(jī)制
多渠道打包
我們?cè)诎l(fā)布APP時(shí),往往需要生成多個(gè)渠道包,以上傳到不同的應(yīng)用市場(chǎng)。
而每個(gè)渠道包中,都可以包含各自的渠道信息,當(dāng)APP和后臺(tái)交互或進(jìn)行數(shù)據(jù)上報(bào)時(shí),就可以帶上各自的渠道信息,通過這種方式,我們就能統(tǒng)計(jì)到每個(gè)分發(fā)市場(chǎng)的下載數(shù),用戶數(shù)等關(guān)鍵信息。
多渠道打包的方式
ProductFlavor
作為Android開發(fā)人,我們肯定知道,Android默認(rèn)為我們提供了這樣的一個(gè)Gradle插件庫(kù):
classpath "com.android.tools.build:gradle:4.0.1"然后我們會(huì)在module中的build.gradle中引入具體的插件,比如:
apply plugin: 'com.android.application'而ProductFlavor,則是這個(gè)插件中的一個(gè)配置,或者說是這個(gè)插件提供的一個(gè)API。
那么怎么使用ProductFlavor進(jìn)行多渠道打包呢?
其實(shí)很簡(jiǎn)單,我們只需要在module中的build.gradle中,寫如下代碼
apply plugin: 'com.android.application' android {// ...flavorDimensions "default"file("channel.txt").readLines().each { channel ->// 基于channel,使用productFlavors.create方法,創(chuàng)建一個(gè)productFlavor(變種)productFlavors.create(channel, {dimension "default"// 填充AndroidManifest中的my_channel的值manifestPlaceholders = [my_channel: channel]})} } // ...上面的代碼是groovy代碼,我來解釋一下它的大致意思:
1、首先使用了相對(duì)路徑,找到與build.gradle的同一級(jí)目錄下的channel.txt文件
2、讀取channel.txt文件中的每一行內(nèi)容,并用channel變量表示
3、基于channel變量,使用productFlavors.create方法,創(chuàng)建一個(gè)productFlavor(也叫做變種)
4、對(duì)于每個(gè)變種的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-dataandroid:name="CHANNEL_VALUE"android:value="${my_channel}" /></application></manifest>這樣,每次在構(gòu)建app的時(shí)候,就會(huì)將AndroidManifest中的my_channel替換成對(duì)應(yīng)變種的channel值。
而channel.txt文件,我們可以這樣寫:
通過以上操作,我們app就具備生成多渠道包的能力了,我們點(diǎn)擊AS的Build Variants按鈕,就會(huì)看到如下變種信息:
可見,有6個(gè)變種可選。但是channel.txt文件明明只寫了 3 個(gè),為什么是 6 個(gè)變種呢?
那是因?yàn)槟J(rèn)情況下,我們一個(gè)App默認(rèn)就會(huì)包含兩個(gè)變種,分別是Debug和Release,
所以我們使用productFlavors.create創(chuàng)建了 N 個(gè)變種,那么我們應(yīng)用實(shí)際上將會(huì)有N * 2個(gè)變種(N個(gè)Debug變種 + N個(gè)Release變種)
使用渠道信息
既然已經(jīng)可以打包多個(gè)渠道包了,那么我們下面就來使用各個(gè)渠道的渠道信息吧!
當(dāng)我們選中xiaomiDebug這個(gè)變種進(jìn)行打包,那么打包出來的APK中的AndroidManifest.xml會(huì)是這樣的:
可見,它生成了一個(gè)渠道信息,即xiaomi。
既然在AndroidManifest.xml已經(jīng)可以拿到渠道信息,那么在java代碼中拿到渠道信息也就輕而易舉了。
有了這個(gè)渠道信息,我們就可以將這個(gè)信息上報(bào)給業(yè)務(wù)后臺(tái),從而就能統(tǒng)計(jì)到每個(gè)應(yīng)用市場(chǎng)的下載數(shù)等關(guān)鍵信息了。
如何優(yōu)化多渠道打包
使用官方提供的ProductFlavor進(jìn)行多渠道打包,其實(shí)是相當(dāng)耗時(shí)的,因?yàn)閷?duì)于每一個(gè)渠道包,都需要進(jìn)行一次完整的APK打包過程,如果一次打包耗時(shí)5分鐘,那么100個(gè)包將需要500分鐘,這就有點(diǎn)夸張了。
那么有沒有更好的多渠道打包方案呢?
那是肯定的。美團(tuán)的Wall,騰信的VasDolly等插件都是不錯(cuò)的選擇。
而這些插件的都不需要在打包時(shí)將所有渠道包都完整的打包一次,它們的大致原理是往APK中的簽名文件夾中添加一個(gè)記錄渠道信息的文件,從而將渠道信息注入到apk中(V1簽名)。
而如果我們想要修改APK中的文件,我們就必須防止破壞APK的簽名,一旦我們破壞了APK的簽名,APK將無法安裝。
所以優(yōu)化多渠道打包耗時(shí),就轉(zhuǎn)變?yōu)槿缦聠栴}:
如何在不破壞APK簽名的前提下,往APK中注入渠道信息?
在講多渠道的優(yōu)化之前,我們就需要先對(duì)Android的簽名機(jī)制有一個(gè)深入的理解。
Android的簽名機(jī)制
Android為什么需要簽名 ?
如果了解 HTTPS 的通信流程,應(yīng)該知道,在消息通信時(shí),必須解決兩個(gè)問題:
- 確保消息來源的真實(shí)性
- 確保消息不會(huì)被第三方篡改
而在安裝 APK 時(shí),同樣需要確保 APK 來源的真實(shí)性,以及 APK 沒有被第三方篡改。
想要了解Android中是如何實(shí)現(xiàn)簽名的,就需要了解幾個(gè)概念:消息摘要、數(shù)字簽名和數(shù)字證書。
消息摘要
消息摘要就是在消息數(shù)據(jù)上,執(zhí)行一個(gè)單向的 Hash 函數(shù),生成一個(gè)固定長(zhǎng)度的 Hash 值,這個(gè)Hash值即是消息摘要。
消息摘要有如下的特性,無論輸入的消息數(shù)據(jù)有多長(zhǎng),計(jì)算出來的消息摘要的長(zhǎng)度總是固定的,另外,消息摘要函數(shù)是單向函數(shù),也就是說只能進(jìn)行正向的信息摘要,無法從摘要中恢復(fù)出任何的消息。一般情況下,不會(huì)出現(xiàn)兩條消息,它們對(duì)應(yīng)的消息摘要相同。
正是由于消息摘要的以上特性,使得消息摘要算法被廣泛應(yīng)用在數(shù)字簽名領(lǐng)域。
常見的消息摘要算法有 MD5 算法、 SHA-1、SHA-256等。目前 Android 簽名使用的默認(rèn)算法就是 SHA-256 。
注意,消息摘要只能保證消息的完整性,并不能保證消息的不可篡改性。
那么,怎么保證消息的不可篡改性呢?
這就是得說到另一個(gè)概念了 —— 數(shù)字簽名。
在講數(shù)字簽名之前,我們需要再簡(jiǎn)單的了解幾個(gè)相關(guān)概念:公鑰密碼體制、對(duì)稱加密算法、非對(duì)稱加密算法。
公鑰密碼體制
公鑰密碼體制分為三個(gè)部分,公鑰、私鑰、加密解密算法,它的加密解密過程如下:
- 加密:通過加密算法和公鑰對(duì)明文進(jìn)行加密,得到密文
- 解密:通過解密算法和私鑰對(duì)密文進(jìn)行解密,得到明文
也就是說,公鑰加密的內(nèi)容,只能由私鑰進(jìn)行解密,如果不知道私鑰,密文是無法被解密的。
在實(shí)際的使用中,有需要的人會(huì)生成一對(duì)公鑰和私鑰,把公鑰發(fā)布出去給別人使用,自己保留私鑰。
對(duì)稱加密算法
在對(duì)稱加密算法中,加密和解密都是使用的同一個(gè)密鑰。
因此對(duì)稱加密算法要保證安全性的話,密鑰要做好保密,只能讓使用的人知道,不能對(duì)外公開。
非對(duì)稱加密算法
在非對(duì)稱加密算法中,加密使用的密鑰和解密使用的密鑰是不相同的。
數(shù)字簽名
當(dāng)使用私鑰進(jìn)行加密,使用公鑰進(jìn)行解密時(shí),這個(gè)過程就稱為簽名,
它和加密有什么區(qū)別呢?
因?yàn)楣€是公開的,所以任何持有公鑰的人都能解密私鑰加密過的密文,所以這個(gè)過程并不能保證消息的安全性,但是它卻能保證消息來源的準(zhǔn)確性和不可否認(rèn)性,
也就是說,如果使用公鑰能正常解密某一個(gè)密文,那么就能證明這段密文一定是由私鑰持有者發(fā)布的,而不是其他第三方發(fā)布的,并且私鑰持有者不能否認(rèn)他曾經(jīng)發(fā)布過該消息。故此將該過程稱為簽名。
常規(guī)的簽名方案應(yīng)該是摘要算法和數(shù)字簽名和加密算法結(jié)合起來使用的,它的過程是這樣的:
- 用摘要算法對(duì)數(shù)據(jù)進(jìn)行摘要,得到摘要值
- 再把摘要值用信源的私鑰加密
通過以上兩步得到的消息就是原始信息的數(shù)字簽名。
綜上所述,數(shù)字簽名其實(shí)就是非對(duì)稱加密技術(shù) + 消息摘要技術(shù)的結(jié)合。
數(shù)字證書
通過數(shù)字簽名技術(shù),可以解決可靠通信的問題。
但是大家有沒有注意到,前面講的數(shù)字簽名方法,有一個(gè)前提,就是消息的接收者必須事先得到正確的公鑰。
如果一開始公鑰就被別篡改了,那么這個(gè)公鑰解密出來的數(shù)據(jù)將不會(huì)是信源發(fā)送的數(shù)據(jù)。
那么如何保證公鑰的安全可信呢?這就要靠數(shù)字證書來解決了。
數(shù)字證書是一個(gè)經(jīng)證書授權(quán)中心數(shù)字簽名的包含公鑰擁有者信息以及公鑰的文件,它包含以下內(nèi)容:
- 證書發(fā)布機(jī)構(gòu)
- 證書有效期
- 證書所有人的公鑰
- 證書所有人的名稱
- 證書使用的簽名算法
- 證書發(fā)行者對(duì)證書的數(shù)字簽名
可以看出,數(shù)字證書本身也用到了數(shù)字簽名技術(shù),只不過簽名的內(nèi)容是整個(gè)證書。
與普通數(shù)字簽名不同的是,數(shù)字證書的簽名者不是隨隨便便一個(gè)普通機(jī)構(gòu),而是 CA 機(jī)構(gòu)。
一般來說,這些 CA 機(jī)構(gòu)的根證書已經(jīng)在設(shè)備出廠前預(yù)先安裝到了你的設(shè)備上了。
所以,數(shù)字證書可以保證證書里的公鑰確實(shí)是這個(gè)證書所有者的,或者證書可以用來確認(rèn)對(duì)方的身份。
可見,數(shù)字證書主要是用來解決公鑰的安全發(fā)放問題。
Android的簽名機(jī)制
如果我們熟悉apk打包流程,我們肯定知道,在打包流程的倒數(shù)第二步,會(huì)對(duì)apk進(jìn)行簽名,Android中對(duì)apk進(jìn)行簽名工具有兩種:jarsigner和 apksigner,它們的簽名算法沒什么區(qū)別,主要是簽名使用的文件不同。
- jarsigner:jdk自帶的簽名工具,可對(duì) jar 進(jìn)行簽名,使用 keystore 文件進(jìn)行簽名,生成的簽名文件默認(rèn)使用 keystore 的別名命名
- apksignersdk:專門用于Android應(yīng)用的簽名工具。生成的簽名文件統(tǒng)一使用CERT命名。
簽名校驗(yàn)的過程
Android為我們提供了三個(gè)簽名版本,分別是V1、V2、V3,其中,V2簽名僅支持Android 7.0及之后版本,V3簽名支持Android 9.0及以上版本。
如果是在Android 9.0以上的系統(tǒng)安裝一個(gè)apk,會(huì)先判斷apk是否使用V3簽名,如果沒有使用V3簽名,就判斷是否使用了V2簽名,如果還是沒有使用V2簽名,則確定apk是用了V1簽名,那么這時(shí)就會(huì)使用V1簽名的來校驗(yàn),如果校驗(yàn)失敗就拒絕安裝,如果校驗(yàn)成功則安裝。
這是Android官方提供的一個(gè)apk校驗(yàn)簽名的流程圖:
由此可見,一個(gè)apk是可以同時(shí)使用三個(gè)版本的簽名的,如果同時(shí)使用三個(gè)版本的簽名方式,則優(yōu)先使用V3的簽名進(jìn)行校驗(yàn)。
需要注意的是,對(duì)于覆蓋安裝的情況,簽名校驗(yàn)只支持升級(jí),而不支持降級(jí)。也就是說設(shè)備上安裝了一個(gè)使用 V1 簽名的 APK,可以使用 V2 簽名的 APK 進(jìn)行覆蓋安裝,反之則是不允許的 。
而在Android studio中,我們可以選擇打包V2和V1簽名,但是目前AS并不支持V3簽名,
如果我們想使用V3簽名的話,可以自己通過命令行的方式來自行操作apksigner工具進(jìn)行V3簽名,具體就不舉例了。
V1簽名機(jī)制
如果我們?cè)赼pk中使用了V1簽名,我們會(huì)發(fā)現(xiàn), 在apk中,會(huì)多出一個(gè)META-INF文件夾,而META-INF文件夾中會(huì)有三個(gè)子文件:
- MANIFEST.MF
- CERT.SF
- CERT.RSA
這三個(gè)文件就是V1簽名的產(chǎn)物,下面我對(duì)它們進(jìn)行簡(jiǎn)單的介紹。
1、MANIFEST.MF:對(duì)apk中每個(gè)文件都使用SHA-256算法提取出該文件的的消息摘要然后進(jìn)行base64編碼,得到的編碼值作為SHA1-Digest屬性的值寫入到 MANIFEST.MF 文件中的一個(gè)塊中,且每一個(gè)塊都有一個(gè)Name屬性, 其值就是該文件在 APK 包中的路徑,如下所示:
2、CERT.SF:對(duì)apk中每個(gè)文件以及MANIFEST.MF文件都使用SHA-256算法提取出每個(gè)文件的的消息摘要然后進(jìn)行base64編碼,可以理解為SF文件其實(shí)是用于保護(hù)MANIFEST.MF文件的。
3、CERT.RSA:這里會(huì)把 CERT.SF 文件,用私鑰計(jì)算出簽名,然后將簽名以及包含公鑰信息的數(shù)字證書一同寫入 CERT.RSA 中保存。
其實(shí)說白了,就是MANIFEST.MF文件是用于保護(hù)apk中所有的文件的,而CERT.S文件則是用于保護(hù)MANIFEST.MF文件,CERT.RSA用于保護(hù) CERT.SF 文件,這樣一環(huán)扣一環(huán),來保證apk的安全性。
注意,
V1簽名保護(hù)的是apk中已存在的文件不被修改,
也就是說,如果把a(bǔ)pk解壓后,再添加一個(gè)自己的文件,然后再壓縮成apk,那么這個(gè)apk仍然是可以安裝成功的,因?yàn)槲覀冃绿砑拥哪莻€(gè)文件本身就不在V1簽名的保護(hù)范圍內(nèi)。
那么,如果我們添加的這個(gè)文件是一個(gè)渠道信息相關(guān)的文件,那是不是就做到了在不破壞APK簽名的前提下,往APK中注入渠道信息了。
V2簽名機(jī)制
其實(shí),我們可以發(fā)現(xiàn), V1 簽名有兩個(gè)地方可以改進(jìn):
- 簽名校驗(yàn)速度慢:校驗(yàn)過程中需要對(duì)apk中所有文件進(jìn)行摘要計(jì)算,在 APK 資源很多、性能較差的機(jī)器上簽名校驗(yàn)會(huì)花費(fèi)較長(zhǎng)時(shí)間,導(dǎo)致安裝速度慢
- 完整性保障不夠:META-INF 目錄用來存放簽名,所以該目錄本身是不計(jì)入簽名校驗(yàn)過程的
為了解決這兩個(gè)問題,在 Android 7.0 版本中,引入了V2簽名。
V1簽名保護(hù)的ZIP中已存在的文件,而V2簽名保護(hù)的則是整個(gè)APK的字節(jié)數(shù)據(jù)。
當(dāng)apk中加入一個(gè)文件,那個(gè)這個(gè)apk對(duì)應(yīng)的字節(jié)數(shù)據(jù)就會(huì)發(fā)生變化了,所以V2簽名就不能像V1簽名那樣往apk中的META-INF文件夾注入一個(gè)新文件從而進(jìn)行多渠道打包了。
那么在V2簽名下,如何在不破壞APK簽名的前提下,往APK中注入渠道信息呢?
首先我們要知道,其實(shí)apk就是一個(gè)zip格式的壓縮包,因此,apk的數(shù)據(jù)格式就跟zip格式的壓縮包一樣了。
按照ZIP文件格式,插入一個(gè)簽名分塊區(qū)域,記錄簽名信息。
在簽名前,ZIP的數(shù)據(jù)格式分三塊,分別是藍(lán)、綠、紫這三塊,而簽名后,在藍(lán)和綠之間插入了一個(gè)簽名分塊: APK Signing Block,如下圖所示:
那么,這個(gè)簽名分塊的數(shù)據(jù)格式是怎么的呢?
在整個(gè)簽名塊中,第一個(gè)size of block數(shù)據(jù)塊,它占8個(gè)字節(jié),記錄的是除自己本身之外的整個(gè)簽名塊的長(zhǎng)度。
再看藍(lán)色部分,這里記錄的是id-value的鍵值對(duì),這里就是用來記錄渠道信息的地方了。在里的鍵值對(duì)中,有一個(gè)id為0x7109871a,它所對(duì)應(yīng)的value,就是V2簽名的簽名數(shù)據(jù)。
當(dāng)所有的鍵值對(duì)記錄完后,還有一個(gè)size of block數(shù)據(jù)塊,它同樣占8各個(gè)字節(jié),它記錄的也是除自己本身之外的整個(gè)簽名塊的長(zhǎng)度。
最后,有個(gè)magic數(shù)據(jù)塊,也叫做魔數(shù),它占16個(gè)字節(jié),它是用于標(biāo)記文件格式的,
有的人會(huì)問:文件的后綴名不是也能標(biāo)記文件格式嗎?
其實(shí),文件的后綴名的本質(zhì)是為了讓程序更方便的識(shí)別文件的格式,從而選擇不同的處理方式來處理這個(gè)文件。
但是也會(huì)有人手動(dòng)去修改一個(gè)文件的后綴名的情況,比如一個(gè)jpg格式的文件,手動(dòng)將其后綴修改為.png,這種操作并不能從根源上修改文件的類型,換句話說,即使手動(dòng)修改為.png文件,它本質(zhì)上仍是一個(gè).jpg文件。
而真正用來標(biāo)記文件格式的,是魔數(shù)。
V2簽名下如何優(yōu)化多渠道打包?
根據(jù)官網(wǎng)的描述,V2簽名保護(hù)的內(nèi)容是藍(lán)、綠、紫這三塊區(qū)域,以及第二部分(簽名分塊)中的 id為0x7109871a的數(shù)據(jù),而對(duì)于簽名分塊的其他數(shù)據(jù),是不會(huì)保證的。
所以當(dāng)我們往簽名分塊中插入一個(gè)id-value鍵值對(duì),來記錄渠道信息,是不會(huì)破壞apk的簽名的。
按照這個(gè)思路,就能在V2簽名下,在不破壞APK簽名的前提下,往APK中注入渠道信息了。
注入渠道信息流程如下:
1、解析apk,判斷是否使用了V2簽名,如果是則定位到V2對(duì)應(yīng)的簽名分塊區(qū)域
2、在簽名分塊中添加包含渠道信息的 id-value鍵值對(duì)
3、拷貝原apk,并修改簽名分塊數(shù)據(jù),生成帶有渠道信息的apk
V3簽名機(jī)制
V3簽名其實(shí)跟V2簽名很相似,它仍然采用檢查整個(gè)壓縮包的校驗(yàn)方式,
不同的是在 V2 簽名后插入的簽名塊中,又添加了一個(gè)Attr塊,在這個(gè)新塊中,會(huì)記錄我們之前的簽名信息以及新的簽名信息,
以密鑰轉(zhuǎn)輪的方案,來做簽名的替換和升級(jí)。這意味著,只要舊簽名證書在手,我們就可以通過它在新的 APK 文件中,更改簽名。
由此可知,V3簽名下優(yōu)化多渠道打包的方案和V2簽名是一樣的。
總結(jié)
以上是生活随笔為你收集整理的【Android】多渠道打包与签名机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 失传千年AE特效真经(五)
- 下一篇: Android 中关与类转换异常的问题。