Android APK 簽名、打包筆記

 我們知道,一款Android 要發佈的話,必須經過簽名,Android目前支持的簽名方式包括三種:

爲了最大限度地提高兼容性,請按照 v1、v2、v3 的先後順序採用所有方案對應用進行簽名。與只通過 v1 方案簽名的應用相比,還通過 v2+ 方案簽名的應用能夠更快速地安裝到 Android 7.0 及更高版本的設備上。更低版本的 Android 平臺會忽略 v2+ 簽名,這就需要應用包含 v1 簽名。

v1簽名方案

    在v1方案中,簽名只保護apk中的元數據,也就是單個文件。apk其實就是一個zip文件,我們將打包簽名好的apk文件,用解壓縮文件解壓,就可以看到一個名稱爲META-INF的文件夾裏面。

在META-INF文件夾中,存在3個文件,MANIFEST.MF , CERT.SF,CERT.RSA。這寫就是v1版本的apk在安裝時候,進行簽名校驗 很重要的問題。

MANIFEST.MF

  這個文件中保存着apk解壓之後,所有文件(非文件夾)的信息,看下面的一段內容:

Manifest-Version: 1.0
Built-By: Generated-by-ADT
Created-By: Android Gradle 3.2.0

Name: AndroidManifest.xml
SHA1-Digest: g+psNXzyZSKU8DMfQr8FdR0KjXI=

Name: LICENSES
SHA1-Digest: +7g2kf8cIIZknpabOMYpXi8vrK8=

Name: META-INF/android.arch.core_runtime.version
SHA1-Digest: OxxKFJcpzAROGjnfMbNijNv1+JU=

Name: META-INF/android.arch.lifecycle_extensions.version
SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw=

在每個文件塊中,其中 name就是解壓之後,文件的名稱(包含路徑),而SHA1-Digets就是對文件的內容提取摘要之後 進行Base64之後生成的字符串內容。

CERT.SF

  先看一段CERT.SF的內容:

Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA1-Digest-Manifest: 00yHda1FOBSkUAwjlTfipwkY+0k=
X-Android-APK-Signed: 2

Name: AndroidManifest.xml
SHA1-Digest: BNl/B2KAFMNJ1keEnqQ5+vkw8mY=

Name: LICENSES
SHA1-Digest: 9irOepAoIoOn8huusmI9KxjOT6c=

Name: META-INF/android.arch.core_runtime.version
SHA1-Digest: gfKdTxzy15iV0z7ZogHp7+qdek8=

SHA1-Digest-Manifest :是對整個MANIFEST.MF使用 SHA1算法之後,生成的Base64的編碼的字符串。

Name:也是對應解壓之後的文件的名稱

SHA1-Digest:是對MANIFEST.MF中對應名稱(Name)做SHA1之後生成再生成的Base64編碼的字符串

CERT.RSA

這裏會把之前生成的CERT.SF文件,用我們私鑰計算出簽名,然後將簽名以及包含公鑰信息的數字證書一同寫入CERT.RSA中保存。

在mac電腦中,我們可以在終端中使用以下命令查看CERT.SF的內容:

openssl pkcs7 -inform DER -in CERT.RSA -text -noout -print_certs

其中CERT.RSA 表示的是文件CERT.RSA ,如果不是在當前文件夾中,應該填寫CERT.RSA 的相對路徑或者絕對路徑。

可以用一張圖來解釋簽名的過程:

安裝校驗過程。

    我們從破解 sign.apk的過程來講解校驗的過程。首先由於v1是針對單一文件進行提取摘要的方式進行校驗。

1.假如,我們破壞或者修改了apk中的某一個文件,那麼我們必須修改MANIFEST.MF中的對應文件的摘要值,才能通過sdk對MANIFEST.MF的校驗。

