Androiud多渠道打包分析

背景:目前項目在打渠道包的時候危队,采用的是AndroidManifest.xml配置渠道號,上線前一個個構(gòu)建出來,全部構(gòu)建完成耗時長達一個小時氧苍,這對于追求高效的工程師來講是無法忍受的。當(dāng)前有很多公司開源了多渠道打包的方案泛范,如美團的walle让虐,騰訊的VasDolly,還有技術(shù)達人個人研究的packer-ng-plugin罢荡,今天就來探索下多渠道打包的奧秘赡突。

在了解多渠道打包之前对扶,需要先了解下android的簽名方式,這樣才能知己知彼百戰(zhàn)不殆惭缰!

APK 簽名方案

Android 支持兩種應(yīng)用簽名方案浪南,一種是基于 JAR 簽名的方案(v1 方案),另一種是 Android Nougat (Android 7.0) 中引入的 APK 簽名方案 v2(v2 方案)漱受。

為了最大限度地提高兼容性络凿,應(yīng)同時采用 v1 和 v2 這兩種方案對應(yīng)用進行簽名。與只通過 v1 方案簽名的應(yīng)用相比昂羡,通過 v2 方案簽名的應(yīng)用能夠更快速地安裝到 Android Nougat 及更高版本的設(shè)備上絮记。更低版本的 Android 平臺會忽略 v2 簽名,這就需要應(yīng)用包含 v1 簽名虐先。

JAR 簽名(v1 方案)

從一開始怨愤,APK 簽名就是 Android 的一個有機部分。該方案基于簽名的JAR蛹批。

v1 簽名不保護 APK 的某些部分撰洗,例如 ZIP 元數(shù)據(jù)。APK 驗證程序需要處理大量不可信(尚未經(jīng)過驗證)的數(shù)據(jù)結(jié)構(gòu)腐芍,然后會舍棄不受簽名保護的數(shù)據(jù)差导。這會導(dǎo)致相當(dāng)大的受攻擊面。此外猪勇,APK 驗證程序必須解壓所有已壓縮的條目设褐,而這需要花費更多時間和內(nèi)存。為了解決這些問題埠对,Android 7.0 中引入了 APK 簽名方案 v2络断。

APK 簽名方案 v2(v2 方案)

Android 7.0 中引入了 APK 簽名方案 v2(v2 方案)。該方案會對 APK 的內(nèi)容進行哈希處理和簽名项玛,然后將生成的“APK 簽名分塊”插入到 APK 中貌笨。

多渠道打包方案

Gradle插件模式

    1. 在AndroidManifest.xml中添加渠道信息:
<meta-data
        android:name="UMENG_CHANNEL"
        android:value="${UMENG_CHANNEL_VALUE}"></meta-data>
            
  • 2.通過Gradle Plugin提供的productFlavors標簽,添加渠道信息:
productFlavors.all { flavor ->
        flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
    }
    
flavorDimensions 'channel'
productFlavors {
    "yingyongbao" {
        dimension "channel"
    }
    "_360" {
        dimension "channel"
    }

Gradle編譯生成多渠道包時襟沮,會用不同的渠道信息替換AndroidManifest.xml中的占位符锥惋。在代碼中,也就可以直接讀取AndroidManifest.xml中的渠道信息了开伏。

ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(),
                    PackageManager.GET_META_DATA);
int channelId = appInfo.metaData.getInt("UMENG_CHANNEL");

當(dāng)前方案存在的問題膀跌,在開頭也說了,現(xiàn)在總結(jié)一下(引用VasDolly原文)

1.每生成一個渠道包固灵,都要重新執(zhí)行一遍構(gòu)建流程捅伤,效率太低,浪費時間巫玻,不太適合多渠道包的場景丛忆。

