APK 簽署配置 v2

APK 簽署配置 v2 是一種整個檔案簽署配置,可偵測 APK 受保護的部分有任何變更,提高驗證速度並完整性保證

使用 APK 簽署配置 v2 進行簽署時,系統會在 APK 檔案的 ZIP 中央目錄區塊前立即插入 APK 簽署區塊。在 APK 簽署區塊中,v2 簽章和簽署者身分資訊會儲存在 APK 簽署配置 v2 區塊中。

APK 簽署前後

圖 1. 簽署前後的 APK

APK Signature Scheme v2 是在 Android 7.0 (Nougat) 中推出。如要讓 APK 可安裝在 Android 6.0 (Marshmallow) 以下版本的裝置上,則 APK 應先使用 JAR 簽署簽署,再使用 v2 配置簽署。

APK 簽署區塊

為維持與 v1 APK 格式的回溯相容性,v2 及更新版本的 APK 簽名會儲存在 APK 簽署區塊中,這是為了支援 APK 簽名配置 v2 而推出的新容器。在 APK 檔案中,APK 簽署區塊位於檔案結尾的 ZIP Central Directory 之前。

這個區塊包含 ID 值組合,並以便於在 APK 中定位區塊的方式包裝。APK 的 v2 簽章會儲存為 ID 值組,其 ID 為 0x7109871a。

格式

APK 簽署區塊的格式如下 (所有數值欄位皆為小端序):

  • size of block 位元組 (不含此欄位) (uint64)
  • uint64 長度前置 ID-value 組合的序列:
    • ID (uint32)
    • value (變長度:組合的長度 - 4 個位元組)
  • size of block 以位元組為單位,與第一個欄位 (uint64) 相同
  • magic「APK Sig Block 42」(16 個位元組)

剖析 APK 時,系統會先找到 ZIP 中央目錄的開頭 (方法是找到檔案結尾的 ZIP 中央目錄結束記錄,然後從記錄中讀取中央目錄的開頭偏移量)。magic 值可讓您快速判斷 Central Directory 前方的內容是否為 APK 簽署區塊。size of block 值隨後會有效地指向檔案中區塊的開頭。

解譯區塊時,系統應忽略 ID 值組合中不明的 ID。

APK 簽署配置 v2 區塊

APK 是由一或多位簽署者/身分所簽署,每位簽署者/身分都由簽署金鑰代表。這類資訊會以 APK 簽名配置 v2 區塊的形式儲存。系統會針對每位簽署者儲存下列資訊:

  • (簽章演算法、摘要、簽章) 元組。摘要會儲存,以便將簽名驗證與 APK 內容的完整性檢查分開。
  • 代表簽署者身分的 X.509 憑證鏈結。
  • 其他屬性做為鍵/值組合。

系統會針對每個簽署者,使用提供清單中的支援簽署來驗證 APK。系統會忽略簽名演算法不明的簽名。遇到多個支援的簽名時,每個實作項目都必須自行選擇要使用的簽名。這可讓您日後以回溯相容的方式,導入更強大的簽署方法。建議的方法是驗證最強的簽名。

格式

APK 簽署配置 v2 區塊會儲存在 APK 簽署區塊內,位於 ID 0x7109871a 下方。

APK 簽名配置 v2 區塊的格式如下 (所有數字值均為 Little-Endian,且開頭為 uint32 的所有欄位長度都會使用 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)
      • 長度前置 signature 超過 signed data
    • 長度前置 public key (SubjectPublicKeyInfo,ASN.1 DER 格式)

簽章演算法 ID

  • 0x0101 - RSASSA-PSS,附有 SHA2-256 摘要、SHA2-256 MGF1、32 個位元組的鹽值,尾節:0xbc
  • 0x0102—RSASSA-PSS,附有 SHA2-512 摘要、SHA2-512 MGF1、64 個位元組的鹽值, 尾節: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
  • 動態搜尋廣告: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 (220 位元組) 區塊。每個區段中最後一個區塊可能會較短。每個區塊的摘要會根據位元組 0xa5、區塊的位元組長度 (位元組由小到大 uint32) 和區塊內容的連結串連來計算。頂層摘要會根據位元組 0x5a、區塊數量 (以低位元組為優先的 uint32),以及區塊在 APK 中顯示的順序,串連區塊摘要後計算而得。摘要會以區塊方式計算,藉此透過並行運算加快運算速度。

APK 摘要

圖 3. APK 摘要

第 4 節 (Central Directory 的 ZIP 結尾) 受到包含 ZIP Central Directory 偏移的部分而言變得複雜。當 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 簽署配置的 APK 簽署配置皆沒有簽章 (例如 v2 配置) 的 APK。這項防護機制仰賴 META-INF/*.SF 檔案的內容受到 v1 簽章保護的事實。請參閱「JAR 簽署 APK 驗證」一節。

攻擊者可以嘗試從 APK 簽名配置 v2 區塊中移除更強的簽名。為減輕這類攻擊,系統會將 APK 簽署所用的簽章演算法 ID 清單儲存在 signed data 區塊中,並由各個簽章保護。

驗證

在 Android 7.0 以上版本中,可根據 APK 簽名配置 v2+ 或 JAR 簽署 (v1 配置) 驗證 APK。較舊的平台會忽略第 2 版簽名,只驗證第 1 版簽名。

APK 簽章驗證程序

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

APK Signature Scheme v2 驗證

  1. 找出 APK 簽署區塊,並確認:
    1. APK 簽署區塊的兩個大小欄位包含相同的值。
    2. ZIP Central Directory 後面緊接中央目錄記錄的 ZIP 結尾。
    3. 在 ZIP End of Central Directory 後面沒有其他資料。
  2. 在 APK 簽署區塊中找出第一個 APK Signature Scheme v2 區塊。如果有 v2 區塊,請繼續執行步驟 3。否則,請改回使用 v1 配置驗證 APK。
  3. 針對 APK 簽名配置 v2 區塊中的每個 signer
    1. signatures 中選擇支援的最高級 signature algorithm ID。強度排序取決於各實作/平台版本。
    2. 使用 public key 驗證 signaturessigned data 的對應 signature。(現在可以安全地剖析 signed data)。
    3. 請確認 digestssignatures 中的簽章演算法 ID 排序清單相同。(這可避免簽章遭到剝離/新增)。
    4. 計算 APK 內容的摘要,使用與簽署演算法使用的摘要演算法相同的摘要演算法。
    5. 確認計算的摘要與 digests 中的對應 digest 相同。
    6. 確認 certificates 個第一個 certificate 的 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 項目的內容。