2.就算我們修改某個文件之後,並且修改了對應的MANIFEST.MF裏面的摘要之後,由於CERT.SF中保存的是未修改之前的MANIFEST.MF中每個條目的SHA1之後的Base64編碼值,因此前後比對,不一樣。第二步對CERT.SF的校驗,也不會通過。只有我們在修改MANIFEST.MF 摘要的同時,也修改CERT.SF對應條目的摘要值,才能通過第二步對CERT.SF的檢測。

3前面兩步通過僞裝修改的方式對MANIFEST.MF和CERT.SF的校驗都通過了的話,那麼第三部CERT.RSA 的校驗,就不可能僞裝了,因爲CERT.RSA 使用的是開發者的私鑰信息進行的簽名。用戶無法獲得其私鑰信息,沒法僞裝。

v1版本簽名下的多渠道包實現   

    既然上面的簽名校驗方式,不允許我們對apk解壓之後的某個文件的修改。  我們就可以不破壞這種校驗機制增加文件,來記錄apk包的渠道信息。或者利用zip的文件格式來做文章存儲我們的渠道信息。市面上有2種對v1簽名生成多渠道包的方式。

方式1:們可以在不改變原來apk中任何單個文件的情況下,通過對apk增加文件的方式來記錄相關的渠道信息。實現多渠道打包。

我們可以在META-INF,或者apk包解壓的任何文件夾位置添加一個我們自己的文件,文件名稱如上面channel_xiaomi.txt,以文件名稱作爲渠道名,然後再重新生成apk的 zip文件。因爲上面的MANIFEST.MF ,CERT.SF ,CERT.RSA是對apk解壓之後的每個單獨文件進行校驗,但是我們增減的channel_xiaomi.txt是在生成渠道包之後,重新加入進去的,所以android系統安裝apk的時候,我們增加的channel_xiaomi.txt並不在我們android系統校驗的範圍內,我們就可以在app內,讀取這個文件的名稱來獲取對應的渠道。這裏主要耗費的時間是對apk解壓之後生成channel_xxx.txt的空文件,然後重新打包成.apk文件。

方式二:利用zip文件的文件格式,我們可以在文件最後的部分的comment(註釋)中加入自己的渠道信息,然後再app中去讀去zip文件的comment裏面的內容,讀去到對應的渠道號。這裏主要是對zip文件的操作。

V2簽名方案

         Android 7.0(Nougat)引入一項新的應用簽名方案APK Signature Scheme v2,它是一個對全文件進行簽名的方案,能提供更快的應用安裝、對未授權APK文件的更改提供更多保護,在默認情況下,Android Gradle 2.2.0插件會使用APK Signature Scheme v2和傳統簽名方案來簽署你的應用。

        目前該方案不是強制性的,在 build.gradle 添加 v2SigningEnabled false ,就能使用傳統簽名方案來簽署我們的應用(見下面的代碼片段)

android {
    ...
    defaultConfig { ... }
    signingConfigs {
      release {
        storeFile file("xxx.keystore")
        storePassword "password"
        keyAlias "MyReleaseKey"
        keyPassword "password"
        v2SigningEnabled false
      }
    }
  }

      APK 簽名方案 v2 是一種全文件簽名方案,該方案能夠發現對 APK 的受保護部分進行的所有更改,從而有助於加快驗證速度並增強完整性保護。使用 APK 簽名方案 v2 進行簽名時,會在 APK 文件中插入一個 APK簽名分塊,該分塊位於“ZIP 中央目錄”部分之前並緊鄰該部分。在“APK 簽名分塊”內,v2 簽名和簽名者身份信息會存儲在 APK 簽名方案 v2 分塊中。

簽名前和簽名後的 APK

新的簽名方案會在ZIP文件格式的 Central Directory 區塊所在文件位置的前面添加一個APK Signing Block區塊。整個APK(ZIP文件格式)會被分爲以下四個區塊: 1. Contents of ZIP entries(from offset 0 until the start of APK Signing Block) 2. APK Signing Block 3. ZIP Central Directory 4. ZIP End of Central Directory。

