1. 程式人生 > 程式設計 >詳解Android v1、v2、v3簽名(小結)

詳解Android v1、v2、v3簽名(小結)

Android簽名機制

什麼是Android簽名

瞭解 HTTPS 通訊的同學都知道,在訊息通訊時,必須至少解決兩個問題:一是確保訊息來源的真實性,二是確保訊息不會被第三方篡改。

同理,在安裝 apk 時,同樣也需要確保 apk 來源的真實性,以及 apk 沒有被第三方篡改。為了解決這一問題,Android官方要求開發者對 apk 進行簽名,而簽名就是對apk進行加密的過程。要了解如何實現簽名,需要了解兩個基本概念:訊息摘要、數字簽名和數字證書。

訊息摘要

訊息摘要(Message Digest),又稱數字摘要(Digital Digest)或數字指紋(Finger Print)。簡單來說,訊息摘要就是在訊息資料上,執行一個單向的 Hash 函式,生成一個固定長度的Hash值,這個Hash值即是訊息摘要。

上面提到的的加密 Hash 函式就是訊息摘要演算法。它有以下特徵:

無論輸入的訊息有多長,計算出來的訊息摘要的長度總是固定的。
例如:應用 MD5 演算法摘要的訊息有128個位元位,用 SHA-1 演算法摘要的訊息最終有 160 位元位的輸出,SHA-1 的變體可以產生 192 位元位和 256 位元位的訊息摘要。一般認為,摘要的最終輸出越長,該摘要演算法就越安全。

訊息摘要看起來是「隨機的」。
這些位元看上去是胡亂的雜湊在一起的。可以用大量的輸入來檢驗其輸出是否相同,一般,不同的輸入會有不同的輸出,而且輸出的摘要訊息可以通過隨機性檢驗。但是,一個摘要並不是真正隨機的,因為用相同的演算法對相同的訊息求兩次摘要,其結果必然相同;而若是真正隨機的,則無論如何都是無法重現的。因此訊息摘要是「偽隨機的」。

訊息摘要函式是單向函式,即只能進行正向的資訊摘要,而無法從摘要中恢復出任何的訊息,甚至根本就找不到任何與原資訊相關的資訊。
當然,可以採用強力攻擊的方法,即嘗試每一個可能的資訊,計算其摘要,看看是否與已有的摘要相同,如果這樣做,最終肯定會恢復出摘要的訊息。但實際上,要得到的資訊可能是無窮個訊息之一,所以這種強力攻擊幾乎是無效的。

好的摘要演算法,沒有人能從中找到「碰撞」。或者說,無法找到兩條訊息,使它們的摘要相同。
雖然「碰撞」是肯定存在的(由於長明文生成短摘要的 Hash 必然會產生碰撞)。即對於給定的一個摘要,不可能找到一條資訊使其摘要正好是給定的。

正是由於以上特點,訊息摘要演算法被廣泛應用在「數字簽名」領域,作為對明文的摘要演算法。著名的訊息摘要演算法有 RSA 公司的 MD5 演算法和 SHA-1 演算法及其大量的變體。SHA-256 是 SHA-1 的升級版,現在 Android 簽名使用的預設演算法都已經升級到 SHA-256 了。

正是因為訊息摘要具有這種特性,很適合來驗證資料的完整性。比如:在網路傳輸過程中下載一個大檔案 BigFile,我們會同時從網路下載 BigFile 和 BigFile.md5,BigFile.md5 儲存 BigFile 的摘要,我們在本地生成 BigFile 的訊息摘要和 BigFile.md5 比較,如果內容相同,則表示下載過程正確。

數字簽名

數字簽名的作用就是保證資訊傳輸的完整性、傳送者的身份認證、防止交易中的抵賴發生。數字簽名技術是將摘要資訊用傳送者的私鑰加密,與原文一起傳送給接收者。接收者只有用傳送者的公鑰才能解密被加密的摘要資訊然後用HASH函式對收到的原文產生一個摘要資訊,與解密的摘要資訊對比。如果相同,則說明收到的資訊是完整的,在傳輸過程中沒有被修改,否則說明資訊被修改過,因此數字簽名能夠驗證資訊的完整性。

