天天看點

APK簽名機制之——JAR簽名機制詳解

轉載請注明出處:https://blog.csdn.net/zwjemperor/article/details/80877305 github:https://github.com/rushgit/zhongwenjun.github.com

在APK簽名機制原理詳解中我們已經了解了APK簽名和校驗的基本過程,這一篇我們來分析JAR簽名機制。JAR簽名對對jar包進行簽名的一種機制,由于jar包apk本質上都是zip包,是以可以應用到對apk的簽名。本文從JAR簽名結構、簽名過程,再到簽名校驗的源碼分析,全方面來分析Android中JAR簽名及校驗的機制。

1. 簽名過程

APK簽名機制之——JAR簽名機制詳解

通過解壓工具打開apk檔案,會發現有一個META-INF目錄,該目錄中有3個檔案,這3個檔案是簽名以後生成的,顯然與簽名相關,我們依次看這幾個檔案中的内容。

這個檔案列出了apk中所有的檔案,以及它們的摘要,摘要字元串是通過base64編碼的,通過計算來驗證下:

先計算出res/drawable-hdpi-v4/abc_list_longpressed_holo.9.png的sha1值。

APK簽名機制之——JAR簽名機制詳解

計算出的sha1值是經過16進行編碼的,再把它轉成base64編碼,可以通過線上工具進行轉換:tomeko.net

APK簽名機制之——JAR簽名機制詳解

可以看到轉換過後的base64值和MANIFEST.MF檔案中内容是一樣的。

SF檔案的内容和MF比較相似,同樣包含了apk所有檔案的摘要,不同的是:

SF檔案在主屬性中記錄了整個MF檔案的摘要(SHA1-Digest-Manifest)

SF檔案其餘部分記錄的是MF相應條目的摘要,也就說對MF檔案相應條目再次進行了摘要計算。

我們再來驗證下,首先計算出MANIFEST.MF檔案的sha1值,再轉換base64編碼:

APK簽名機制之——JAR簽名機制詳解
APK簽名機制之——JAR簽名機制詳解

再來驗證res/drawable-hdpi-v4/abc_list_longpressed_holo.9.png

這裡要注意下,.MF檔案是以空行分隔的。計算.MF各條目摘要時需要再加一個換行符,因為空行還有一個換行符(具體可參考apksigner源碼)。我們把abc_list_longpressed_holo.9.png條目儲存到一個新檔案中,先計算這個條目的sha1值:

APK簽名機制之——JAR簽名機制詳解

再把sha1值轉為base64編碼:

APK簽名機制之——JAR簽名機制詳解

cert.rsa中的是二進行内容,裡面儲存了簽名者的證書資訊,以及對cert.sf檔案的簽名。具體證書包含的内容已經在APK簽名機制原理詳解中作了說明,這裡不再重複介紹。

具體簽名過程如下:

APK簽名機制之——JAR簽名機制詳解

具體過程可參考apksigner源碼

2. 校驗過程

上面說的是簽名過程,接下來看apk安裝過程是怎樣進行簽名校驗的。校驗過程和簽名過程剛好相反:

首先校驗cert.sf檔案的簽名

計算cert.sf檔案的摘要,與通過簽名者公鑰解密簽名得到的摘要進行對比,如果一緻則進入下一步;

校驗manifest.mf檔案的完整性

計算manifest.mf檔案的摘要,與cert.sf主屬性中記錄的摘要進行對比,如一緻則逐一校驗mf檔案各個條目的完整性;

校驗apk中每個檔案的完整性

逐一計算apk中每個檔案(META-INF目錄除外)的摘要,與mf中的記錄進行對比,如全部一緻,剛校驗通過;

校驗簽名的一緻性

如果是更新安裝,還需校驗證書簽名是否與已安裝app一緻。

以上步驟需要全部通過才算簽名校驗通過,任何一步失敗都将導緻校驗失敗。這個過程能保證apk不可被篡改嗎?我們來看看篡改apk内容會發生什麼:

篡改apk内容

校驗apk中每個檔案的完整性時失敗;如果是添加新檔案,因為此檔案的hash值在.mf和.sf中無記錄,同樣校驗失敗;

篡改apk内容,同時篡改manifest.mf檔案相應的摘要

校驗manifest.mf檔案的摘要會失敗;

篡改apk内容,同時篡改manifest.mf檔案相應的摘要,以及cert.sf檔案的内容

校驗cert.sf檔案的簽名會失敗;

把apk内容和簽名資訊一同全部篡改

這相當于對apk進行了重新簽名,在此apk沒有安裝到系統中的情況下,是可以正常安裝的,這相當于是一個新的app;但如果進行覆寫安裝,則證書不一證,安裝失敗。

從這裡可以看出隻要篡改了apk中的任何内容,都會使得簽名校驗失敗。

3. 簽名校驗代碼分析

前面介紹了簽名和校驗的整體流程,現在來看校驗過程的代碼實作。安裝apk的入口在frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java的installPackageLI方法。(注:這裡參考的是5.1.1版本的源碼)

