APK 簽署配置 v2

APK 簽章方案 v2 是一種全文件簽章方案,可透過偵測 APK 受保護部分的任何變更來提高驗證速度並加強完整性保證

使用 APK 簽章方案 v2 進行簽章會將APK 簽章區塊插入 APK 檔案中緊鄰 ZIP 中央目錄部分之前。在 APK 簽章區塊內,v2 簽章和簽章者身分資訊儲存在APK 簽章方案 v2 區塊中。

APK簽名前後

圖 1.簽名前後的 APK

APK 簽章方案 v2 是在 Android 7.0 (Nougat) 中引入的。若要讓 APK 可安裝在 Android 6.0 (Marshmallow) 及更早版本的裝置上,應先使用JAR 簽章對 APK 進行簽名,然後再使用 v2 方案進行簽章。

APK簽名區塊

為了保持與 v1 APK 格式的向後相容性,v2 和更新的 APK 簽章儲存在 APK 簽章區塊內,APK 簽章區塊是為支援 APK 簽章方案 v2 而引進的新容器。在 APK 檔案中,APK 簽章區塊位於緊鄰 ZIP 中央目錄之前,ZIP 中央目錄位於檔案末端。

該區塊包含以某種方式包裝的 ID 值對,以便更輕鬆地在 APK 中定位該區塊。 APK 的 v2 簽章儲存為 ID 值對,ID 為 0x7109871a。

格式

APK簽章區塊的格式如下(所有數字欄位均為小端):

  • size of block以位元組為單位)(不包括該欄位)(uint64)
  • 以 uint64 長度為前綴的 ID 值對的序列:
    • ID (uint32)
    • value (可變長度:該對的長度 - 4 個位元組)
  • size of block (以位元組為單位)—與第一個欄位 (uint64) 相同
  • magic 「APK Sig Block 42」(16 位元組)

APK 的解析方法是先尋找 ZIP 中央目錄的開頭(透過在檔案末端尋找 ZIP 中央目錄結束記錄,然後從該記錄讀取中央目錄的起始偏移量)。 magic值提供了一種快速方法來確定中央目錄之前的內容可能是 APK 簽章區塊。然後, size of block有效地指向檔案中區塊的開頭。

解釋區塊時應忽略具有未知 ID 的 ID 值對。

APK簽章方案v2塊

APK 由一個或多個簽署者/身分簽名,每個簽署者/身分都由一個簽名金鑰表示。此資訊儲存為 APK 簽章方案 v2 區塊。對於每個簽名者,都會儲存以下資訊:

  • (簽名演算法、摘要、簽名)元組。儲存摘要是為了將簽名驗證與 APK 內容的完整性檢查分開。
  • 代表簽署者身分的 X.509 憑證鏈。
  • 作為鍵值對的附加屬性。

對於每個簽署者,都會使用提供的清單中支援的簽章來驗證 APK。簽名演算法未知的簽名將被忽略。當遇到多個受支援的簽章時,由每個實作選擇使用哪個簽章。這使得將來能夠以向後相容的方式引入更強大的簽名方法。建議的方法是驗證最強的簽名。

格式

APK 簽章方案 v2 區塊儲存在 ID 0x7109871a下的 APK 簽章區塊內。

APK簽章方案v2塊的格式如下(所有數值均為little-endian,所有長度前綴欄位使用uint32作為長度):

  • 長度前綴signer的長度前綴序列:
    • 長度前綴的signed data
      • 長度前綴digests的長度前綴序列:
      • X.509 certificates的長度前綴序列:
        • 長度前綴的 X.509 certificate (ASN.1 DER 形式)
      • 長度前綴附加additional attributes的長度前綴序列:
        • ID (uint32)
        • value (可變長度:附加屬性的長度 - 4 個位元組)
    • 長度前綴signatures的長度前綴序列:
      • signature algorithm ID (uint32)
      • signed data進行長度前綴signature
    • 長度前綴的public key (SubjectPublicKeyInfo,ASN.1 DER 形式)

簽章演算法ID

  • 0x0101—具有 SHA2-256 摘要、SHA2-256 MGF1、32 位元組鹽的 RSASSA-PSS,尾部:0xbc
  • 0x0102—具有 SHA2-512 摘要、SHA2-512 MGF1、64 位元組鹽的 RSASSA-PSS,尾部:0xbc
  • 0x0103—具有 SHA2-256 摘要的 RSASSA-PKCS1-v1_5。這適用於需要確定性簽名的建置系統。
  • 0x0104—具有 SHA2-512 摘要的 RSASSA-PKCS1-v1_5。這適用於需要確定性簽名的建置系統。
  • 0x0201—具有 SHA2-256 摘要的 ECDSA
  • 0x0202—具有 SHA2-512 摘要的 ECDSA
  • 0x0301—具有 SHA2-256 摘要的 DSA

Android平台均支援上述所有簽章演算法。簽名工具可能支援演算法的子集。

支援的密鑰大小和 EC 曲線:

  • RSA:1024、2048、4096、8192、16384
  • EC:NIST P-256、P-384、P-521
  • DSA:1024、2048、3072

受完整性保護的內容

為了保護 APK 內容,APK 由四個部分組成:

  1. ZIP 條目的內容(從偏移量 0 到 APK 簽名區塊的開頭)
  2. APK簽名區塊
  3. ZIP 中央目錄
  4. ZIP 中央目錄末尾

簽名後的 APK 部分

圖 2.簽名後的 APK 部分

