借騰訊開源 VasDolly,談談 Android 簽名和多渠道打包的原理!

承香墨影發表於2018-02-28

借騰訊開源 VasDolly,談談 Android 簽名和多渠道打包的原理!

一、前言

Hi,大家好,我是承香墨影!

當我們需要釋出一款 App 到應用市場的時候,一般需要我們針對不同的市場生產不同的渠道包,它們使用的是同一套程式碼,只是會包含一些各自的渠道資訊,用於我們做資料分析。

前幾天,企鵝電競團隊開源了自己的 Android Apk 多渠道打包工具:VasDolly,比美團的 Walle 更全面一些。

正好借這個機會,來講解一下 Android 的不同版本的簽名機制的差異。

二、Android 的簽名

2.1 應用簽名

通過對 Apk 進行簽名,開發者可以證明對 Apk 的所有權和控制權,可用於安裝和更新其應用。而在 Android 裝置上的安裝 Apk ,如果是一個沒有被簽名的 Apk,則會被拒絕安裝。

在安裝 Apk 的時候,軟體包管理器也會驗證 Apk 是否已經被正確簽名,並且通過簽名證書和資料摘要驗證是否合法沒有被篡改。只有確認安全無篡改的情況下,才允許安裝在裝置上。

簡單來說,APK 的簽名主要作用有兩個:

  1. 證明 APK 的所有者。
  2. 允許 Android 市場和裝置校驗 APK 的正確性。

而在 Android 中,支援兩種應用簽名的方案:

  1. 基於 JAR 簽名方案(v1 方案)。
  2. Android Nougat(7.0) 中引入的 APK 簽名方案 v2(v2 方案)。

既然簽名方案在升級,v1 方案一定有一些缺陷的地方,接下來我們先來了解一下這兩個方案的細節。

2.2 v1 簽名方案

v1 簽名方案,並不會保護 Apk 內的所有內容,有一些例外部分,被修改也並不會導致簽名失效。例如:ZIP 後設資料。

這樣,在驗證 APK 簽名的時候,就需要處理大量不可信(尚未經過驗證)的資料結構,然後還需要過濾並捨棄掉這部分不受簽名保護的資料,再進行簽名校驗。也就是說你可以在已簽名的檔案中,增加一些不被簽名保護的內容,這將導致受攻擊的可能增大。

另外,v1 方案是對 APK 內部的被保護的原始檔案(未壓縮),單獨進行計算資料摘要,所以在驗證期間,也需要對每個檔案進行減壓再進行簽名校驗,來驗證是否被篡改。所以在驗證 APK 簽名的時候,必須解壓 APK 的所有已壓縮的檔案條目進行資料摘要的校驗,而這些,都將需要花費更多的時間和記憶體。

正是因為 v1 方案的缺陷,Android 7.0 開始,才引入了 APK 簽名方案 v2。

2.3 v2 簽名方案

APK 簽名方案 v2 是一種全檔案的簽名方案,該方案能夠對 APK 所有受保護的部分進行簽名保護,從能能夠發現它們被篡改。

在 APK 驗證期間,v2 方案會將 APK 檔案視為 Blob,並對整個檔案進行簽名檢查。對 APK 進行的任何修改(包括對 ZIP 後設資料的修改),都會使 APK 簽名作廢。

使用 APK 簽名方案 v2 進行簽名時,會在 APK 檔案中,插入一個 APK 簽名塊,該分塊位於“ZIP 中央目錄”部分之前並緊鄰該部分。在“APK 簽名分塊”內,v2 簽名和簽名者身份資訊會儲存在 APK 簽名方案 v2 分塊中。

該分塊包含多個 "ID-值" 對,所採用的封裝方式有助於更輕鬆地在 APK 中找到該分塊。APK 的 v2 簽名會儲存為一個 "ID-值" 對,其中 ID 為 0x7109871a

借騰訊開源 VasDolly,談談 Android 簽名和多渠道打包的原理!

上圖是簽名前後,APK 檔案結構的對比。可以看到在 v2 已簽名的 APK 中,包含了 4 個部分。

  1. ZIP 條目的內容。
  2. APK 簽名分塊(APK Signing Block)。
  3. ZIP 中央目錄。
  4. ZIP 中央目錄結尾。

