android-應用簽名

應用簽名

通過應用簽名,開發者可以標識應用創作者並更新其應用,而無需創建複雜的接口和權限。在 Android 平臺上運行的每個應用都必須要有開發者的簽名。Google Play 或 Android 設備上的軟件包安裝程序會拒絕沒有獲得簽名就嘗試安裝的應用。

在 Google Play 上,應用簽名可以將 Google 對開發者的信任和開發者對自己的應用的信任聯繫在一起。這樣一來,開發者就知道自己的應用是以未經修改的形式提供給 Android 設備,並且可以對其應用的行爲負責。

在 Android 上,應用簽名是將應用放入其應用沙盒的第一步。已簽名的應用證書定義了哪個用戶 ID 與哪個應用相關聯;不同的應用要以不同的用戶 ID 運行。應用簽名可確保一個應用無法訪問任何其他應用的數據,通過明確定義的 IPC 進行訪問時除外。

當應用(APK 文件)安裝到 Android 設備上時,軟件包管理器會驗證 APK 是否已經過適當簽名(已使用 APK 中包含的證書籤名)。如果該證書(或更準確地說,證書中的公鑰)與設備上的任何其他 APK 使用的簽名密鑰一致,那麼這個新 APK 就可以選擇在清單中指定它將與其他以類似方式簽名的 APK 共用一個 UID。

應用可以由第三方(OEM、運營商、其他應用市場)簽名,也可以自行簽名。Android 提供了使用自簽名證書進行代碼簽名的功能,而開發者無需外部協助或許可即可生成自簽名證書。應用並非必須由核心機構簽名。Android 目前不對應用證書進行 CA 認證。

應用還可以在“簽名”保護級別聲明安全權限,以便只有使用同一個密鑰簽名的應用可以獲得此僅限,同時讓這些應用可以各自維持單獨的 UID 和應用沙盒。通過共用 UID 功能,多個應用可以共用一個應用沙盒,從而建立起更緊密的聯繫。在該功能中,使用同一個開發者密鑰簽名的兩個或更多應用可以在其清單中聲明共用的 UID。

APK 簽名方案

Android 支持兩種應用簽名方案,一種是基於 JAR 簽名的方案(v1 方案),另一種是 Android Nougat (Android 7.0) 中引入的 APK 簽名方案 v2(v2 方案)

爲了最大限度地提高兼容性,應同時採用 v1 和 v2 這兩種方案對應用進行簽名。與只通過 v1 方案簽名的應用相比,通過 v2 方案簽名的應用能夠更快速地安裝到 Android Nougat 及更高版本的設備上。更低版本的 Android 平臺會忽略 v2 簽名,這就需要應用包含 v1 簽名。

JAR 簽名(v1 方案)

從一開始,APK 簽名就是 Android 的一個有機部分。該方案基於簽名的 JAR。如要詳細瞭解如何使用該方案,請參閱介紹如何爲您的應用簽名的 Android Studio 文檔。

v1 簽名不保護 APK 的某些部分,例如 ZIP 元數據。APK 驗證程序需要處理大量不可信(尚未經過驗證)的數據結構,然後會捨棄不受簽名保護的數據。這會導致相當大的受攻擊面。此外,APK 驗證程序必須解壓所有已壓縮的條目,而這需要花費更多時間和內存。爲了解決這些問題,Android 7.0 中引入了 APK 簽名方案 v2。

APK 簽名方案 v2(v2 方案)

Android 7.0 中引入了 APK 簽名方案 v2(v2 方案)。該方案會對 APK 的內容進行哈希處理和簽名,然後將生成的“APK 簽名分塊”插入到 APK 中。如要詳細瞭解如何在應用中使用 v2 方案,請參閱 Android N 開發者預覽版中的 APK 簽名方案 v2

在驗證期間,v2 方案會將 APK 文件視爲 Blob,並對整個文件進行簽名檢查。對 APK 進行的任何修改(包括對 ZIP 元數據進行的修改)都會使 APK 簽名作廢。這種形式的 APK 驗證不僅速度要快得多,而且能夠發現更多種未經授權的修改。

新的簽名格式向後兼容,因此,使用這種新格式簽名的 APK 可在更低版本的 Android 設備上進行安裝(會直接忽略添加到 APK 的額外數據),但前提是這些 APK 還帶有 v1 簽名。

APK 簽名驗證過程

圖 1. APK 簽名驗證過程(新步驟以紅色顯示)

驗證程序會對照存儲在“APK 簽名分塊”中的 v2 簽名對 APK 的全文件哈希進行驗證。該哈希涵蓋除“APK 簽名分塊”(其中包含 v2 簽名)之外的所有內容。在“APK 簽名分塊”以外對 APK 進行的任何修改都會使 APK 的 v2 簽名作廢。v2 簽名被刪除的 APK 也會被拒絕,因爲 v1 簽名指明相應 APK 帶有 v2 簽名,所以 Android Nougat 及更高版本會拒絕使用 v1 簽名驗證 APK。

如需關於 APK 簽名驗證過程的詳細信息,請參閱 APK 簽名方案 v2 的“驗證”部分

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 簽名分塊