如 RSA 作為數字簽名方案使用時,它的使用流程如下:這種簽名實際上就是用信源的私鑰加密訊息,加密後的訊息即成了籤體;而用對應的公鑰進行驗證,若公鑰解密後的訊息與原來的訊息相同,則訊息是完整的,否則訊息不完整。

RSA正好和公鑰密碼用於訊息保密是相反的過程。因為只有信源才擁有自己地私鑰,別人無法重新加密源訊息,所以即使有人截獲且更改了源訊息,也無法重新生成籤體,因為只有用信源的私鑰才能形成正確地籤體。

同樣信宿只要驗證用信源的公鑰解密的訊息是否與明文訊息相同,就可以知道訊息是否被更改過,而且可以認證訊息是否是確實來自意定的信源,還可以使信源不能否認曾經發送的訊息。所以 這樣可以完成數字簽名的功能。

但這種方案過於單純,它僅可以保證訊息的完整性,而無法確保訊息的保密性。而且這種方案要對所有的訊息進行加密操作,這在訊息的長度比較大時,效率是非常低的,主要原因在於公鑰體制的加解密過程的低效性。所以這種方案一般不可取。

幾乎所有的數字簽名方案都要和快速高效的摘要演算法(Hash 函式)一起使用,當公鑰演算法與摘要演算法結合起來使用時,便構成了一種有效地數字簽名方案。

簽名證書

通過數字簽名技術,確實可以解決可靠通訊的問題。一旦驗籤通過,接收者就能確信該訊息是期望的傳送者傳送的,而傳送者也不能否認曾經發送過該訊息。

大家有沒有注意到,前面講的數字簽名方法,有一個前提,就是訊息的接收者必須事先得到正確的公鑰。如果一開始公鑰就被別人篡改了,那壞人就會被你當成好人,而真正的訊息傳送者給你發的訊息會被你視作無效的。而且,很多時候根本就不具備事先溝通公鑰的資訊通道。

那麼如何保證公鑰的安全可信呢?這就要靠數字證書來解決了。

數字證書是一個經證書授權(Certificate Authentication)中心數字簽名的包含公鑰擁有者資訊以及公鑰的檔案。數字證書的格式普遍採用的是 X.509 V3 國際標準,一個標準的 X.509 數字證書通常包含以下內容:

證書的釋出機構(Issuer):該證書是由哪個機構(CA 中心)頒發的。

證書的有效期(Validity):證書的有效期,或者說使用期限。過了該日期,證書就失效了。

證書所有人的公鑰(Public-Key):該證書所有人想要公佈出去的公鑰。

證書所有人的名稱(Subject):這個證書是發給誰的,或者說證書的所有者,一般是某個人或者某個公司名稱、機構的名稱、公司網站的網址等。

證書所使用的簽名演算法(Signature algorithm):這個數字證書的數字簽名所使用的加密演算法,這樣就可以使用證書釋出機構的證書裡面的公鑰,根據這個演算法對指紋進行解密。

證書發行者對證書的數字簽名(Thumbprint):數字證書的hash 值(指紋),用於保證數字證書的完整性,確保證書沒有被修改過。

數字證書的原理就是在證書釋出時,CA 機構會根據簽名演算法(Signature algorithm)對整個證書計算其 hash 值(指紋)並和證書放在一起,使用者開啟證書時,自己也根據簽名演算法計算一下證書的 hash 值(指紋),如果和證書中記錄的指紋對的上,就說明證書沒有被修改過。

可以看出,數字證書本身也用到了數字簽名技術,只不過簽名的內容是整個證書(裡面包含了證書所有者的公鑰以及其他一些內容)。與普通數字簽名不同的是,數字證書的簽名者不是隨隨便便一個普通機構,而是 CA 機構。

總結一下,數字簽名和簽名驗證的大體流程如下圖所示:

在這裡插入圖片描述

