APK 簽名方案 v2

APK Signature Scheme 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 簽名方案 v2。在 APK 文件中,APK 簽名塊位於 ZIP 中央目錄之前,該目錄位於文件末尾。

該塊包含以某種方式包裝的 ID-值對,以便更容易在 APK 中找到該塊。 APK 的 v2 簽名存儲為 ID 值對,ID 為 0x7109871a。

格式

APK Signing Block 的格式如下(所有數字字段都是 little-endian):

  • size of block以字節為單位)(不包括此字段)(uint64)
  • uint64-length-prefixed ID-value 對的序列:
    • ID (uint32)
    • value (可變長度:對的長度 - 4 個字節)
  • size of block以字節為單位)——與第一個字段(uint64)相同
  • magic “APK Sig Block 42”(16 字節)

APK 通過首先找到 ZIP Central Directory 的開頭來解析(通過在文件末尾找到 Central Directory 的 ZIP End of Central Directory 記錄,然後從記錄中讀取 Central Directory 的起始偏移量)。 magic值提供了一種快速方法來確定中央目錄之前的內容可能是 APK 簽名塊。然後size of block有效地指向文件中塊的開始。

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

APK 簽名方案 v2 塊

APK 由一個或多個簽名者/身份簽名,每個簽名者/身份都由一個簽名密鑰表示。此信息存儲為 APK 簽名方案 v2 塊。對於每個簽名者,將存儲以下信息:

  • (簽名算法,摘要,簽名)元組。存儲摘要以將簽名驗證與 APK 內容的完整性檢查分離。
  • 代表簽名者身份的 X.509 證書鏈。
  • 作為鍵值對的附加屬性。

對於每個簽名者,APK 會使用提供的列表中支持的簽名進行驗證。具有未知簽名算法的簽名將被忽略。當遇到多個支持的簽名時,由每個實現選擇使用哪個簽名。這使得將來能夠以向後兼容的方式引入更強大的簽名方法。建議的方法是驗證最強的簽名。

格式

APK 簽名方案 v2 塊存儲在 ID 0x7109871a下的 APK 簽名塊中。

APK Signature Scheme v2 Block的格式如下(所有數值均為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
  • 動態搜索廣告: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的連接、塊的數量(小端 uint32)以及塊的摘要的連接(按照塊在 APK 中出現的順序)計算得出的。摘要以分塊方式計算,以通過並行化加速計算。

APK摘要

圖 3. APK 摘要

第 4 節(中央目錄的 ZIP 結尾)的保護因包含 ZIP 中央目錄偏移的部分而變得複雜。當 APK 簽名塊的大小發生變化時,偏移量也會發生變化,例如,添加新簽名時。因此,在計算中央目錄 ZIP 端的摘要時,必須將包含 ZIP 中央目錄偏移量的字段視為包含 APK 簽名塊的偏移量。

回滾保護

攻擊者可能會嘗試在支持驗證 v2 簽名 APK 的 Android 平台上將 v2 簽名 APK 驗證為 v1 簽名 APK。為了緩解這種攻擊,v2 簽名的 APK 和 v1 簽名的 APK 必須在其 META-INF/*.SF 文件的主要部分包含 X-Android-APK-Signed 屬性。該屬性的值是一組以逗號分隔的 APK 簽名方案 ID(此方案的 ID 為 2)。在驗證 v1 簽名時,APK 驗證者需要拒絕沒有簽名的 APK,該 APK 簽名方案是驗證者從該集合中首選的 APK 簽名方案(例如,v2 方案)。這種保護依賴於內容 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 Central Directory 後緊跟 ZIP End of Central Directory 記錄。
    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. 驗證第一個certificatecertificates是否與public key相同。
  4. 如果至少找到一個signer並且步驟 3 對每個找到的signer都成功,則驗證成功。

注意:如果第 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. META-INF/MANIFEST.MF 為每個完整性保護的 JAR 條目包含一個相應命名的部分,其中包含條目的未壓縮內容的摘要。所有這些摘要都經過驗證。
  5. 如果 APK 包含未在 MANIFEST.MF 中列出且不屬於 JAR 簽名的 JAR 條目,則 APK 驗證失敗。

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