Android 應用簽名是應用打包過程的重要步驟之一,Google 要求全部的應用必須被簽名才能夠安裝到 Android 操做系統中。Android 的簽名機制也爲開發者識別和更新本身應用提供了方便。android
數字摘要主要做用是將任意長度的消息使用單向 HASH 算法摘要成一串固定長度的密文。經常使用的 HASH 算法包括 SHA-1, SHA-256, MD5 等等,MD5 中的 MD 就是 Message Digest 的縮寫。數字摘要有時也被稱爲數字指紋,消息摘要等等,其實表達的都是一個意思。它有如下三個特色:git
Android 簽名須要用到一種後綴名爲 keystore 的文件。在打 Debug 包的時候,若是沒有在 build.gradle 文件中指定的話,Gradle 就會自動爲咱們生成一個 keystore文件,保存在系統用戶根目錄 .android 文件夾內,名稱爲 debug.keystore. 咱們以它爲例看看 keystore 文件包含了什麼內容。 經過 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 相似一個鑰匙倉庫,裏面有證書的全部者和發佈者信息,包含了私鑰和公鑰信息,並設置了密碼進行保護。安全
公鑰和私鑰都是公共密鑰系統裏的概念。最初全部的加密算法都屬於對稱加密,也就是說加密和解密使用的相同的密碼,通訊雙方如何安全溝通和保存密碼,是這種加密方法的主要難題。難保沒有豬隊友。 而在公共密鑰系統中,加密和解密使用的是不一樣的密鑰,分別稱爲公鑰和私鑰,公鑰意思就是全部人均可以知道,私鑰則只有全部者才持有,單從公鑰沒法在現有的計算能力下推導出私鑰。這樣一來就不存在溝經過程中泄露密鑰的問題,只要私鑰不泄露,通訊就一直是安全的。 公共密鑰系統能夠說是如今最最最重要密鑰系統,是互聯網的基石之一。 公共密鑰系統能夠用來加密,也能夠用來簽名。加密方案中,是不但願別人知道個人消息,因此公鑰用於加密信息,私鑰用於解密信息;而簽名方案中,是不但願別人冒充我發消息,只有我才能發佈這個簽名,因此須要用私鑰進行簽名,公鑰用於驗證簽名。bash
咱們先來看看 Android 應用簽名發生在構建的哪一步。app
在編譯過程當中,編譯器首先會將源代碼和資源進行編譯,生成 DEX 文件和一些編譯後的資源文件,而後 APK Manager 會根據配置使用 keystore 文件進行簽名,簽名後纔會將全部資源壓縮到一個 ZIP 包裏,這個 ZIP 包其實咱們安裝的時候用的 APK 文件。能夠看到簽名是在構建基本完成的時候發生的。ide
那 APK Manager 是如何使用 keystore 進行簽名的呢?咱們一步一步看到底發生了什麼。模塊化
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 中看到的值是可以對上的。gradle
對 MANIFEST.MF 文件生成數字摘要,並寫入 CERT.SF,這裏有一個細節,除了對整個文件作 HASH 外,還會將文件分紅多段計算 HASH 一樣保存在 CERT.SF 文件中
計算 CERT.SF 的數字摘要,並使用 RSA 私鑰進行加密,生成簽名
將簽名、公鑰、哈希算法信息寫入 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 的前邊。它能夠對第一三四,以及第二塊除了簽名部分的其餘區域提供一致性保護。
在計算簽名的時候,會將這些部分的數據切割成 1MB 大小的 CHUNK,分別計算簽名,而後彙總後再計算一個總簽名,這麼作的主要目的是爲了並行計算,加快速度。
爲了不攻擊者在 7.0 以上系統中繞過 v2 簽名機制(好比刪除 APK Signing Block?),v2 簽名要求若是 APK 同時提供了 V1 簽名的話,須要在 META-INF/*.SF 文件中增長一行 X-Android-APK-Signed 屬性,這樣一來,支持 V2 簽名的系統在回滾到 V1 簽名的時候就會校驗是否存在這個屬性,若是存在,就會拒絕安裝 APK,這一切都是創建在 *.SF 文件被 V1 簽名保護的基礎之上。
Android 9.0 引入了第三代簽名機制,主要增長一個功能叫 APK key rotation. 意思是容許開發者在更新 APK 的時候更換籤名。簽名的主要機制跟 V2 實際上是同樣的,只是從新設計了 APK Signing Block 的存儲結構,以支持更換籤名。這裏就再也不細說,能夠參考官方的 文檔 下圖是安裝一個 APK 時,系統對三代簽名校驗的流程示意。