Android打包流程

整個Android的打包流程如下圖所示:

在這裡插入圖片描述

編譯打包步驟:

1,打包資原始檔,生成R.java檔案
打包資源的工具是aapt(The Android Asset Packaing Tool)(E:\Documents\Android\sdk\build-tools\25.0.0\aapt.exe)。

在這個過程中,專案中的AndroidManifest.xml檔案和佈局檔案XML都會編譯,然後生成相應的R.java,另外AndroidManifest.xml會被aapt編譯成二進位制。

存放在APP的res目錄下的資源,該類資源在APP打包前大多會被編譯,變成二進位制檔案,並會為每個該類檔案賦予一個resource id。對於該類資源的訪問,應用層程式碼則是通過resource id進行訪問的。Android應用在編譯過程中aapt工具會對資原始檔進行編譯,並生成一個resource.arsc檔案,resource.arsc檔案相當於一個檔案索引表,記錄了很多跟資源相關的資訊。

2. 處理aidl檔案,生成相應的Java檔案
這一過程中使用到的工具是aidl(Android Interface Definition Language),即Android介面描述語言(E:\Documents\Android\sdk\build-tools\25.0.0\aidl.exe)。

aidl工具解析介面定義檔案然後生成相應的Java程式碼介面供程式呼叫。如果在專案沒有使用到aidl檔案,則可以跳過這一步。

3. 編譯專案原始碼,生成class檔案
專案中所有的Java程式碼,包括R.java和.aidl檔案,都會變Java編譯器(javac)編譯成.class檔案,生成的class檔案位於工程中的bin/classes目錄下。

4. 轉換所有的class檔案,生成classes.dex檔案
dx工具生成可供Android系統Dalvik虛擬機器執行的classes.dex檔案,該工具位於(E:\Documents\Android\sdk\build-tools\25.0.0\dx.bat)。

任何第三方的libraries和.class檔案都會被轉換成.dex檔案。dx工具的主要工作是將Java位元組碼轉成成Dalvik位元組碼、壓縮常量池、消除冗餘資訊等。

5. 打包生成APK檔案
所有沒有編譯的資源,如images、assets目錄下資源(該類檔案是一些原始檔案,APP打包時並不會對其進行編譯,而是直接打包到APP中,對於這一類資原始檔的訪問,應用層程式碼需要通過檔名對其進行訪問);編譯過的資源和.dex檔案都會被apkbuilder工具打包到最終的.apk檔案中。

打包的工具apkbuilder位於 android-sdk/tools目錄下。apkbuilder為一個指令碼檔案,實際呼叫的是(E:\Documents\Android\sdk\tools\lib)檔案中的com.android.sdklib.build.ApkbuilderMain類。

6. 對APK檔案進行簽名
一旦APK檔案生成,它必須被簽名才能被安裝在裝置上。

在開發過程中,主要用到的就是兩種簽名的keystore。一種是用於除錯的debug.keystore,它主要用於除錯,在Eclipse或者Android Studio中直接run以後跑在手機上的就是使用的debug.keystore。

另一種就是用於釋出正式版本的keystore。

7. 對簽名後的APK檔案進行對齊處理
如果你釋出的apk是正式版的話,就必須對APK進行對齊處理,用到的工具是zipalign(E:\Documents\Android\sdk\build-tools\25.0.0\zipalign.exe)

對齊的主要過程是將APK包中所有的資原始檔距離檔案起始偏移為4位元組整數倍,這樣通過記憶體對映訪問apk檔案時的速度會更快。對齊的作用就是減少執行時記憶體的使用。

從上圖可以看到,簽名發生在打包過程中的倒數第二步,而且簽名針對的是已經存在的apk包,並不會影響我們寫的程式碼。事實也確實是如此,Android的簽名,大致的簽名原理就是對未簽名的apk裡面的所有檔案計算hash,然後儲存起來(MANIFEST.MF),然後在對這些hash計算hash儲存起來(CERT.SF),然後在計算hash,然後再通過我們上面生成的keystore裡面的私鑰進行加密並儲存(CERT.RSA)。