2.Gradle會為每個渠道包生成一個不同的BuildConfig.java類祠汇,記錄渠道信息,導(dǎo)致每個渠道包的DEX的CRC值都不同熄诡。一般情況下可很,這是沒有影響的。但是如果你使用了微信的Tinker熱補丁方案凰浮,那么就需要為不同的渠道包打不同的補丁我抠,這完全是不可以接受的。(因為Tinker是通過對比基礎(chǔ)包APK和新包APK生成差分補丁袜茧,然后再把補丁和基礎(chǔ)包APK一起合成新包APK菜拓。這就要求用于生成差分補丁的基礎(chǔ)包DEX和用于合成新包的基礎(chǔ)包DEX是完全一致的,即:每一個基礎(chǔ)渠道包的DEX文件是完全一致的惫周,不然就會合成失敵揪濉)

有沒有可能康栈,在不改變基礎(chǔ)包的情況下递递,快速的打出多渠道包呢?

APK本身也是個zip壓縮包啥么,多渠道打包是根據(jù)zip包的文件格式來切入的登舞,所以可以先來看下zip包的文件格式。

[local file header 1]
[local file header 1]
[file data 1]
[data descriptor 1]
. 
.
.
[local file header n]
[file data n]
[data descriptor n]
[archive decryption header] (EFS)
[archive extra data record] (EFS)
[central directory]
[zip64 end of central directory record]
[zip64 end of central directory locator] 
[end of central directory record]
  

zip主要由三部分組成:

壓縮源文件數(shù)據(jù)區(qū) 中央目錄 目錄結(jié)束
local file header + file data + data descriptor central directory end of central directory record
記錄了文件名悬荣、壓縮算法菠秒、壓縮前后的文件大小、修改時間氯迂、CRC32值等 包含了多個central directory file header(和第一部分的local file header一一對應(yīng))践叠,每個中央目錄文件頭主要記錄了壓縮算法、注釋信息嚼蚀、對應(yīng)local file header的偏移量等 主要記錄了中央目錄大小禁灼、偏移量和ZIP注釋信息等

因基于v1方案的多渠道打包,會在目錄結(jié)束部分做文章轿曙,這里把End of central directory record(EOCD)結(jié)構(gòu)詳細結(jié)構(gòu)描述下弄捕。

目錄結(jié)束標識存在于整個歸檔包的結(jié)尾,用于標記壓縮的目錄數(shù)據(jù)的結(jié)束导帝。每個壓縮文件必須有且只有一個EOCD記錄守谓。

Offset Bytes Description
0 4 End of central directory signature = 0x06054b50 核心目錄結(jié)束標記(0x06054b50)
4 2 Number of this disk 當(dāng)前磁盤編號
6 2 number of the disk with the start of the central directory 核心目錄開始位置的磁盤編號
8 2 total number of entries in the central directory on this disk 該磁盤上所記錄的核心目錄數(shù)量
10 2 total number of entries in the central directory 核心目錄結(jié)構(gòu)總數(shù)
12 2 Size of central directory (bytes) 核心目錄的大小
16 4 offset of start of central directory with respect to the starting disk number 核心目錄開始位置相對于archive開始的位移
20 2 .ZIP file comment length(n) 注釋長度
22 n .ZIP Comment 注釋內(nèi)容

有了以上對zip文件格式的認知,來看下android簽名方案以及如何從簽名方案下手來做多渠道打包您单。

基于V1簽名的多渠道打包方案

根據(jù)之前的V1簽名和校驗機制可知斋荞,V1簽名只會檢驗第一部分的所有壓縮文件,而不理會后兩部分內(nèi)容虐秦。因此平酿,只要把渠道信息寫入到后兩塊內(nèi)容就可以通過V1校驗讯檐,而EOCD的注釋字段無疑是最好的選擇湿痢。

上面也說過锡足,apk實際上就是普通的zip,在一個zip文件的最后允許寫入N個字符的注釋携栋,zip末尾兩個部分:2字節(jié)的的注釋長度+N個字節(jié)的注釋柳刮。

那么挖垛,我們只要把簽名內(nèi)容作為注釋寫入,再修改2字節(jié)的注釋長度即可秉颗。

那么我們怎么知道一個apk有沒有寫入這個渠道信息呢痢毒?

我們可以在文件文件末尾寫入一個特殊的字符串,當(dāng)我們讀取文件末尾為這個特殊的字符串蚕甥,即可認為該apk寫入了渠道信息哪替。該特殊字符串稱之為魔數(shù)