APK 簽名方案 v2 負責保護第 1、3、4 部分的完整性,以及第 2 部分包含的 APK 簽名方案 v2 分塊中的 signed data 分塊的完整性。

第 1、3 和 4 部分的完整性通過其內容的一個或多個摘要來保護,這些摘要儲存在 signed data 分塊中,而這些分塊則通過一個或多個簽名來保護。

第 1、3 和 4 部分的摘要資訊,採用一種類似兩級 Merkle 樹的方式進行計算,所以效率上會提高很多。這個就比較複雜了,大家有興趣再深入研究。

借騰訊開源 VasDolly,談談 Android 簽名和多渠道打包的原理!

既然已經有了 v2 方案,我們是否可以完全捨棄 v1 方案,只使用 v2 方案呢?

從官方文件瞭解到,因為 APK 簽名方案 v2 是在 Android 7.0 中引入的,為了使 APK 可以在 Android 6.0 以及更低的版本中正常安裝,應該先使用 JAR簽名(v1 簽名)對 APK 進行簽名然後再使用 v2 方案對其進行簽名

而在實際測試中,這種僅使用 v2 方案不使用 v1 方案的情況,是可以編譯通過的,並且在 Android 7.0 之上的裝置上也是可以安裝和執行,但是在 7.0 之下,因為不檢測 v2 簽名,而 APK 又不存在 v1 簽名,在安裝的時候,會提示沒有簽名的錯誤。

為了保證相容性的問題,在 7.0 以上的裝置中,也是相容了 v1 簽名的方案。所以 v2 簽名方案,暫時並不是一個強制的方案。

優先順序是:優先校驗 v2 方案,沒有或者不存在校驗機制,則校驗 v1 方案

借騰訊開源 VasDolly,談談 Android 簽名和多渠道打包的原理!

2.4 兩種方案對比

到這裡,我想應該對 v1 和 v2 簽名方案應該已經有一定了解了。v2 簽名既然是升級版,就是為了解決 v1 簽名方案的一些問題。

1、效率問題

v1 方案,會在 /META-INF/MANIFEST.MF 檔案中,記錄所有需要校驗簽名的檔案的資料摘要,並且這裡生產資料摘要是依據壓縮前的原始檔,而在打包的過程中,這些檔案又會被壓縮。

所以在驗證簽名的過程中,需要先解壓出原始檔案,才能計算資料摘要從而驗證它,而這個過程,會消耗更多的時間和記憶體。

v2 簽名方案,是對 APK 檔案本身進行資料摘要計算,也就是說它只計算一個檔案(v1 會計算 Apk 內的很多檔案),這樣就不存在解壓 APK 的操作,效率和記憶體都得到優化。

2、安全問題

v1 方案,它只會對 APK 內部,部分檔案進行校驗,並不會對 APK 的完整性進行校驗。因此,簽名後,我們依然可以修改 APK 檔案,例如:美團的多渠道方案就是在 /META-INF/ 目錄下,新增一個空檔案,使用檔名來標誌渠道。

而 v2 簽名是針對整個 APK 進行校驗,所以對 APK 的任何改動,都無法通過 v2 簽名的驗證,這樣安全性會更高。

關於簽名校驗的耗時上,主要影響的是安裝耗時,這裡有一份實驗室資料(Nexus 6P、Android 7.1.1)可供參考。

借騰訊開源 VasDolly,談談 Android 簽名和多渠道打包的原理!

差不多 2.x 倍的差距,v2 簽名對 APK 的安裝還是有不少提升的。

三、常見的多渠道方案

多渠道的需求點,在於同一個 App,需要有一些不同來區分它們,最常見的場景就是 Android 國內的市場,對不同的市場使用不同的渠道包,它們的程式碼是一致的,只是一個渠道號的資料不一樣。這樣區分的好處在於資料更清晰,我們知道那個渠道的使用者是優質使用者。

3.1 被廢棄的方案