爲了保持與當前 APK 格式向後兼容,v2 及更高版本的 APK 簽名會存儲在“APK 簽名分塊”內,該分塊是爲了支持 APK 簽名方案 v2 而引入的一個新容器。在 APK 文件中,“APK 簽名分塊”位於“ZIP 中央目錄”(位於文件末尾)之前並緊鄰該部分。

該分塊包含多個“ID-值”對,所採用的封裝方式有助於更輕鬆地在 APK 中找到該分塊。APK 的 v2 簽名會存儲爲一個“ID-值”對,其中 ID 爲 0x7109871a。

格式

“APK 簽名分塊”的格式如下(所有數字字段均採用小端字節序):

  • size of block,以字節數(不含此字段)計 (uint64)
  • 帶 uint64 長度前綴的“ID-值”對序列:
    • ID (uint32)
    • value(可變長度:“ID-值”對的長度 - 4 個字節)
  • size of block,以字節數計 - 與第一個字段相同 (uint64)
  • magic APK 簽名分塊 42(16 個字節)

在解析 APK 時,首先要通過以下方法找到“ZIP 中央目錄”的起始位置:在文件末尾找到“ZIP 中央目錄結尾”記錄,然後從該記錄中讀取“中央目錄”的起始偏移量。通過 magic 值,可以快速確定“中央目錄”前方可能是“APK 簽名分塊”。然後,通過size of block 值,可以高效地找到該分塊在文件中的起始位置。

在解譯該分塊時,應忽略 ID 未知的“ID-值”對。

APK 簽名方案 v2 分塊

APK 由一個或多個簽名者/身份簽名,每個簽名者/身份均由一個簽名密鑰來表示。該信息會以“APK 簽名方案 v2 分塊”的形式存儲。對於每個簽名者,都會存儲以下信息:

  • (簽名算法、摘要、簽名)元組。摘要會存儲起來,以便將簽名驗證和 APK 內容完整性檢查拆開進行。
  • 表示簽名者身份的 X.509 證書鏈。
  • 採用鍵值對形式的其他屬性。

對於每位簽名者,都會使用收到的列表中支持的簽名來驗證 APK。簽名算法未知的簽名會被忽略。如果遇到多個支持的簽名,則由每個實現來選擇使用哪個簽名。這樣一來,以後便能夠以向後兼容的方式引入安全係數更高的簽名方法。建議的方法是驗證安全係數最高的簽名。

格式

“APK 簽名方案 v2 分塊”存儲在“APK 簽名分塊”內,ID 爲 0x7109871a

“APK 簽名方案 v2 分塊”的格式如下(所有數字值均採用小端字節序,所有帶長度前綴的字段均使用 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 個字節的鹽且尾部爲 0xbc 的 RSASSA-PSS 算法
  • 0x0102 - 採用 SHA2-512 摘要、SHA2-512 MGF1、64 個字節的鹽且尾部爲 0xbc 的 RSASSA-PSS 算法
  • 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 包含以下 4 個部分:

  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 部分(ZIP 中央目錄結尾)包含“ZIP 中央目錄”的偏移量,因此該部分的保護比較複雜。當“APK 簽名分塊”的大小發生變化(例如,添加了新簽名)時,偏移量也會隨之改變。因此,在通過“ZIP 中央目錄結尾”計算摘要時,必須將包含“ZIP 中央目錄”偏移量的字段視爲包含“APK 簽名分塊”的偏移量。

防回滾保護

攻擊者可能會試圖在支持對帶 v2 簽名的 APK 進行驗證的 Android 平臺上將帶 v2 簽名的 APK 作爲帶 v1 簽名的 APK 進行驗證。爲了防範此類攻擊,帶 v2 簽名的 APK 如果還帶 v1 簽名,其 META-INF/*.SF 文件的主要部分中必須包含 X-Android-APK-Signed 屬性。該屬性的值是一組以英文逗號分隔的 APK 簽名方案 ID(v2 方案的 ID 爲 2)。在驗證 v1 簽名時,對於此組中驗證程序首選的 APK 簽名方案(例如,v2 方案),如果 APK 沒有相應的簽名,APK 驗證程序必須要拒絕這些 APK。此項保護依賴於內容 META-INF/*.SF 文件受 v1 簽名保護這一事實。請參閱 JAR 已簽名的 APK 的驗證部分。

攻擊者可能會試圖從“APK 簽名方案 v2 分塊”中刪除安全係數較高的簽名。爲了防範此類攻擊,對 APK 進行簽名時使用的簽名算法 ID 的列表會存儲在通過各個簽名保護的 signed data 分塊中。

驗證

在 Android 7.0 中,可以根據 APK 簽名方案 v2(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 並對照 signed data 驗證 signatures 中對應的 signature。(現在可以安全地解析signed data 了。)
    3. 驗證 digests 和 signatures 中的簽名算法 ID 列表(有序列表)是否相同。(這是爲了防止刪除/添加簽名。)
    4. 使用簽名算法所用的同一種摘要算法計算 APK 內容的摘要
    5. 驗證計算出的摘要是否與 digests 中對應的 digest 相同。
    6. 驗證 certificates 中第一個 certificate 的 SubjectPublicKeyInfo 是否與 public key 相同。
  4. 如果找到了至少一個 signer,並且對於每個找到的 signer,第 3 步都取得了成功,APK 驗證將會成功。

注意:如果第 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 驗證將會失敗。

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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章