1. 程式人生 > >【Android開發技巧】Android7.0新簽名對多渠道打包的影響

【Android開發技巧】Android7.0新簽名對多渠道打包的影響

老簽名多渠道打包原理
前言

由於Android7.0釋出了新的簽名機制,加強了簽名的加固,導致在新的簽名機制下無法通過美團式的方式再繼續打多渠道包了。不過在說新的簽名機制對打包方案的
影響和為什麼會影響我們原有的打包機制之前,需要先簡單理解下打包原理和簽名在整個打包過程中的作用。

Android打包流程

這裡寫圖片描述

Android打包過程大致如圖所示,整個流程就是將Java程式碼,資原始檔以及第三方庫整合成一個Apk檔案,並對整合後的檔案進行簽名和優化對齊。整個過程可以簡
單分為以下幾個步驟:

  • 資源預編譯   為每一個非assert資源生成一個ID並儲存在一個R檔案中。

  • Java檔案編譯

      把手動編寫的Java檔案和前面步驟生成的Java檔案一起編譯成.class檔案

  • Dex檔案生成  把前面生成的class檔案和第三方庫的class檔案一起整合成.dex檔案,相當於所有.class檔案的索引表,可以通過這個檔案找到每一個
    .class檔案

  • 資原始檔打包  對資源進行大小進行優化,並把所有有ID的資源對應的配置資訊收錄生成resource.arsc檔案中,相當於資源的索引表。

  • APK檔案生成  把之前生成的.dex檔案和resource.arsc檔案以及資原始檔(res檔案,裡面包含一些已編譯成二進位制的檔案如佈局檔案等和一些未編譯
    成二進位制的檔案如圖片等)和未分配ID的檔案(assert檔案)等整合成一個APk檔案(特殊的.zip檔案)。

  • 簽名   對生成的APK進行簽名,新增唯一標識,從而保證能夠正常安裝和覆蓋安裝,不會被惡意覆蓋,以及說明APK的來源和擁有者。

  • 對齊處理   對APK中一些資源的二進位制上的位置進行調整,從而加快APk執行時的速度。

APK生成的步驟大概就是這個樣子,我們不需要糾結一些細節上的實現,只要大概瞭解這些流程就能理解後面我們打包方案的原理了。當然,這麼看會覺得APK
是一個很神奇的檔案,能夠安裝並且執行。其實,APK只是一中比較特別的.zip檔案而已,只是它可以被我們的Android機器識別並且安裝成一個應用,我們可
以把APK檔案解壓出來看一下里面的內容。

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

可以看到檔案內容和我們前面說的基本一致,接下來再看看最重要的一個資料夾,就是第三章圖裡的檔案,這些檔案都在META-INF資料夾裡,這就是我們在籤
名過程中加入的檔案來作為APK的唯一標識。那麼這對我們打包優化來說有什麼意義呢,這裡先保留下懸念,待會兒來說。在瞭解這個之前必須先把簽名這件
事情說清楚。

簽名的作用和原理

簽名的作用

Android App在開發時都有一個唯一的標識,我們把它叫做包名,比如大客戶端的包名是com.Quanr,包名就像身份證號一樣,是和每一個人一一對應的,在
把App安裝時機器也是通過包名來識別應用的,一個機器中不能存在兩個包名一樣的應用,這就是App在機器中的唯一性。我們在給App升級的時候,比如覆蓋
安裝,就是通過包名來進行識別和對應的覆蓋,這樣才能保證App可以順利升級而不會覆蓋了其他的App,同樣,別的App包名不同也無法覆蓋我們的App。雖
然Android提供了一套包名識別機制,但僅有包名就可以了嗎。試想一下,如果別人用我們的包名新建一個App想要覆蓋我們的App亦或是不法分子破解我們的
App往裡面新增一些自己的內容,比如內嵌廣告來牟利,把篡改過的App再發出去讓使用者覆蓋安裝,我們可能會受到巨大的損失。當然這種事情是不會發生的
,Google為每一個App增加了一個簽名機制,就像我們去銀行辦理業務,不但要提供身份證,還要簽字畫押,身份證號大家都可以看到,但簽字畫押只有自己
才能做,別人是無法模仿的,App正是通過這種機制保證了自身的唯一性和安全性。那麼,簽名是如何做到這些的呢。

簽名的原理