APK 簽章方案 v2 保護第 1、3、4 部分以及第 2 部分所包含的 APK 簽章方案 v2 區塊的signed data區塊的完整性。

第 1、3 和 4 節的完整性受到儲存在signed data區塊中的一個或多個內容摘要的保護,而這些內容又受到一個或多個簽章的保護。

第 1、3 和 4 部分的摘要計算如下,類似兩層Merkle 樹。每個部分被分割成連續的 1 MB(2 20位元組)區塊。每個部分的最後一個區塊可能會更短。每個區塊的摘要是透過位元組0xa5 、區塊的位元組長度 (little-endian uint32) 和區塊的內容的串聯來計算的。頂級摘要是根據位元組0x5a的串聯、塊的數量 (little-endian uint32) 以及塊的摘要的串聯(按照塊在 APK 中出現的順序)計算的。摘要以分塊方式計算,以便透過並行化來加速計算。

APK摘要

圖 3. APK 摘要

第 4 部分(ZIP 中央目錄末尾)的保護因包含 ZIP 中央目錄偏移量的部分而變得複雜。當 APK 簽章區塊的大小改變時,例如新增簽章時,偏移量也會改變。因此,當透過 ZIP 中央目錄末尾計算摘要時,包含 ZIP 中央目錄偏移量的欄位必須被視為包含 APK 簽章區塊的偏移量。

復原保護

攻擊者可能會嘗試在支援驗證 v2 簽章 APK 的 Android 平台上將 v2 簽章 APK 驗證為 v1 簽章 APK。為了減輕這種攻擊,同時進行 v1 簽署的 v2 簽章 APK 必須在其 META-INF/*.SF 檔案的主要部分中包含 X-Android-APK-Signed 屬性。此屬性的值是一組以逗號分隔的 APK 簽章方案 ID(此方案的 ID 為 2)。當驗證 v1 簽章時,APK 驗證者需要拒絕不具有驗證者從該集合中首選的 APK 簽章方案(例如,v2 方案)簽署的 APK。此保護依賴 META-INF/*.SF 檔案內容受 v1 簽章保護這一事實。請參閱有關JAR 簽章 APK 驗證的部分。

攻擊者可能會嘗試從 APK 簽章方案 v2 區塊中剝離更強的簽章。為了減輕這種攻擊,用於簽署 APK 的簽章演算法 ID 清單儲存在受每個簽章保護的signed data區塊中。

確認

在 Android 7.0 及更高版本中,可以根據 APK 簽章方案 v2+ 或 JAR 簽章(v1 方案)來驗證 APK。較舊的平台會忽略 v2 簽名,僅驗證 v1 簽名。

APK簽名驗證流程

圖 4. APK 簽章驗證流程(紅色為新步驟)

APK簽章方案v2驗證

  1. 找到 APK 簽名區塊並驗證:
    1. APK 簽名區塊的兩個大小欄位包含相同的值。
    2. ZIP 中央目錄後面緊跟著 ZIP 中央目錄結束記錄。
    3. ZIP 中央目錄末尾後面沒有更多資料。
  2. 找到 APK 簽章區塊內的第一個 APK 簽章方案 v2 區塊。如果 v2 區塊存在,則繼續執行步驟 3。否則,回退到使用 v1 方案驗證 APK。
  3. 對於 APK 簽章方案 v2 區塊中的每個signer
    1. signatures中選擇支援的最強signature algorithm ID 。強度排序取決於每個實作/平台版本。
    2. 使用public key根據signatures signed data驗證相應的signature 。 (現在可以安全地解析signed data 。)
    3. 驗證digestssignatures中的簽名演算法 ID 的有序列表是否相同。 (這是為了防止簽名剝離/添加。)
    4. 使用與簽章演算法使用的摘要演算法相同的摘要演算法來計算 APK 內容的摘要
    5. 驗證計算出的摘要與digests中對應的digest相同。
    6. 驗證certificate中第一個certificates的SubjectPublicKeyInfo 是否與public key相同。
  4. 如果至少找到一名signer ,並且每個找到的signer的步驟 3 均成功,則驗證成功。

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

JAR 簽署的 APK 驗證(v1 方案)

JAR 簽署的 APK 是標準簽署的 JAR ,它必須完全包含 META-INF/MANIFEST.MF 中列出的條目,並且所有條目必須由同一組簽署者簽署。其完整性驗證如下:

  1. 每個簽署者都由 META-INF/<signer>.SF 和 META-INF/<signer>.(RSA|DSA|EC) JAR 條目表示。
  2. <signer>.(RSA|DSA|EC) 是具有 SignedData 結構的 PKCS #7 CMS ContentInfo ,其簽章透過 <signer>.SF 檔案進行驗證。
  3. <signer>.SF 檔案包含 META-INF/MANIFEST.MF 的整個檔案摘要以及 META-INF/MANIFEST.MF 每個部分的摘要。驗證 MANIFEST.MF 的整個文件摘要。如果失敗,則會驗證每個 MANIFEST.MF 部分的摘要。
  4. 對於每個受完整性保護的 JAR 條目,META-INF/MANIFEST.MF 包含一個相應命名的部分,其中包含該條目未壓縮內容的摘要。所有這些摘要都經過驗證。
  5. 如果 APK 包含未在 MANIFEST.MF 中列出且不是 JAR 簽章一部分的 JAR 條目,則 APK 驗證失敗。

因此,保護鏈是 <signer>.(RSA|DSA|EC) -> <signer>.SF -> MANIFEST.MF -> 每個受完整性保護的 JAR 條目的內容。