之前的渠道包生成方案是通過在META-INF目錄下添加空文件,用空文件的名稱來作爲渠道的唯一標識,之前在META-INF下添加文件是不需要重新簽名應用的,這樣會節省不少打包的時間,從而提高打渠道包的速度。但在新的應用簽名方案下META-INF已經被列入了保護區了,向META-INF添加空文件的方案會對區塊1、3、4都會有影響,v2簽名方案簽署的應用經過我們舊的生成渠道包方案處理後,在安裝時會報以下錯誤:

Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES:

Failed to collect certificates from base.apk: META-INF/CERT.SF indicates base.apk is signed using APK Signature Scheme v2, but no such signature was found. Signature stripped?]

目前另外一種比較流行的往APK中添加ZIP Comment,生成多渠道包的方案,也因爲上述原因,無法在新的應用簽名方案下進行正常工作。

既然v1簽名生成多渠道包的方式對於v2不能使用,那麼就需要找另外的出路了。

既然v2簽名保護1,3,4塊數據區,不能對1,3,4區快的數據做修改,那麼就可以在2 數據區 Apk Signing block做修改,在這塊數據加入我們的渠道信息。

APK 簽名分塊

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

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

數據 字節數 描述
size of blcok 8個字節 除此字段外 block的總長度
id-value-pairs 8個字節 此id與value 數據的總長度
id 4個字節 id數據
value n個字節 value數據
...
size of  block 8個字節 與第一個字段相同
magic 16個字節 魔數,標記block的數據格式

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

驗證

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

APK 簽名驗證過程

通過上圖可以看出新的應用簽名方案的驗證過程:

1. 尋找APK Signing Block,如果能夠找到,則進行驗證,驗證成功則繼續進行安裝,如果失敗了則終止安裝

2. 如果未找到APK Signing Block,則執行原來的簽名驗證機制,也是驗證成功則繼續進行安裝,如果失敗了則終止安裝。

在參考文章中,zip的文件格式中我們可以知道End of Central Directory的這塊的數據格式,如下:

Offset Bytes Description
0 4 End of central directory signature = 0x06054b50 核心目錄結束標記,固定爲0x06054b50
4 2 Number of this disk 當前的磁盤編號
6 2 Disk where central directory starts 核心目錄開始的磁盤編號
8 2 Number of central directory records on this disk 磁盤上所記錄的核心目錄數量
10 2 Total number of central directory records 核心目錄的總數
12 4 Size of central directory (bytes) 核心目錄的大小
16 4 Offset of start of central directory, relative to start of archive 核心目錄開始位置相對於archive的偏移
20 2 Comment length (n) 註釋的長度
22 n Comment 註釋的內容

知道apk的文件結構,這樣可以從apk(實質就是zip文件) EOCD,反推出Central Directory的位置,最後確定是否有APK signing Block,然後確認是否是v2簽名,同時我們也可以在APK signing Block這塊內容增加一個固定的id(不與ID 0x7109871a重複就行)-value對,來存放我們apk的channel信息,這樣就不會破壞整個apk的簽名信息,又可以增加我們的渠道信息。

在使用美團打包walle的時候,如果你的build-tools 版本較高的話,可能打出來的包,無法在Android P 上安裝。因爲 android p 需要 apksigningblock 的長度確保爲 4096 的倍數。具體的解決方案在這裏

V3簽名方案

Android 9 支持 APK祕鑰輪替,這使應用能夠在 APK 更新過程中更改其簽名密鑰。爲了實現輪替,APK 必須指示新舊簽名密鑰之間的信任級別。爲了支持密鑰輪替,google將 APK簽名從 v2 更新爲 v3,以允許使用新舊密鑰。v3 在 APK 簽名分塊中添加了有關受支持的 SDK 版本和 proof-of-rotation 結構的信息。

APK 簽名驗證過程

參考文章:

https://source.android.google.cn/security/apksigning/v2#apk-signing-block-format

zip文件格式

美團自動化打包實踐

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