最終的渠道信息為:
渠道字符串+渠道字符串長度+魔數(shù)

在APK文件的注釋字段,添加渠道信息菇怀。
整個方案包括以下幾步:

1.復(fù)制APK
2.找到EOCD數(shù)據(jù)塊
3.修改注釋長度
4.添加渠道信息
5.添加渠道信息長度
6.添加魔數(shù)

接下來看如何從apk讀取這個渠道信息呢凭舶?

根據(jù)上面的了解,添加魔數(shù)的好處是方便從后向前讀取數(shù)據(jù)爱沟,定位渠道信息帅霜。 因此,讀取渠道信息包括以下幾步:

1.定位到魔數(shù)
2.向前讀兩個字節(jié)呼伸,確定渠道信息的長度LEN
3.繼續(xù)向前讀LEN字節(jié)身冀,就是渠道信息了。

用二進制編輯器打開打包好的Apk括享,看末尾的幾個字節(jié)搂根,如圖:

image.png

對這圖可以分析如下:

  • 首先讀取8個字節(jié),對應(yīng)一個特殊字符串“l(fā)tlovezh”
  • 往前兩個字節(jié)為02 00铃辖,對應(yīng)渠道信息長度剩愧,實際值為2.
  • 再往前讀取2個字節(jié)為63 31,對照ASCII表澳叉,即可知為c1
    讀取到渠道信息為:c1隙咸。

到這,渠道讀寫方案基本完了成洗,再此不再深入五督,有時間在結(jié)合代碼分析。

基于V2簽名的多渠道打包方案

使用 APK 簽名方案 v2 進行簽名時瓶殃,會在 APK 文件中插入一個 APK 簽名分塊充包,該分塊位于“ZIP 中央目錄”部分之前并緊鄰該部分。在“APK 簽名分塊”內(nèi),v2 簽名和簽名者身份信息會存儲在 APK 簽名方案 v2 分塊中基矮。


image.png

上圖中淆储,簽名前和簽名后的 APK
APK 簽名方案 v2 是在 Android 7.0 (Nougat) 中引入的。為了使 APK 可在 Android 6.0 (Marshmallow) 及更低版本的設(shè)備上安裝家浇,應(yīng)先使用 JAR 簽名功能對 APK 進行簽名本砰,然后再使用 v2 方案對其進行簽名。

APK 簽名分塊

為了保持與當(dāng)前 APK 格式向后兼容钢悲,v2 及更高版本的 APK 簽名會存儲在“APK 簽名分塊”內(nèi)点额,該分塊是為了支持 APK 簽名方案 v2 而引入的一個新容器。在 APK 文件中莺琳,“APK 簽名分塊”位于“ZIP 中央目錄”(位于文件末尾)之前并緊鄰該部分还棱。
該分塊包含多個“ID-值”對,所采用的封裝方式有助于更輕松地在 APK 中找到該分塊惭等。APK 的 v2 簽名會存儲為一個“ID-值”對珍手,其中 ID 為 0x7109871a。

為了保護 APK 內(nèi)容辞做,APK 包含以下 4 個部分:

ZIP 條目的內(nèi)容(從偏移量 0 處開始一直到“APK 簽名分塊”的起始位置)
APK 簽名分塊
ZIP 中央目錄
ZIP 中央目錄結(jié)尾

image.png

上圖中琳要,簽名后的各個 APK 部分

APK 簽名方案 v2 負責(zé)保護第 1、3凭豪、4 部分的完整性焙蹭,以及第 2 部分包含的“APK 簽名方案 v2 分塊”中的 signed data 分塊的完整性晒杈。

第 1嫂伞、3 和 4 部分的完整性通過其內(nèi)容的一個或多個摘要來保護,這些摘要存儲在 signed data 分塊中拯钻,而這些分塊則通過一個或多個簽名來保護帖努。