在這方法中構造了一個PackageParser對象,這個類是用來解析apk檔案的,具體的簽名校驗在這個對象的collectCertificates方法中。

這個方法中建立了一個StrictJarFile對象,在StrictJarFile的構造方法中完成了CERT.SF檔案的簽名校驗和MANIFEST.MF檔案的hash校驗,校驗的結果儲存在了isSigned成員變量中。

StrictJarFile中構造了Manifest和JarVerifier對象,具體校驗過程是中在readCertificates方法中實作的,校驗成功後将證書儲存到了certificates集合中。來看readCertificates方法的具體實作:

如果meta條目為空,則直接傳回false;如果存在,則周遊找到簽名塊檔案(.DSA/.RSA/.EC),依次進行校驗。apk簽名通常使用RSA算法,是以找到的是.RSA檔案,從這裡可以看出,.RSA檔案名并不是固定的,校驗過程中是通常字尾查找的。這裡是一個while循環,從這裡可以看出,是可以有多個簽名者對apk進行簽名的,readCertificates會依次對每一個簽名進行校驗。

繼續看verifyCertificate的實作:

首先通過.RSA檔案名找到.SF檔案,然後通過JarUtils類的verifySignature方法校驗.SF檔案簽名。校驗過後把相應證書儲存到了certificates成員變量。

verifySignature方法入參分别是.SF檔案和簽名塊(.RSA檔案)的二進制流,這個方法中計算了.SF檔案的摘要,對其簽名做了校驗。

繼續看verifyCertificate方法的後半段實作:

verifyCertificate方法的後半段做了2個事情:第一件是找到.SF檔案的SHA1-Digest-Manifest屬性值,校驗.MF檔案hash的正确性;第二件是針對.SF檔案中的每個條目,校驗.MF檔案相應條目hash的正确性。具體校驗的工作在verify方法中完成:

這裡進行了一次for循環來找到.SF檔案使用的hash算法,校驗的過程很簡單,用相應hash算法計算.MF檔案相應條目的摘要,比較是否一緻即可。至此完成了對.MF檔案hash及.MF檔案各條目hash的校驗。

繼續看的PackageParser collectCertificates方法:

在通過StrictJarFile構造方法完成.SF和.MF檔案的校驗之後,首先查找AndroidManifest.xml檔案是否存在,不存在則直接抛出異常;然後周遊apk中的條個檔案,把除META-INF目錄之外的檔案加入toVerify集合,然後對toVerity集合中的每一個檔案進行校驗。先來看loadCertificates方法:

再看StrictJarFile的getInputStream方法:

在getInputStream判斷了isSigned,這個字段在StrictJarFile構造方法中校驗.SF檔案和.MF後指派為true。通過JarVerifier的initEntry方法拿到了VerifierEntry對象,再來看initEntry的實作:

這個方法做了2件事情:一是周遊.SF檔案中已經過簽名校驗的條目(signatures map是在verifyCertificate中校驗.SF檔案後儲存的),查找是否存在方法入參指定的檔案名,不存在則說明是新增檔案,直接傳回null;第二件事是構造VerifierEntry對象,參數分别是 檔案名、hash算法、.MF檔案中相應檔案名對應的hash值、證書鍊、已簽名的檔案清單。

getInputStream在建立VerifierEntry對象後,進行了一次封裝,傳回了JarFile.JarFileInputStream對象。再回頭來看PackageParser的loadCertificates中調用的readFullyIgnoringContents方法:

這個方法看上去隻是讀取檔案流,但實際上in是JarFileInputStream,來看JarFileInputStream中read方法的實作:

可以看到,read方法中調用了VerifierEntry的verify方法,最終在verify方法中完成了apk中相應檔案的hash校驗,也就比較apk中各檔案的hash與.MF檔案中對應的值是否一緻。

再回頭來看collectCertificates方法的剩餘部分:

周遊toVerify集合時,如果loadCertificates傳回null,說明該檔案是在對apk簽名過後新增的檔案,抛出異常。緊接着後面再次作了校驗,對比後續檔案的證書簽名和第一個檔案的證書簽名是否一緻,如有不一緻仍然抛出異常。

到這一步為止,簽名校驗的工作基本就結束了,PackageManagerService.java的installPackageLI方法還有一步是針對更新安裝的場景,校驗證書公鑰是否一緻。

好了,到這裡簽名校驗的代碼介紹就結束了,代碼比較亂,梳理一下時序圖:

APK簽名機制之——JAR簽名機制詳解

4. JAR簽名機制的劣勢

從Android 7.0開始,Android支援了一套全新的V2簽名機制,為什麼要推出新的簽名機制呢?通過前面的分析,可以發現JAR簽名有兩個地方可以改進:

簽名校驗速度慢

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

完整性保障不夠

META-INF目錄用來存放簽名,自然此目錄本身是不計入簽名校驗過程的,可以随意在這個目錄中添加檔案,比如一些快速批量打包方案就選擇在這個目錄中添加管道檔案。

為了解決這兩個問題,Android 7.0推出了全新的簽名方案V2,關于V2簽名機制的詳解參見下一篇文章Apk簽名機制之——V2簽名機制詳解