APK簽名機制之——V2簽名機制詳解
通過前一篇Apk簽名機制之——JAR簽名機制詳解的分析我們知道,JAR簽名需要對apk內所有檔案進行hash校驗,當資源較多時簽名驗證速度較慢。為了加快驗證速度並加強完整性保證,Andorid在7.0引入一種全檔案簽名方案V2。下面來看V2方案的具體設計原理。
1. V2簽名設計思想
1.1 ZIP檔案結構
zip檔案分為3部分:
資料區
此區塊包含了zip中所有檔案的記錄,是一個列表,每條記錄包含:檔名、壓縮前後size、壓縮後的資料等;
中央目錄
存放目錄資訊,也是一個列表,每條記錄包含:檔名、壓縮前後size、本地檔案頭的起始偏移量等。通過本地檔案頭的起始偏移量即可找到壓縮後的資料;
中央目錄結尾記錄
標識中央目錄結尾,包含:中央目錄條目數、size、起始偏移量、zip檔案註釋內容等。
通過中央目錄起始偏移量和size即可定位到中央目錄,再遍歷中央目錄條目,根據本地檔案頭的起始偏移量即可在資料區中找到相應的壓縮資料。
1.2 V2簽名原理
在Apk簽名機制之——JAR簽名機制詳解中我們已經知道,JAR簽名是在apk檔案中新增META-INF目錄,即需要修改資料區
、中央目錄
,因為新增檔案後會導致中央目錄大小和偏移量發生變化,還需要修改中央目錄結尾記錄
。V2方案為加強資料完整性保證,不在資料區
和中央目錄
中插入資料,選擇在 資料區
和中央目錄
之間插入一個APK簽名分塊
v2 簽名塊負責保護第 1、3、4 部分的完整性,以及第 2 部分包含的APK 簽名方案 v2分塊
中的signed data
分塊的完整性。
1.3 如何定位 APK簽名方案V2分塊?
APK簽名分塊包含了4部分:分塊長度、ID-VALUE序列、分塊長度、固定magic值。其中APK 簽名方案 v2分塊
存放在ID為0x7109871a的鍵值對中。在進行簽名校驗時,先找到zip中央目錄結尾記錄
,從該記錄中找到中央目錄起始偏移量
,再通過magic值即可確定前方可能是APK簽名分塊
,再通過前後兩個分塊長度欄位,即可確定APK簽名分塊
APK 簽名方案 v2分塊
位置。
1.4 APK簽名方案V2分塊 格式
APK 簽名方案 v2分塊
是一個簽名序列,說明可以使用多個簽名者對同一個APK進行簽名。每個簽名信息中均包含了三個部分的內容:
帶長度字首的
signed data
其中包含了通過一系列演算法計算的摘要列表、證書資訊,以及extra資訊(可選);
帶長度字首的signatures序列
通過一系列演算法對
signed data
的簽名列表。簽名時使用了多個簽名演算法,在簽名校驗時會是選擇系統支援的安全係數最高的簽名進行校驗;證書公鑰
1.5 摘要計算過程
前面說了v2 簽名塊負責保護第 1、3、4 部分的完整性,以及第 2 部分包含的APK 簽名方案 v2分塊
中的 signed data
分塊的完整性。第1、3、4部分的完整性是通過內容摘要來保護的,這些摘要儲存在signed data
分塊中,而signed data
分塊的完整性是通過簽名來保證的。下面來看摘要的計算過程:
第 1、3 和 4 部分的摘要採用以下計算方式,類似於兩級 Merkle 樹。
拆分chunk
將每個部分拆分成多個大小為 1 MB大小的chunk,最後一個chunk可能小於1M。之所以分塊,是為了可以通過平行計算摘要以加快計算速度;
計算chunk摘要
位元組
0xa5
+ 塊的長度(位元組數) + 塊的內容 進行計算;計算整體摘要
位元組
0x5a
+ chunk數 + 塊的摘要的連線(按塊在 APK 中的順序)進行計算。這裡要注意的是:
中央目錄結尾記錄
中包含了中央目錄
的起始偏移量,插入APK簽名分塊
後,中央目錄
的起始偏移量將發生變化。故在校驗簽名計算摘要時,需要把中央目錄
的起始偏移量當作APK簽名分塊
的起始偏移量。
1.6 v2 驗證過程
- 找到
APK 簽名分塊
並驗證以下內容:
a.APK 簽名分塊
的兩個大小欄位包含相同的值。
b.ZIP 中央目錄結尾
緊跟在ZIP 中央目錄
記錄後面。
c.ZIP 中央目錄結尾
之後沒有任何資料。 - 找到
APK 簽名分塊
中的第一個APK 簽名方案 v2 分塊
。如果 v2 分塊存在,則繼續執行第 3 步。否則,回退至使用 v1 方案驗證 APK。 - 對
APK 簽名方案 v2 分塊
中的每個 signer 執行以下操作:
a. 從 signatures 中選擇安全係數最高的受支援 signature algorithm ID。安全係數排序取決於各個實現/平臺版本。
b. 使用 public key 並對照signed data
驗證 signatures 中對應的 signature。(現在可以安全地解析signed data
了。)
c. 驗證 digests 和 signatures 中的簽名演算法 ID 列表(有序列表)是否相同。(這是為了防止刪除/添加簽名。)
d. 使用簽名演算法所用的同一種摘要演算法計算 APK 內容的摘要。
e. 驗證計算出的摘要是否與 digests 中對應的 digest 相同。
f. 驗證 certificates 中第一個 certificate 的 SubjectPublicKeyInfo 是否與 public key 相同。 - 如果找到了至少一個 signer,並且對於每個找到的 signer,第 3 步都取得了成功,APK 驗證將會成功。
2. 相容機制&防回滾機制
2.1 相容機制
因為V2簽名機制是在Android 7.0中引入的,為了使APK可在Android 7.0以下版本中安裝,應先用JAR簽名對APK進行簽名,再用V2方案進行簽名。要注意順序一定是先JAR簽名再V2簽名,因為JAR簽名需要修改zip資料區
和中央目錄
的內容,先使用V2簽名再JAR簽名會破壞V2簽名的完整性。
實際上我們在編譯APK時並不需要關心這個過程,在Android Plugin for Gradle 2.2中,gradle預設會同時使用JAR簽名和V2方案對APK進行簽名,如果想要關閉JAR簽名或V2簽名,可以在build.gradle中進行配置:
android {
...
defaultConfig { ... }
signingConfigs {
release {
...
// v1SigningEnabled false
v2SigningEnabled false
}
}
}
在 Android 7.0 中,會優先以 v2方案驗證 APK,在Android 7.0以下版本中,系統會忽略 v2 簽名,僅驗證 v1 簽名。Android 7.0+的校驗過程如下:
2.2 防回滾機制
因為在經過V2簽名的APK中同時帶有JAR簽名,攻擊者可能將APK的V2簽名刪除,使得Android系統只校驗JAR簽名。為防範此類攻擊,V2方案規定:
V2簽名的APK如果還帶JAR簽名,其 META-INF/.SF 檔案的首部中必須包含 X-Android-APK-Signed 屬性。該屬性的值是一組以英文逗號分隔的 APK 簽名方案 ID(v2 方案的 ID 為 2)。在驗證 v1 簽名時,對於此組中驗證程式首選的 APK 簽名方案(例如,v2 方案),如果 APK 沒有相應的簽名,APK 驗證程式必須要拒絕這些 APK。此項保護依賴於內容 META-INF/.SF 檔案受 v1 簽名保護這一事實。
攻擊者還可能試圖刪除APK 簽名方案 v2 分塊
中安全係數較高的簽名,從而使系統驗證安全係數較低的簽名。為防範此類攻擊:
對 APK 進行簽名時使用的簽名演算法 ID 的列表會儲存在通過各個簽名保護的
signed data
分塊中。
3. 總結
- JAR簽名是針對ZIP檔案所有檔案依次進行簽名;
- V2方案是針對APK整體檔案進行簽名
JAR簽名的劣勢
- 需對所有檔案進行hash校驗,速度較慢;
- 只保證了APK內各檔案的完整性,APK(zip包)其它內容的完整性未保證。
V2簽名的優勢
只需進行一次hash校驗,速度快;
不需要計算所有檔案的摘要,以分塊形式進行hash,支援平行計算。
除保證了APK內各檔案的完整性,APK(zip包)中
資料區
、中央目錄
和中央目錄結尾記錄
的完整性均得到了保證。zip檔案的這三個區塊均有擴充套件欄位,JAR簽名因為只校驗檔案hash,這部分的完整性未保證。
4. 回顧
簽名校驗的機制是什麼?具體校驗的是什麼內容?
APK簽名是為了保證APK的完整性和來源的真實性,分為JAR簽名和V2簽名兩種方案。核心思想均是計算APK內容的hash,再使用簽名演算法對hash進行簽名。校驗時通過簽名者公鑰解密簽名,再與校驗者計算的APK內容hash進行比對,一致則校驗通過。
申請第三方SDK(如微信支付)時填入的SAH1值是什麼?
簽名證書的指紋,在申請第三方SDK時,需填入APK包名和證書指紋,SDK開發者後臺會根據這兩個值生成一個key。第三方SDK在初始化時,會從系統中獲取當前APK的包名、簽名證書指紋以及key,然後將此指紋上傳到其伺服器,然後校驗包名、簽名證書指紋是否與此key繫結,校驗通過後才進行授權。
目前眾多的快速批量打包方案又是如何繞過簽名檢驗的?
在V2方案出現之前,快速批量打包方案有3類:
反編譯APK後修改渠道值,再重新打包
這種方案實際上是重新簽名,因有反編譯、重新打包、簽名的過程,速度相對後兩種方案較慢;
將渠道資訊以檔案形式寫入META-INF目錄中
因為META-INF目錄是用來存放簽名的,其本身無法加入簽名校驗中,在META-INF目錄中新增檔案不會破壞原有簽名。此方案需同時修改zip
資料區
、中央目錄
和中央目錄結尾記錄
;將渠道資訊寫到zip
中央目錄結尾記錄
的comment欄位中通過前面分析zip檔案結構,可以發現
中央目錄結尾記錄
最後註釋
欄位,這部分內容在JAR簽名方案中同樣不在簽名校驗範圍中,故添加註釋也不會破壞原有簽名。此方案只需修改中央目錄結尾記錄
;
在V2方案出現之後,因同時保證了資料區
、中央目錄
和中央目錄結尾記錄
的完整性,故方案2、3均不適用了。那是不是就沒有快速批量打包的可能了呢?當然不是,可以從APK簽名分塊
中著手。再回過頭來看一下APK簽名分塊
的結構:
- size of block,以位元組數(不含此欄位)計 (uint64)
- 帶 uint64 長度字首的“ID-值”對序列:
- ID (uint32)
- value(可變長度:“ID-值”對的長度 - 4 個位元組)
- size of block,以位元組數計 - 與第一個欄位相同 (uint64)
- magic“APK 簽名分塊 42”(16 個位元組)
APK簽名分塊
中有一個ID-VALUE序列, 簽名信息(APK 簽名方案 v2 分塊
)只儲存在ID 為 0x7109871a的ID-VALUE中,通過分析簽名校驗原始碼可以發現,其它ID-VALUE資料是未被解析的,也就是說除APK 簽名方案 v2 分塊
外,其餘ID-VALUE是不影響簽名校驗的。故可以定義一個新的ID-VALUE,將渠道資訊寫入APK簽名分塊
中。因為V2方案只保證了第1、3、4部分和第 2 部分(APK簽名分塊
)包含的APK 簽名方案 v2分塊
中的 signed data
分塊的完整性。新寫入的ID-VALUE不受保護,所以此方案可行。實際上美團新一代渠道包生成工具Walle就是以這個方案實現的。
好了,到這裡APK簽名機制的全部內部就分析完了,相信大家看完這三篇文章之後,對JAR簽名和V2簽名機制都有了大致的瞭解,有興趣的同學可以閱讀簽名和校驗的原始碼進一步分析。