Android簽名方案

Android 系統從誕生到現在的1.0版本,一共經歷了三代應用簽名方案,分別是v1、v2和v3方案。

  • v1 方案:基於 JAR 簽名。
  • v2 方案:APK 簽名方案 v2,在 Android 7.0 引入。
  • v3 方案:APK 簽名方案v3,在 Android 9.0 引入。

其中,v1 到 v2 是顛覆性的,主要是為了解決 JAR 簽名方案的安全性問題,而到了 v3 方案,其實結構上並沒有太大的調整,可以理解為 v2 簽名方案的升級版。

v1 到 v2 方案的升級,對開發者影響是最大的,就是渠道簽署的問題。v2的簽名也是為了讓不同渠道、市場的安裝包有所區別,攜帶渠道的唯一標識,也即是我們俗稱的渠道包。好在各大廠都開源了自己的籤渠道方案,例如:Walle(美團)、VasDolly(騰訊)都是非常優秀的方案。

V1簽名

簽名工具

Android 應用的簽名工具有兩種:jarsigner 和 apksigner。它們的簽名演算法沒什麼區別,主要是簽名使用的檔案不同。它們的區別如下:

jarsigner:jdk 自帶的簽名工具,可以對 jar 進行簽名。使用 keystore 檔案進行簽名。生成的簽名檔案預設使用 keystore 的別名命名。

apksigner:Android sdk 提供的專門用於 Android 應用的簽名工具。使用 pk8、x509.pem 檔案進行簽名。其中 pk8 是私鑰檔案,x509.pem 是含有公鑰的檔案。生成的簽名檔案統一使用“CERT”命名。

既然這兩個工具都是給 APK 簽名的,那麼 keystore 檔案和 pk8,x509.pem 他們之間是不是有什麼聯絡呢?答案是肯定的,他們之間是可以轉化的,這裡就不再分析它們是如何進行轉化,網上的例子很多。

還有一個需要注意的知識點,如果我們檢視一個keystore 檔案的內容,會發現裡面包含有一個 MD5 和 SHA1 摘要,這個就是 keystore 檔案中私鑰的資料摘要,這個資訊也是我們在申請很多開發平臺賬號時需要填入的資訊。

簽名過程

首先,我們任意選取一個簽名後的 APK(Sample-release.APK)進行解壓,會得到如下圖所示的檔案。

在這裡插入圖片描述

可以發現,在 META-INF 資料夾下有三個檔案:MANIFEST.MF、CERT.SF、CERT.RSA。它們就是簽名過程中生成的檔案,它們的作用如下。

MANIFEST.MF

該檔案中儲存的其實就是逐一遍歷 APK 中的所有條目,如果是目錄就跳過,如果是一個檔案,就用 SHA1(或者 SHA256)訊息摘要演算法提取出該檔案的摘要然後進行 BASE64 編碼後,作為「SHA1-Digest」屬性的值寫入到 MANIFEST.MF 檔案中的一個塊中。該塊有一個「Name」屬性, 其值就是該檔案在 APK 包中的路徑。

開啟MANIFEST.MF檔案,檔案內容如下圖:

在這裡插入圖片描述

CERT.SF

開啟CERT.SF檔案,檔案的內容如下:

在這裡插入圖片描述

  • SHA1-Digest-Manifest-Main-Attributes:對 MANIFEST.MF 頭部的塊做SHA1(或者SHA256)後再用 Base64 編碼。
  • SHA1-Digest-Manifest:對整個 MANIFEST.MF 檔案做 SHA1(或者 SHA256)後再用 Base64 編碼。
  • SHA1-Digest:對 MANIFEST.MF 的各個條目做 SHA1(或者 SHA256)後再用 Base64 編碼。

CERT.RSA

把之前生成的 CERT.SF 檔案用私鑰計算出簽名,然後將簽名以及包含公鑰資訊的數字證書一同寫入 CERT.RSA 中儲存。需要注意的是,Android APK 中的 CERT.RSA 證書是自簽名的,並不需要第三方權威機構釋出或者認證的證書,使用者可以在本地機器自行生成這個自簽名證書。Android 目前不對應用證書進行 CA 認證。