對Apk簽名有好幾種工具,但原理都是大同小異的,這裡拿signapk這個工具來說,對apk簽名只要用這個工具附帶一些引數以及我們的唯一的簽名檔案就可以
完成了,直接使用工具簽名是很簡單的,我們更需要關注的是它的原理和具體是怎麼實現的,這樣才能幫助我們從中找出打渠道包的祕密。在使用簽名工具
之前我們必須準備好簽名要用的私鑰和公鑰(a–(私鑰)–>b–(公鑰)–>a,最好用圖片展示),然後再用簽名工具對apk簽名,之後簽名流程會在apk裡新
建META-INF資料夾並在裡面生成三個檔案,這三個檔案就是簽名的關鍵和驗證的依據。從圖中可以看到三個檔案分別是:

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

接下來說說這三個檔案是怎麼生成的。
MANIFEST.MF
這裡寫圖片描述

先看看它的內容,可以看到是一些Name對應的SHA1的值,很容易就能知道這個檔案儲存的是每一個檔案對應的一個唯一的值。MANIIFEST.MF檔案的功能就如
剛才說的一樣,在對APK簽名的時候,首先會對每一個檔案進行數字編碼,生成一個唯一的SHA1的值,不同的檔案內容不一樣所對應的SHA1的值也是不同的,
所以如果有人篡改了檔案內容,那麼相應的SHA1的值也會發生變化。在對APK進行簽名的時候檢查自己的每一個檔案,並生成對應的SHA1值,把這些內容都保
存在一個新建的MANIFEST.MF檔案中,並把這個檔案放到META-INF目錄下,就完成了第一個簽名檔案的生成。

CERT.SF

這裡寫圖片描述

在生成了一個MANIFEST.MF檔案之後,就能記錄下我們每一個檔案的唯一值,從而保證檔案不被篡改,這樣雖然保證了MANIFEST中記錄檔案的安全,但卻無法
保證自身的安全,別人照樣可以修改原有檔案之後生成對應的SHA1值然後再修改MANIFEST檔案,所以我們還要對MANIFEST進行加固,從而保證安全性。在進行
完第一個檔案生成後,簽名工具開始生成第二個檔案了,這個檔案就是CERT.RSA。在這個步驟裡,對上一步生成的檔案繼續進行一次數字編碼,把結果儲存在
這個新生成了CERT.RSA檔案的頭部,在繼續對MANIFEST.MF檔案中各個屬性塊做一次數字編碼,存到到CERT.SF檔案的一個屬性塊中。

CERT.RSA
這裡寫圖片描述

這個檔案都是二進位制的,生成完CERT.SF檔案之後,我們用私鑰對CERT.SF進行加密計算得出簽名,將得到的簽名和公鑰的數字證書的資訊一起儲存到CERT.RSA中,整個簽名過程就結束了。

下面再簡單敘述一下Apk安裝過程中的驗證步驟,其實就是和生成簽名檔案的步驟類似:

  • 首先檢驗Apk中的所有檔案的數字編碼和MANIFEST.MF中的數字編碼一致
  • 驗證CERT.SF檔案的簽名信息和CERT.RSA中的內容是否一致
  • MANIFEST.MF整個檔案簽名在CERT.SF檔案中頭屬性中的值是否匹配以及驗證MANIFEST.MF檔案中的各個屬性塊的簽名在CERT.SF檔案中是否匹配

從上面的過程中,可以很明顯的發現一個問題,在這個過程中從來就沒有對META-INF裡的任何檔案進行數字編碼和加密,因為這個資料夾是簽名時生成的,在
生成第一個檔案MANIFEST.MF時並沒有對這個資料夾裡的任何檔案進行數字編碼,因為這個資料夾一定是空的,第二個檔案是基於第一個檔案生成的,第三個
檔案又是基於第二個生成的,所以整個過程中,這個META-INF資料夾幾乎是在控制範圍之外的。我們可以在裡面新增一些檔案從而躲過簽名這個過程。這就是
美團多渠道包方案的突破口。

美團通過利用簽名原理來完成多渠道打包

通過前面打包過程的瞭解,可以知道想快速打多渠道包,就得跳過打包的階段直接想辦法對Apk檔案進行修改,但這得挑戰Android的簽名校驗規則了,不過正好,通過剛才我們對簽名過程的分
析,我們發現可以在META-INF資料夾裡隨意新增檔案從而躲過簽名規則的檢查,這時候新方案就孕育而生了,在打包生成了一個沒有渠道的Apk檔案後,我們
copy這個Apk並在在META-INF裡新增一個檔案,然後通過檔名來確定渠道號,不同的Apk裡就在META-INF裡新增不同的渠道名檔案,即可確定不同的渠道了
,之後要做的就是在我們需要渠道引數的時候直接去這個檔案裡拿就行了,完美的跳過了打包這個過程。那麼這種方案的耗時呢?僅僅就是基於一個Apk檔案
的基礎上覆制並插入一個檔案而已,這樣的動作在高配的電腦裡一分鐘可以做900次。所以,這時候打一個渠道包要4分鐘,打100個渠道包也只要四分鐘加一
個小零頭,當然,這還不是極限,如果有900個渠道的話,一個個渠道包打包需要900x4分鐘,美團方案只要5分鐘,提高了
好幾百倍的效率,可以說美團方案在老簽名下是非常完美的。
這裡寫圖片描述

