Android 應用簽名是應用打包過程的重要步驟之一,Google 要求全部的應用必須被簽名才能夠安裝到 Android 操做系統中。Android 的簽名機制也爲開發者識別和更新本身應用提供了方便。本文嘗試對 Android 應用簽名機制進行簡單分析,我的理解有限,不免有紕漏之處,請多多拍磚。html
想要搞清楚安卓應用簽名究竟是什麼東西,首先須要瞭解一些背景知識。java
數字摘要主要做用是將任意長度的消息使用單向 HASH 算法摘要成一串固定長度的密文。經常使用的 HASH 算法包括 SHA-1, SHA-256, MD5 等等,MD5 中的 MD 就是 Message Digest 的縮寫。數字摘要有時也被稱爲數字指紋,消息摘要等等,其實表達的都是一個意思。它有如下三個特色:android
Android 簽名須要用到一種後綴名爲 keystore 的文件。在打 Debug 包的時候,若是沒有在 build.gradle 文件中指定的話,Gradle 就會自動爲咱們生成一個 keystore文件,保存在系統用戶根目錄 .android 文件夾內,名稱爲 debug.keystore. 咱們以它爲例看看 keystore 文件包含了什麼內容。git
經過 keytool 工具來查看,默認的密碼是 android. $ keytool -list -v -keystore debug.keystore -storepass android
結果以下:算法
密鑰庫類型: jks
密鑰庫提供方: SUN
您的密鑰庫包含 1 個條目
別名: androiddebugkey
建立日期: 2013-4-26
條目類型: PrivateKeyEntry
證書鏈長度: 1
證書[1]:
全部者: CN=Android Debug, O=Android, C=US
發佈者: CN=Android Debug, O=Android, C=US
序列號: 517a38f2
有效期爲 Fri Apr 26 15:46:58 CST 2013 至 Sun Apr 19 15:46:58 CST 2043
證書指紋:
MD5: 37:09:10:A9:F1:AE:9C:E4:C0:85:B9:35:D9:93:93:52
SHA1: F1:60:3F:72:2A:F2:3A:BC:BE:1C:DB:F6:F4:5B:FD:5E:34:8C:01:A9
SHA256: 86:C7:CB:D1:56:E7:D8:B8:AD:67:A7:A1:8F:C0:F6:E6:FC:E1:3D:45:AE:BC:F5:DF:B4:A9:F9:9A:F7:89:F7:0D
簽名算法名稱: SHA1withRSA
主體公共密鑰算法: 1024 位 RSA 密鑰
版本: 3
複製代碼
能夠看到,其實 keystore 相似一個鑰匙倉庫,裏面有證書的全部者和發佈者信息,包含了私鑰和公鑰信息,並設置了密碼進行保護。那什麼是公鑰和私鑰呢。shell
公鑰和私鑰都是公共密鑰系統裏的概念。最初全部的加密算法都屬於對稱加密,也就是說加密和解密使用的相同的密碼,通訊雙方如何安全溝通和保存密碼,是這種加密方法的主要難題。難保沒有豬隊友。安全
而在公共密鑰系統中,加密和解密使用的是不一樣的密鑰,分別稱爲公鑰和私鑰,公鑰意思就是全部人均可以知道,私鑰則只有全部者才持有,單從公鑰沒法在現有的計算能力下推導出私鑰。這樣一來就不存在溝經過程中泄露密鑰的問題,只要私鑰不泄露,通訊就一直是安全的。bash
公共密鑰系統能夠說是如今最最最重要密鑰系統,是互聯網的基石之一。oracle
公共密鑰系統能夠用來加密,也能夠用來簽名。加密方案中,是不但願別人知道個人消息,因此公鑰用於加密信息,私鑰用於解密信息;而簽名方案中,是不但願別人冒充我發消息,只有我才能發佈這個簽名,因此須要用私鑰進行簽名,公鑰用於驗證簽名。app
回到正題,咱們先來看看 Android 應用簽名發生在構建的哪一步。
在編譯過程當中,編譯器首先會將源代碼和資源進行編譯,生成 DEX 文件和一些編譯後的資源文件,而後 APK Manager 會根據配置使用 keystore 文件進行簽名,簽名後纔會將全部資源壓縮到一個 ZIP 包裏,這個 ZIP 包其實咱們安裝的時候用的 APK 文件。能夠看到簽名是在構建基本完成的時候發生的。
那 APK Manager 是如何使用 keystore 進行簽名的呢?咱們一步一步看到底發生了什麼。
META-INF/MANIFEST.MF
文件中Name: res/drawable-xhdpi-v4/im_ic_keyboard_pressed.png
SHA-256-Digest: cqjOi3gUv9O0IBfgLOlIJUZTBwyCPcWbXIs/o6TMfTc
Name: classes.dex
SHA-256-Digest: FJCwLV1TyZuL1qxkDsJ6bXTmaSkK+JkKt5zmpDBc8Tg=
複製代碼
咱們看一下 im_ic_keyboard_pressed.png
這個文件的數字摘要究竟是如何計算出來的。
$ shasum -a 256 im_ic_keyboard_pressed.png
72a8ce8b7814bfd3b42017e02ce948254653070c823dc59b5c8b3fa3a4cc7d37 im_ic_keyboard_pressed.png
複製代碼
$ echo "72a8ce8b7814bfd3b42017e02ce948254653070c823dc59b5c8b3fa3a4cc7d37" | xxd -r -p | base64
cqjOi3gUv9O0IBfgLOlIJUZTBwyCPcWbXIs/o6TMfTc=
複製代碼
能夠看到跟咱們在 MANIFEST.MF 中看到的值是可以對上的。 2. 對 MANIFEST.MF 文件生成數字摘要,並寫入 CERT.SF,這裏有一個細節,除了對整個文件作 HASH 外,還會將文件分紅多段計算 HASH 一樣保存在 CERT.SF 文件中【爲何,還沒搞清楚】 3. 計算 CERT.SF 的數字摘要,並使用 RSA 私鑰進行加密,生成簽名 4. 將簽名、公鑰、哈希算法信息寫入 CERT.RSA 文文件,並將這些文件添加到 APK 壓縮包 META-INF 目錄中
目前應用簽名不須要申請可信的證書機構 (Certificate Authority) 簽發的證書,開發者能夠經過 keytool 來建立自簽名的證書。
應用簽名不能保證 APK 不被篡改,只是爲了可以校驗出 APK 是否被篡改。在系統安裝過程當中,若是發現 APK 被篡改,安裝就會失敗。那系統是如何校驗的呢?
整個校驗過程當中,環環相扣,從 CERT.RSA -> CERT.SF -> MANIFEST.SF -> All Other Files,只要有一環失敗,系統就會終止 APK 的安裝.
若是給新版應用分配了新的簽名文件,那就必須更改包名,這樣系統纔會認爲是不一樣的應用。不然安裝就會失敗,提示簽名不一致。
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES
除了用於安裝時校驗應用,簽名還有一些別的用途。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:sharedUserId="com.meng.sharedappid" package="meng.mainuicomponents">
複製代碼
val friendContext = this.createPackageContext("packageName", Context.CONTEXT_INCLUDE_CODE)
val friendClass = friendContext.classLoader.loadClass("packageName.className")
val noparams = arrayOf<Class<*>>() //say the function (functionName) required no inputs
friendClass.getMethod("functionName", *noparams).invoke(friendClass.newInstance(), null)
複製代碼
<!-- 聲明權限供兄弟 APP 使用 -->
<permission android:name="com.xxx.permission.SHARED_ACCOUNT" android:protectionLevel="signature" />
<!-- 申請獲取兄弟 APP 的聲明的權限 -->
<uses-permission android:name="com.xxx.broapp1.permission.SHARED_ACCOUNT" />
<uses-permission android:name="com.xxx.broapp2.permission.SHARED_ACCOUNT" />
<!-- 定義 ContentProvider 來供兄弟 APP 獲取共享帳戶信息,指定度權限爲簽名聲明的權限 -->
<provider android:name="com.xxx.XXXXSharedAccountProvider" android:authorities="com.xxx.shared_account" android:exported="true" android:readPermission="com.xxx.permission.SHARED_ACCOUNT" />
複製代碼
目前簽名模式有三代,基本原理都是同樣的,只是在流程上有些不一樣。
第一代是基於 JAR 文件簽名,它主要的缺陷是隻保護了一部分文件,而不是對整個 APK 文件作保護。這是由於全部文件都不可能包含了自身的簽名,由於它不可能爲本身簽名後再把簽名信息保存到本身內部,這是一個雞生蛋蛋生雞的問題,由於這個問題的存在,第一代簽名機制會忽略全部以 .SF/.DSA/.RSA 的文件以及 META-INFO 目錄下的全部文件。
因此攻擊者就能夠解壓縮後在 APK/META-INF 目錄新增一個含有惡意代碼的文件,而後再壓縮成 APK,一樣是能夠覆蓋安裝正版應用的,這樣一來好好的應用就會被殺毒軟件標記爲惡意軟件,從而達到攻擊應用的目的。 除了容易被攻擊外,應用安裝起來也比較慢,由於安裝器在校驗時須要解壓計算全部文件的數字摘要,確認沒有被惡意修改。
美團打渠道包的方法本質上就利用了這個第一代簽名的漏洞,在 META-INF 目錄下新建了一個包含 vendor 名稱的文件,從而不須要從新編譯,只須要解壓縮 APK,添加文件,從新壓縮就完成了一個渠道包的生成,速度很是快。
Android 7.0 引入了第二代簽名,避免了第一代簽名模式的問題,主要改進在於它在驗證過程當中,將整個 APK 文件看成一個總體,只校驗 APK 文件的簽名就能夠了,從而一方面更嚴苛的避免了 APK 被篡改,另一方面也不用加壓縮後對全部文件進行校驗,從而極大提高了安裝速度。第二代簽名向後兼容,使用新簽名的 APK 能夠安裝到 <7.0 的系統上,但要求 APK 同時也進行 v1 的簽名。
具體來講,第二代簽名將整個 APK 文件進行簽名,並將簽名信息保存在了 APK 文件的的尾部 Central Directory 的前邊。它能夠對第一三四,以及第二塊除了簽名部分的其餘區域提供一致性保護。
X-Android-APK-Signed
屬性,這樣一來,支持 V2 簽名的系統在回滾到 V1 簽名的時候就會校驗是否存在這個屬性,若是存在,就會拒絕安裝 APK,這一切都是創建在 *.SF 文件被 V1 簽名保護的基礎之上。
Android 9.0 引入了第三代簽名機制,主要增長一個功能叫 APK key rotation. 意思是容許開發者在更新 APK 的時候更換籤名。簽名的主要機制跟 V2 實際上是同樣的,只是從新設計了 APK Signing Block 的存儲結構,以支持更換籤名。這裏就再也不細說,能夠參考官方的 文檔
下圖是安裝一個 APK 時,系統對三代簽名校驗的流程示意。
by @monkeyM