第 1、3 和 4 部分的摘要采用以下計算方式粪般,類似于兩級 Merkle 樹拼余。 每個部分都會被拆分成多個大小為 1 MB(220 個字節(jié))的連續(xù)塊。每個部分的最后一個塊可能會短一些亩歹。每個塊的摘要均通過字節(jié) 0xa5 的連接匙监、塊的長度(采用小端字節(jié)序的 uint32 值,以字節(jié)數(shù)計)和塊的內(nèi)容進行計算小作。頂級摘要通過字節(jié) 0x5a 的連接亭姥、塊數(shù)(采用小端字節(jié)序的 uint32 值)以及塊的摘要的連接(按照塊在 APK 中顯示的順序)進行計算。摘要以分塊方式計算顾稀,以便通過并行處理來加快計算速度达罗。

image.png

上圖中 APK 摘要

由于第 4 部分(ZIP 中央目錄結(jié)尾)包含“ZIP 中央目錄”的偏移量,因此該部分的保護比較復(fù)雜静秆。當(dāng)“APK 簽名分塊”的大小發(fā)生變化(例如粮揉,添加了新簽名)時巡李,偏移量也會隨之改變。因此扶认,在通過“ZIP 中央目錄結(jié)尾”計算摘要時侨拦,必須將包含“ZIP 中央目錄”偏移量的字段視為包含“APK 簽名分塊”的偏移量。

V2校驗流程

在 Android 7.0 中辐宾,可以根據(jù) APK 簽名方案 v2(v2 方案)或 JAR 簽名(v1 方案)驗證 APK阳谍。更低版本的平臺會忽略 v2 簽名,僅驗證 v1 簽名螃概。


image.png

上圖 中APK 簽名驗證過程(新步驟以紅色顯示)

APK 簽名方案 v2 驗證

  1. 找到“APK 簽名分塊”并驗證以下內(nèi)容:
    1. “APK 簽名分塊”的兩個大小字段包含相同的值矫夯。
    2. “ZIP 中央目錄結(jié)尾”緊跟在“ZIP 中央目錄”記錄后面。
    3. “ZIP 中央目錄結(jié)尾”之后沒有任何數(shù)據(jù)吊洼。
  2. 找到“APK 簽名分塊”中的第一個“APK 簽名方案 v2 分塊”训貌。如果 v2 分塊存在,則繼續(xù)執(zhí)行第 3 步冒窍。否則递沪,回退至使用 v1 方案驗證 APK。
  3. 對“APK 簽名方案 v2 分塊”中的每個 signer 執(zhí)行以下操作:
    1. signatures 中選擇安全系數(shù)最高的受支持 signature algorithm ID综液。安全系數(shù)排序取決于各個實現(xiàn)/平臺版本款慨。
    2. 使用 public key 并對照 signed data 驗證 signatures 中對應(yīng)的 signature。(現(xiàn)在可以安全地解析 signed data 了谬莹。)
    3. 驗證 digestssignatures 中的簽名算法 ID 列表(有序列表)是否相同檩奠。(這是為了防止刪除/添加簽名。)
    4. 使用簽名算法所用的同一種摘要算法計算 APK 內(nèi)容的摘要附帽。
    5. 驗證計算出的摘要是否與 digests 中對應(yīng)的 digest 相同埠戳。
    6. 驗證 certificates 中第一個 certificate 的 SubjectPublicKeyInfo 是否與 public key 相同。
  4. 如果找到了至少一個 signer蕉扮,并且對于每個找到的 signer整胃,第 3 步都取得了成功,APK 驗證將會成功喳钟。

注意:如果第 3 步或第 4 步失敗了屁使,則不得使用 v1 方案驗證 APK。

基于V2簽名的多渠道打包方案

在看一個問題奔则,V2簽名是怎么保證APK不被篡改的蛮寂?
首先,如果破壞者修改了APK文件的任何部分(簽名塊本身除外)应狱,那么APK的數(shù)據(jù)摘要就和“MF”數(shù)據(jù)塊中記錄的數(shù)據(jù)摘要不一致共郭,導(dǎo)致校驗失敗。 其次,如果破壞者同時修改了“MF”數(shù)據(jù)塊中的數(shù)據(jù)摘要除嘹,那么“MF”數(shù)據(jù)塊的數(shù)字簽名就和“SF”數(shù)據(jù)塊中記錄的數(shù)字簽名不一致写半,導(dǎo)致校驗失敗。 然后尉咕,如果破壞者使用自己的私鑰去加密生成“SF”數(shù)據(jù)塊叠蝇,那么使用開發(fā)者的公鑰去解密“SF”數(shù)據(jù)塊中的數(shù)字簽名就會失敗年缎; 最后悔捶,更進一步,若破壞者甚至替換了開發(fā)者公鑰单芜,那么使用數(shù)字證書中的公鑰校驗簽名塊中的公鑰就會失敗蜕该,這也正是數(shù)字證書的作用。
綜上所述洲鸠,任何對APK的修改堂淡,在安裝時都會失敗,除非對APK重新簽名扒腕。但是相同包名绢淀,不同簽名的APK也是不能同時安裝的。