開啟CERT.RSA檔案,內容如下圖:

在這裡插入圖片描述

這裡看到的都是二進位制檔案,因為 使用RSA 加密了,所以我們需要用 openssl 命令才能檢視其內容,如下所示。

$ openssl pkcs7 -inform DER -in /<檔案存放路徑>/Sample-release_new/original/META-INF/CERT.RSA -text -noout -print_certs

執行上面的命令後,得到的內容如下圖:

在這裡插入圖片描述

綜上可以看出,一個完整的簽名過程如下圖:

在這裡插入圖片描述

簽名校驗

簽名驗證是發生在 APK 的安裝過程中,一共分為三步:

  • 檢查 APK 中包含的所有檔案,對應的摘要值與 MANIFEST.MF 檔案中記錄的值一致。
  • 使用證書檔案(RSA 檔案)檢驗簽名檔案(SF 檔案)沒有被修改過。
  • 使用簽名檔案(SF 檔案)檢驗 MF 檔案沒有被修改過。

綜上所述,一個完整的簽名驗證過程如下所示:

在這裡插入圖片描述

V2簽名

APK 簽名方案 v2 是一種全檔案簽名方案,該方案能夠發現對 APK 的受保護部分進行的所有更改,從而有助於加快驗證速度並增強完整性保證。通過前面的分析,可以發現 v1 簽名有兩個地方可以改進:

簽名校驗速度慢
校驗過程中需要對apk中所有檔案進行摘要計算,在 APK 資源很多、效能較差的機器上簽名校驗會花費較長時間,導致安裝速度慢。

完整性保障不夠
META-INF 目錄用來存放簽名,自然此目錄本身是不計入簽名校驗過程的,可以隨意在這個目錄中新增檔案,比如一些快速批量打包方案就選擇在這個目錄中新增渠道檔案。

為了解決這兩個問題,在 Android 7.0 版本 中引入了全新的 APK Signature Scheme v2。

V2的改進

由於在 v1 僅針對單個 ZIP 條目進行驗證,因此,在 APK 簽署後可進行許多修改 — 可以移動甚至重新壓縮檔案。事實上,編譯過程中要用到的 ZIPalign 工具就是這麼做的,它用於根據正確的位元組限制調整 ZIP 條目,以改進執行時效能。而且我們也可以利用這個東西,在打包之後修改 META-INF 目錄下面的內容,或者修改 ZIP 的註釋來實現多渠道的打包,在 v1 簽名中都可以校驗通過。

v2 簽名將驗證歸檔中的所有位元組,而不是單個 ZIP 條目,因此,在簽署後無法再執行 ZIPalign(必須在簽名之前執行)。正因如此,現在,在編譯過程中,Google 將壓縮、調整和簽署合併成一步完成。

v2 簽名

v2 簽名會在原先 APK 塊中增加了一個新的塊(簽名塊),新的塊儲存了簽名、摘要、簽名演算法、證書鏈和額外屬性等資訊,這個塊有特定的格式。最終的簽名APK其實就有四塊:標頭檔案區、V2簽名塊、中央目錄、尾部。下圖是V1簽名和V2簽名的組成。

在這裡插入圖片描述

整個簽名塊的格式如下:

  • size of block,以位元組數(不含此欄位)計 (uint64)
  • 帶 uint64 長度字首的“ID-值”對序列:
  • size of block,以位元組數計 - 與第一個欄位相同 (uint64)
  • magic“APK 簽名分塊 42”(16 個位元組)

在多個“ID-值”對中,APK簽名信息的 ID 為 0x7109871a,包含的內容如下:
帶長度字首的 signer:

  • 帶長度字首的 signed data,包含digests序列,X.509 certificates 序列,additional attributes序列
  • 帶長度字首的 signatures(帶長度字首)序列
  • 帶長度字首的 public key(SubjectPublicKeyInfo,ASN.1 DER 形式)
  • value可能會包含多個 signer,因為Android允許多個簽名。

