Android v一、v二、v3簽名詳解

Android簽名機制

什麼是Android簽名

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

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

消息摘要

消息摘要(Message Digest),又稱數字摘要(Digital Digest)或數字指紋(Finger Print)。簡單來講,消息摘要就是在消息數據上,執行一個單向的 Hash 函數,生成一個固定長度的Hash值,這個Hash值便是消息摘要。android

上面提到的的加密 Hash 函數就是消息摘要算法。它有如下特徵:git

  • 不管輸入的消息有多長,計算出來的消息摘要的長度老是固定的。 例如:應用 MD5 算法摘要的消息有128個比特位,用 SHA-1 算法摘要的消息最終有 160 比特位的輸出,SHA-1 的變體能夠產生 192 比特位和 256 比特位的消息摘要。通常認爲,摘要的最終輸出越長,該摘要算法就越安全。github

  • 消息摘要看起來是「隨機的」。 這些比特看上去是胡亂的雜湊在一塊兒的。能夠用大量的輸入來檢驗其輸出是否相同,通常,不一樣的輸入會有不一樣的輸出,並且輸出的摘要消息能夠經過隨機性檢驗。可是,一個摘要並非真正隨機的,由於用相同的算法對相同的消息求兩次摘要,其結果必然相同;而如果真正隨機的,則不管如何都是沒法重現的。所以消息摘要是「僞隨機的」。算法

  • 消息摘要函數是單向函數,即只能進行正向的信息摘要,而沒法從摘要中恢復出任何的消息,甚至根本就找不到任何與原信息相關的信息。 固然,能夠採用強力攻擊的方法,即嘗試每個可能的信息,計算其摘要,看看是否與已有的摘要相同,若是這樣作,最終確定會恢復出摘要的消息。但實際上,要獲得的信息多是無窮個消息之一,因此這種強力攻擊幾乎是無效的。安全

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

正是因爲以上特色,消息摘要算法被普遍應用在「數字簽名」領域,做爲對明文的摘要算法。著名的消息摘要算法有 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版本,一共經歷了三代應用簽名方案,分別是v一、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 應用的簽名工具。使用 pk八、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 的安裝過程當中,一共分爲三步:

  1. 檢查 APK 中包含的全部文件,對應的摘要值與 MANIFEST.MF 文件中記錄的值一致。
  2. 使用證書文件(RSA 文件)檢驗簽名文件(SF 文件)沒有被修改過。
  3. 使用簽名文件(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 添加空文件的方案會對區塊 一、三、4 都會有影響。對於這個問題,能夠參考美團多渠道打包總結。而且V2簽名也存在一些漏洞,具體參考Android簽名漏洞

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 進行覆蓋安裝,反之則不容許。關於V3簽名的更多知識,能夠參考下面的文章: Android v3簽名新特性
相關文章
相關標籤/搜索