基于V2簽名的多渠道打包方案
在上節(jié)V2簽名的校驗流程中瘾腰,有一個很重要的細節(jié):Android系統(tǒng)只會關(guān)注ID為0x7109871a的V2簽名塊皆的,并且忽略其他的ID-Value,同時V2簽名只會保護APK本身蹋盆,不包含簽名塊费薄,因此,k添加一個ID-Value怪嫌,存儲渠道信息义锥,即解決了基于V2簽名的多渠道打包問題。

方案包括以下幾步:

找到APK的EOCD塊
找到APK簽名塊
獲取已有的ID-Value Pair
添加包含渠道信息的ID-Value
基于所有的ID-Value生成新的簽名塊
修改EOCD的中央目錄的偏移量(上面已介紹過:修改EOCD的中央目錄偏移量岩灭,不會導(dǎo)致數(shù)據(jù)摘要校驗失敗)
用新的簽名塊替代舊的簽名塊赂鲤,生成帶有渠道信息的APK

實際上噪径,除了渠道信息,我們可以在APK簽名塊中添加任何輔助信息数初。

多渠道包的強校驗

那么如何保證通過這些方案生成的渠道包找爱,能夠在所有Android平臺上正確安裝呢?

Google提供了一個同時支持V1和V2簽名和校驗的工具:apksig泡孩。它包括一個apksigner命令行和一個apksig類庫车摄。其中前者就是Android SDK build-tools下面的命令行工具。而我們正是借助后面的apksig來進行渠道包強校驗,它可以保證渠道包在apk Minsdk ~ Maxsdk之間都校驗通過吮播。

至此变屁,多渠道打包方案基本看完了,還有其他方案意狠,暫未分析粟关,有興趣的可以自行研究。

image.png

參考文獻:
APK 簽名方案 v2
應(yīng)用簽名
VasDolly
walle
帶你了解騰訊開源的多渠道打包技術(shù) VasDolly源碼解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末环戈,一起剝皮案震驚了整個濱河市闷板,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌院塞,老刑警劉巖遮晚,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拦止,居然都是意外死亡鹏漆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門创泄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來艺玲,“玉大人,你說我怎么就攤上這事鞠抑》咕郏” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵搁拙,是天一觀的道長秒梳。 經(jīng)常有香客問我,道長箕速,這世上最難降的妖魔是什么酪碘? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮盐茎,結(jié)果婚禮上兴垦,老公的妹妹穿的比我還像新娘。我一直安慰自己字柠,他們只是感情好探越,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著窑业,像睡著了一般钦幔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上常柄,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天鲤氢,我揣著相機與錄音搀擂,去河邊找鬼。 笑死卷玉,一個胖子當(dāng)著我的面吹牛哨颂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揍庄,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼咆蒿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蚂子?” 一聲冷哼從身側(cè)響起沃测,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎食茎,沒想到半個月后蒂破,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡别渔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年附迷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哎媚。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡喇伯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拨与,到底是詐尸還是另有隱情稻据,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布买喧,位于F島的核電站捻悯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏淤毛。R本人自食惡果不足惜今缚,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望低淡。 院中可真熱鬧姓言,春花似錦、人聲如沸查牌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纸颜。三九已至,卻和暖如春绎橘,著一層夾襖步出監(jiān)牢的瞬間胁孙,已是汗流浹背唠倦。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涮较,地道東北人稠鼻。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像狂票,于是被迫代替她去往敵國和親候齿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容