1. 程式人生 > >APK簽名機制之——V2簽名機制詳解

APK簽名機制之——V2簽名機制詳解

通過前一篇Apk簽名機制之——JAR簽名機制詳解的分析我們知道,JAR簽名需要對apk內所有檔案進行hash校驗,當資源較多時簽名驗證速度較慢。為了加快驗證速度並加強完整性保證,Andorid在7.0引入一種全檔案簽名方案V2。下面來看V2方案的具體設計原理。

1. V2簽名設計思想

1.1 ZIP檔案結構

這裡寫圖片描述

zip檔案分為3部分:

  1. 資料區

    此區塊包含了zip中所有檔案的記錄,是一個列表,每條記錄包含:檔名、壓縮前後size、壓縮後的資料等;

  2. 中央目錄

    存放目錄資訊,也是一個列表,每條記錄包含:檔名、壓縮前後size、本地檔案頭的起始偏移量等。通過本地檔案頭的起始偏移量即可找到壓縮後的資料;

  3. 中央目錄結尾記錄

    標識中央目錄結尾,包含:中央目錄條目數、size、起始偏移量、zip檔案註釋內容等。

通過中央目錄起始偏移量和size即可定位到中央目錄,再遍歷中央目錄條目,根據本地檔案頭的起始偏移量即可在資料區中找到相應的壓縮資料。

1.2 V2簽名原理

Apk簽名機制之——JAR簽名機制詳解中我們已經知道,JAR簽名是在apk檔案中新增META-INF目錄,即需要修改資料區中央目錄,因為新增檔案後會導致中央目錄大小和偏移量發生變化,還需要修改中央目錄結尾記錄。V2方案為加強資料完整性保證,不在資料區中央目錄中插入資料,選擇在 資料區中央目錄 之間插入一個APK簽名分塊

,從而保證了原始zip(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簽名分塊

的位置,最後通過ID(0x7109871a)定位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 樹

  1. 拆分chunk

    將每個部分拆分成多個大小為 1 MB大小的chunk,最後一個chunk可能小於1M。之所以分塊,是為了可以通過平行計算摘要以加快計算速度;

  2. 計算chunk摘要

    位元組 0xa5 + 塊的長度(位元組數) + 塊的內容 進行計算;

  3. 計算整體摘要

    位元組 0x5a + chunk數 + 塊的摘要的連線(按塊在 APK 中的順序)進行計算。

    這裡要注意的是中央目錄結尾記錄中包含了中央目錄的起始偏移量,插入APK簽名分塊後,中央目錄的起始偏移量將發生變化。故在校驗簽名計算摘要時,需要把中央目錄的起始偏移量當作APK簽名分塊的起始偏移量。

1.6 v2 驗證過程

  1. 找到APK 簽名分塊並驗證以下內容:
    a. APK 簽名分塊的兩個大小欄位包含相同的值。
    b. ZIP 中央目錄結尾緊跟在ZIP 中央目錄記錄後面。
    c. ZIP 中央目錄結尾之後沒有任何資料。
  2. 找到APK 簽名分塊中的第一個APK 簽名方案 v2 分塊。如果 v2 分塊存在,則繼續執行第 3 步。否則,回退至使用 v1 方案驗證 APK。
  3. 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 相同。
  4. 如果找到了至少一個 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類:

  1. 反編譯APK後修改渠道值,再重新打包

    這種方案實際上是重新簽名,因有反編譯、重新打包、簽名的過程,速度相對後兩種方案較慢;

  2. 將渠道資訊以檔案形式寫入META-INF目錄中

    因為META-INF目錄是用來存放簽名的,其本身無法加入簽名校驗中,在META-INF目錄中新增檔案不會破壞原有簽名。此方案需同時修改zip資料區中央目錄中央目錄結尾記錄

  3. 將渠道資訊寫到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簽名機制都有了大致的瞭解,有興趣的同學可以閱讀簽名和校驗的原始碼進一步分析。