既然簽名方案在升級,多渠道的方案也需要升級。曾經有一寫可行的方案,基本上已經被廢棄了。例如:Gradle Plugin 配置不同的 Flavors、利用 ApkTool 解包改資料再重新打包並重新簽名。

這些方案都有一些侷限性。例如 Gradle Flavors 方案,每個渠道都需要重新打包,非常的耗時,並且生成的渠道包 DEX 的 CRC 值都不一致,不利於我們使用熱更新的方案;而 ApkTool 的方案,首先 解包→打包→重新簽名 的過程同樣耗時,其次在於,不穩定,可能升級了 Gradle Plugin 的版本之後,會導致解包失敗。

所以我們在想,一個高效的多渠道打包方案,有幾個關鍵單需要注意。

  1. 不能破壞簽名。
  2. 不能重新打包。
  3. 讀取資訊,必須高效。

不破壞簽名就限制了不能解包以及重新簽名,勢必對效率有所提高。

而除了 VasDolly 之外,市面上流傳比較廣的就是 美團 給出的 Walle 方案,這裡就以它們的原理進行簡單的講解。

3.2 v1 簽名下的常見多渠道方案

v1 下對簽名的校驗比較弱了,美團給出了完整的解決方案。前面也介紹過,就是在以及打包簽名好的 Apk 中,向 /META-INF 目錄下,寫入一個空檔案,以檔名來標識渠道號。

借騰訊開源 VasDolly,談談 Android 簽名和多渠道打包的原理!

首先,使用這樣的方案,並不會破壞 v1 簽名,所以效率會很高。

而在騰訊的 VasDolly 中,其實是向 Apk 檔案的 EOCD 部分,增加渠道的資訊。

借騰訊開源 VasDolly,談談 Android 簽名和多渠道打包的原理!

APK 檔案本質上是一個 ZIP 包,而 EOCD 就是上圖所示的第三部分,並且這部分是不被 v1 簽名校驗的,可以用來記錄我們的渠道資訊。

3.3 v2 簽名下的多渠道方案

v2 方案下,其實騰訊 VasDolly 和美團的 Walle 方案,並沒有什麼區別,因為驗證更強了,大家可操作的區域就只有那麼多了。

前面也提到過,使用 v2 簽名後的 APK 檔案結構中,插入了一個 APK Signing Block 的簽名塊。

apk-sections

在這個 APK 檔案結構下,只有 塊2 ,也就是記錄 APK v2 簽名資訊的區域,不是全部參與 v2 簽名的校驗,所以大家都在這部分做文章。

APK Signing Block 中包含了多個 "ID-值" 的鍵值對,而 v2 簽名的資訊,會記錄在 ID 為 0x7109871a 中,而不會校驗其他 ID 下的值。

因此,基於 v2 簽名的多渠道方案就這樣誕生了:在 APK 簽名塊中新增一個額外的 ID-值,用於記錄渠道資訊

四、可商用的多渠道打包方案

在 VasDolly 開源之前,市面上支援 v2 簽名的多渠道打包方案,就屬美團的 Walle 了,下面簡單比對一下它們的優缺點。

借騰訊開源 VasDolly,談談 Android 簽名和多渠道打包的原理!

具體你想使用哪種方案,就看你的實際需要了。

美團 Walle Github 地址:

https://github.com/Meituan-Dianping/walle

Walle 掃碼直達

借騰訊開源 VasDolly,談談 Android 簽名和多渠道打包的原理!

騰訊 VasDolly Github 地址:

https://github.com/Tencent/VasDolly/

VasDolly 掃碼直達

借騰訊開源 VasDolly,談談 Android 簽名和多渠道打包的原理!

部分圖片來源以及參考資料:

https://source.android.com/security/apksigning/v2

https://github.com/Tencent/VasDolly/wiki/VasDolly%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86

https://tech.meituan.com/mt-apk-packaging.html

今天在公眾號後臺回覆成長『成長』,將會得到我整理的一些學習資料,也能回覆『加群』,一起學習進步。

推薦閱讀:

借騰訊開源 VasDolly,談談 Android 簽名和多渠道打包的原理!

相關文章