新簽名方式對現有方案的影響

在Android 7.0 之後,Google為了增強簽名的安全性,採用了新的簽名驗證規則,不是針對每個檔案來進行數字編碼了,如圖所示:

這裡寫圖片描述

新的簽名方式是根據zip檔案結構來進行簽名的,Apk檔案本質上就是一個.zip檔案,如圖所示,在被簽名前可以分為三塊內容。

  • 壓縮檔案實體資料 包含所有的元素具體資料
  • 核心目錄資料 包含所有元資料的相對偏移量
  • 目錄結束標誌 包含目錄資料的偏移位置和相關目錄詳情記錄

下圖中藍色的部分代表Contents of ZIP entries,粉色的部分代表Central Directory。

File Entry表示一個檔案實體,一個壓縮檔案中有多個檔案實體。
檔案實體由一個頭部和檔案資料組成(壓縮後的,壓縮演算法在頭部有說明)
Central Directory由多個File header組成,每個File header都儲存一個檔案實體的偏移
檔案最後由一個叫End of central directory的結構結束。

這裡寫圖片描述

再看看End of Central Directory的作用,它記錄了Central Directory相對於頭部的偏移位置,再看看我們的新簽名資料Apk Signing Block是放在
Contents of ZIP entries和Central Directory之間的,它是根據未簽名的ZIP檔案三個模組的所有內容進行編碼簽名後產生的一個唯一的資料,可以
按照前面說的數字編碼來理解,這三塊任何內容發生改變後所產生的這塊的資料都是不一致的,所以在簽名之後無法對Apk的任何內容進行修改,不
過我們前面也沒修改過Apk簽名之外的內容,所以這個時候是不是可以考慮修改Apk Signing Block這塊內容呢,我們在後面再追加一個小尾巴。顯然
這個方法也不行了,前面說過最後有一個叫End of Central Direcotry的部分,記錄著Central Direcotry相對與ZIP檔案起始位置的偏移,正好APK S
igning Block位於Central Direcotry前面,如果改變APK Signing Block的長度那麼Central Direcotry相對於頭部的偏移就改變了,內容就會和End
of Central Directory裡記錄的不一樣,整個Apk包就被破壞了。那麼是不是可以修改這個偏移呢,顯然不行,修改過這個偏移之後Apk Signing Block
也會變,然後只有擁有簽名檔案的開發者才能得到對的APK Signing Block資料,想要篡改這個的人只能望洋興嘆了。這就是新簽名機制的作用原理。在
這套新的機制下,我們老的簽名可能就要進行適當的修改了。

改進方案的思路

前面說過了,我們新的打包方案在新的簽名機制下又要捉襟見肘了(當然,如果你還採用老簽名的話那麼就不需要考慮這個問題了),不過,Google有張良計,我們還有過牆梯~

之前說過新增渠道包有好幾種方式:

  • 直接修改程式碼,一個渠道包改一次程式碼,然後再打包
  • 不直接修改程式碼,放出一個渠道引數在打包之前動態改變然後再打包
  • 修改打包完成後的檔案通過跳過簽名校驗加入渠道號

現在這三種方式都不行了,沒關係,我們還有第四種。

之前分析過,如果修改了zip檔案的任何模組內容,APK Signing Block都會發生改變,從而無法再繞過簽名機制。
不過這個不要緊,簽名機制只是為了保障我們Apk的安全,我們顯然不是想惡意篡改App,我們可是開發者,手握著簽名檔案的私鑰和公鑰,既然簽名機制
繞不過去,那麼我們就直接修改Apk包然後重新進行簽名就行了。雖然這樣的效率沒有繞過簽名機制效率高,但相比於好幾分鐘的打包過程,這些時間也是
可以接受的。一般重新簽名大概需要三四秒的時間,相對與我們四分鐘的打包流程,顯然還是可以接受和繼續使用的。
當然,這只是基於我們簽名機制想出來的一個方案,之前和大家分析了打包和簽名的整個流程和機制,也許還有更好的方式可以從中挖掘出來,這就需
要我們集思廣益了,仔細分析和思考這些流程和原理,也許你就能找出更好的解決方案來繼續優化我們的打包方案~