總結一下:一個簽名塊,可以包含多個ID-VALUE,APK的簽名信息會存放在 ID 為 0x7109871a的鍵值對裡。他的內容可以包含多個簽名者的簽名信息,每個簽名信息下包含signed data、signatures、public key,其中,signed data主要存放摘要序列、證書鏈、額外屬性,signatures包含多個簽名演算法計算出來的簽名值,public key表示簽名者公鑰,用於校驗的時候驗證簽名的。

簽名過程

首先,說一下 APK 摘要計算規則,對於每個摘要演算法,計算結果如下:

  • 將 APK 中檔案 ZIP 條目的內容、ZIP 中央目錄、ZIP 中央目錄結尾按照 1MB 大小分割成一些小塊。
  • 計算每個小塊的資料摘要,資料內容是 0xa5 + 塊位元組長度 + 塊內容。
  • 計算整體的資料摘要,資料內容是 0x5a + 資料塊的數量 + 每個資料塊的摘要內容

總之,就是把 APK 按照 1M 大小分割,分別計算這些分段的摘要,最後把這些分段的摘要在進行計算得到最終的摘要也就是 APK 的摘要。然後將 APK 的摘要 + 數字證書 + 其他屬性生成簽名資料寫入到 APK Signing Block 區塊。

在這裡插入圖片描述

簽名校驗過程

接下來我們來看一下v2簽名的校驗過程,整體大概流程如下圖所示。

在這裡插入圖片描述

其中, v2 簽名機制是在 Android 7.0 以及以上版本才支援的。因此對於 Android 7.0 以及以上版本,在安裝過程中,如果發現有 v2 簽名塊,則必須走 v2 簽名機制,不能繞過。否則降級走 v1 簽名機制。v1 和 v2 簽名機制是可以同時存在的,其中對於 v1 和 v2 版本同時存在的時候,v1 版本的 META_INF 的 .SF 檔案屬性當中有一個 X-Android-APK-Signed 屬性。

X-Android-APK-Signed: 2

之前的渠道包生成方案是通過在 META-INF 目錄下新增空檔案,用空檔案的名稱來作為渠道的唯一標識。但在新的應用簽名方案下 META-INF 已經被列入了保護區了,向 META-INF 新增空檔案的方案會對區塊 1、3、4 都會有影響。對於這個問題,可以參考美團多渠道打包總結。

V3簽名

新版v3簽名在v2的基礎上,仍然採用檢查整個壓縮包的校驗方式。不同的是在簽名部分增可以新增新的證書(Attr塊)。在這個新塊中,會記錄我們之前的簽名信息以及新的簽名信息,以金鑰轉輪的方案,來做簽名的替換和升級。這意味著,只要舊簽名證書在手,我們就可以通過它在新的 APK 檔案中,更改簽名。

v3 簽名新增的新塊(attr)儲存了所有的簽名信息,由更小的 Level 塊,以連結串列的形式儲存。

其中每個節點都包含用於為之前版本的應用簽名的簽名證書,最舊的簽名證書對應根節點,系統會讓每個節點中的證書為列表中下一個證書籤名,從而為每個新金鑰提供證據來證明它應該像舊金鑰一樣可信。

簽名校驗過程

Android 的簽名方案,無論怎麼升級,都是要確保向下相容。因此,在引入 v3 方案後,Android 9.0 及更高版本中,可以根據 APK 簽名方案,v3 -> v2 -> v1 依次嘗試驗證 APK。而較舊的平臺會忽略 v3 簽名並嘗試 v2 簽名,最後才去驗證 v1 簽名。
整個驗證的過程,如下圖:

在這裡插入圖片描述

需要注意的是,對於覆蓋安裝的情況,簽名校驗只支援升級,而不支援降級。也就是說裝置上安裝了一個使用 v1 簽名的 APK,可以使用 v2 簽名的 APK 進行覆蓋安裝,反之則不允許。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。