在正式解釋Android應(yīng)用程序簽名過(guò)程之前,作為鋪墊,還得先講講最基本的一些概念。
?
非對(duì)稱加密算法
?
非對(duì)稱加密算法需要兩個(gè)密鑰:公開密鑰(簡(jiǎn)稱公鑰)和私有密鑰(簡(jiǎn)稱私鑰)。公鑰與私鑰是一對(duì),如果用公鑰對(duì)數(shù)據(jù)進(jìn)行加密,只有用對(duì)應(yīng)的私鑰才能解密;如果用私鑰對(duì)數(shù)據(jù)進(jìn)行加密,那么只有用對(duì)應(yīng)的公鑰才能解密。因?yàn)榧用芎徒饷苁褂玫氖莾蓚€(gè)不同的密鑰,所以這種算法叫作非對(duì)稱加密算法。
非對(duì)稱加密算法是數(shù)字簽名和數(shù)字證書的基礎(chǔ),大家非常熟悉的RSA就是非對(duì)稱加密算法的一種實(shí)現(xiàn)。
消息摘要算法
消息摘要算法(Message Digest Algorithm)是一種能產(chǎn)生特殊輸出格式的算法,其原理是根據(jù)一定的運(yùn)算規(guī)則對(duì)原始數(shù)據(jù)進(jìn)行某種形式的信息提取,被提取出的信息就被稱作原始數(shù)據(jù)的消息摘要。著名的摘要算法有RSA公司的MD5算法和SHA-1算法及其大量的變體。
消息摘要的主要特點(diǎn)有:
?
1)無(wú)論輸入的消息有多長(zhǎng),計(jì)算出來(lái)的消息摘要的長(zhǎng)度總是固定的。例如應(yīng)用MD5算法摘要的消息有128個(gè)比特位,用SHA-1算法摘要的消息最終有160比特位的輸出。
2)一般來(lái)說(shuō)(不考慮碰撞的情況下),只要輸入的原始數(shù)據(jù)不同,對(duì)其進(jìn)行摘要以后產(chǎn)生的消息摘要也必不相同,即使原始數(shù)據(jù)稍有改變,輸出的消息摘要便完全不同。但是,相同的輸入必會(huì)產(chǎn)生相同的輸出。
3)具有不可逆性,即只能進(jìn)行正向的信息摘要,而無(wú)法從摘要中恢復(fù)出任何的原始消息。
數(shù)字簽名和數(shù)字證書
其實(shí)數(shù)字簽名的概念很簡(jiǎn)單。大家知道,要確保可靠通信,必須要解決兩個(gè)問題:首先,要確定消息的來(lái)源確實(shí)是其申明的那個(gè)人;其次,要保證信息在傳遞的過(guò)程中不被第三方篡改,即使被篡改了,也可以發(fā)覺出來(lái)。
所謂數(shù)字簽名,就是為了解決這兩個(gè)問題而產(chǎn)生的,它是對(duì)前面提到的非對(duì)稱加密技術(shù)與數(shù)字摘要技術(shù)的一個(gè)具體的應(yīng)用。
對(duì)于消息的發(fā)送者來(lái)說(shuō),先要生成一對(duì)公私鑰對(duì),將公鑰給消息的接收者。
如果消息的發(fā)送者有一天想給消息接收者發(fā)消息,在發(fā)送的信息中,除了要包含原始的消息外,還要加上另外一段消息。這段消息通過(guò)如下兩步生成:
1)對(duì)要發(fā)送的原始消息提取消息摘要;
2)對(duì)提取的信息摘要用自己的私鑰加密。
通過(guò)這兩步得出的消息,就是所謂的原始信息的數(shù)字簽名。
而對(duì)于信息的接收者來(lái)說(shuō),他所收到的信息,將包含兩個(gè)部分,一是原始的消息內(nèi)容,二是附加的那段數(shù)字簽名。他將通過(guò)以下三步來(lái)驗(yàn)證消息的真?zhèn)?#xff1a;
1)對(duì)原始消息部分提取消息摘要,注意這里使用的消息摘要算法要和發(fā)送方使用的一致;
2)對(duì)附加上的那段數(shù)字簽名,使用預(yù)先得到的公鑰解密;
3)比較前兩步所得到的兩段消息是否一致。如果一致,則表明消息確實(shí)是期望的發(fā)送者發(fā)的,且內(nèi)容沒有被篡改過(guò);相反,如果不一致,則表明傳送的過(guò)程中一定出了問題,消息不可信。
通過(guò)這種所謂的數(shù)字簽名技術(shù),確實(shí)可以有效解決可靠通信的問題。如果原始消息在傳送的過(guò)程中被篡改了,那么在消息接收者那里,對(duì)被篡改的消息提取的摘要肯定和原始的不一樣。并且,由于篡改者沒有消息發(fā)送方的私鑰,即使他可以重新算出被篡改消息的摘要,也不能偽造出數(shù)字簽名。
所以,綜上所述,數(shù)字簽名其實(shí)就是只有信息的發(fā)送者才能產(chǎn)生的別人無(wú)法偽造的一段數(shù)字串,這段數(shù)字串同時(shí)也是對(duì)信息的發(fā)送者發(fā)送信息真實(shí)性的一個(gè)有效證明。
不知道大家有沒有注意,前面講的這種數(shù)字簽名方法,有一個(gè)前提,就是消息的接收者必須要事先得到正確的公鑰。如果一開始公鑰就被別人篡改了,那壞人就會(huì)被你當(dāng)成好人,而真正的消息發(fā)送者給你發(fā)的消息會(huì)被你視作無(wú)效的。而且,很多時(shí)候根本就不具備事先溝通公鑰的信息通道。那么如何保證公鑰的安全可信呢?這就要靠數(shù)字證書來(lái)解決了。
所謂數(shù)字證書,一般包含以下一些內(nèi)容:
?
- 證書的發(fā)布機(jī)構(gòu)(Issuer)
- 證書的有效期(Validity)
- 消息發(fā)送方的公鑰
- 證書所有者(Subject)
- 數(shù)字簽名所使用的算法
- 數(shù)字簽名
?
可以看出,數(shù)字證書其實(shí)也用到了數(shù)字簽名技術(shù)。只不過(guò)要簽名的內(nèi)容是消息發(fā)送方的公鑰,以及一些其它信息。但與普通數(shù)字簽名不同的是,數(shù)字證書中簽名者不是隨隨便便一個(gè)普通的機(jī)構(gòu),而是要有一定公信力的機(jī)構(gòu)。這就好像你的大學(xué)畢業(yè)證書上簽名的一般都是德高望重的校長(zhǎng)一樣。一般來(lái)說(shuō),這些有公信力機(jī)構(gòu)的根證書已經(jīng)在設(shè)備出廠前預(yù)先安裝到了你的設(shè)備上了。所以,數(shù)字證書可以保證數(shù)字證書里的公鑰確實(shí)是這個(gè)證書的所有者的,或者證書可以用來(lái)確認(rèn)對(duì)方的身份。數(shù)字證書主要是用來(lái)解決公鑰的安全發(fā)放問題。
綜上所述,總結(jié)一下,數(shù)字簽名和簽名驗(yàn)證的大體流程如下圖所示:
?
Android應(yīng)用程序簽名流程
?
大家知道,Android采用的是開放的生態(tài)系統(tǒng),任何人都可以開發(fā)和發(fā)布應(yīng)用程序給別人使用。不像iOS,在沒有被破解的情況下只能通過(guò)App Store安裝應(yīng)用,Android在打開了“Unknow Sources”選項(xiàng)后,可以安裝任何來(lái)源的應(yīng)用程序,可以是第三方市場(chǎng),可以是自己開發(fā)的應(yīng)用,也可以從論壇下載。
那么問題來(lái)了,對(duì)于有一些不懷好意的人,完全可以拿到一個(gè)原生的應(yīng)用,然后加入一些惡意的代碼,再發(fā)布出去,誘使別人去安裝,達(dá)到不可告人的目的。
有沒有什么辦法可以防止應(yīng)用程序在傳送的過(guò)程中被第三方惡意篡改呢?Google因此引入了應(yīng)用程序簽名機(jī)制。
它是如何工作的呢?我們先來(lái)看看簽名前后,一個(gè)apk文件到底發(fā)生了哪些變化。
首先,在沒簽名之前,apk文件內(nèi)的目錄結(jié)構(gòu)是這樣的:
?
而簽名之后,會(huì)變成這樣:
?
可以看到,多出來(lái)了一個(gè)META-INF目錄。可以肯定的是,簽名的機(jī)關(guān)就在這個(gè)目錄中,里面有三個(gè)文件:
?
其實(shí),在Android的源代碼里包含了一個(gè)工具,可以對(duì)apk文件進(jìn)行簽名,具體的代碼位置在build\tools\signapk目錄下,通過(guò)分析其中的SignApk.java文件,可以大致了解簽名的過(guò)程。其流程大致有如下幾步:
1)打開待簽名的apk文件(由于apk其實(shí)是一個(gè)用zip壓縮的文件,其實(shí)就是用zip解壓整個(gè)apk文件),逐一遍歷里面的所有條目,如果是目錄就跳過(guò),如果是一個(gè)文件,就用SHA1(或者SHA256)消息摘要算法提取出該文件的摘要然后進(jìn)行BASE64編碼后,作為“SHA1-Digest”屬性的值寫入到MANIFEST.MF文件中的一個(gè)塊中。該塊有一個(gè)“Name”屬性,其值就是該文件在apk包中的路徑。
2)計(jì)算這個(gè)MANIFEST.MF文件的整體SHA1值,再經(jīng)過(guò)BASE64編碼后,記錄在CERT.SF主屬性塊(在文件頭上)的“SHA1-Digest-Manifest”屬性值值下。
然后,再逐條計(jì)算MANIFEST.MF文件中每一個(gè)塊的SHA1,并經(jīng)過(guò)BASE64編碼后,記錄在CERT.SF中的同名塊中,屬性的名字是“SHA1-Digest”。
3)把之前生成的?CERT.SF文件,?用私鑰計(jì)算出簽名, 然后將簽名以及包含公鑰信息的數(shù)字證書一同寫入??CERT.RSA??中保存。CERT.RSA是一個(gè)滿足PKCS7格式的文件,可以通過(guò)openssl工具來(lái)查看簽名證書的信息。在Ubuntu或者在Windows上使用Cygwin,敲入以下命令:
?
[plain]?view plaincopy
openssl?pkcs7?-inform?DER?-in?CERT.RSA?-noout?-print_certs?–text?? 可以得到如下輸出:
?
下面我們來(lái)看看,如果apk文件被篡改后會(huì)發(fā)生什么。
?
首先,如果你改變了apk包中的任何文件,那么在apk安裝校驗(yàn)時(shí),改變后的文件摘要信息與MANIFEST.MF的檢驗(yàn)信息不同,于是驗(yàn)證失敗,程序就不能成功安裝。
其次,如果你對(duì)更改的過(guò)的文件相應(yīng)的算出新的摘要值,然后更改MANIFEST.MF文件里面對(duì)應(yīng)的屬性值,那么必定與CERT.SF文件中算出的摘要值不一樣,照樣驗(yàn)證失敗。
最后,如果你還不死心,繼續(xù)計(jì)算MANIFEST.MF的摘要值,相應(yīng)的更改CERT.SF里面的值,那么數(shù)字簽名值必定與CERT.RSA文件中記錄的不一樣,還是失敗。
那么能不能繼續(xù)偽造數(shù)字簽名呢?不可能,因?yàn)闆]有數(shù)字證書對(duì)應(yīng)的私鑰。
所以,如果要重新打包后的應(yīng)用程序能再Android設(shè)備上安裝,必須對(duì)其進(jìn)行重簽名。
總結(jié)
1)Android應(yīng)用程序簽名只是用來(lái)解決發(fā)布的應(yīng)用不被別人篡改的,其并不會(huì)對(duì)應(yīng)用程序本身進(jìn)行加密,這點(diǎn)不同于Windows Phone和iOS。
2)Android并不要求所有應(yīng)用程序的簽名證書都由可信任CA的根證書簽名,通過(guò)這點(diǎn)保證了其生態(tài)系統(tǒng)的開放性,所有人都可以用自己生成的證書對(duì)應(yīng)用程序簽名。
3)如果想修改一個(gè)已經(jīng)發(fā)布的應(yīng)用程序,哪怕是修改一張圖片,都必須對(duì)其進(jìn)行重新簽名。但是,簽原始應(yīng)用的私鑰一般是拿不到的(肯定在原始應(yīng)用程序開發(fā)者的手上,且不可能公布出去),所以只能用另外一組公私鑰對(duì),生成一個(gè)新的證書,對(duì)重打包的應(yīng)用進(jìn)行簽名。所以重打包的apk中所帶證書的公鑰肯定和原始應(yīng)用不一樣。同時(shí),在手機(jī)上如果想安裝一個(gè)應(yīng)用程序,應(yīng)用程序安裝器會(huì)先檢查相同包名的應(yīng)用是否已經(jīng)被安裝過(guò),如果已經(jīng)安裝過(guò),會(huì)繼續(xù)判斷已經(jīng)安裝的應(yīng)用和將要安裝的應(yīng)用,其所攜帶的數(shù)字證書中的公鑰是否一致。如果相同,則繼續(xù)安裝;而如果不同,則會(huì)提示用戶先卸載前面已安裝的應(yīng)用。通過(guò)這種方式來(lái)提示用戶,前后兩個(gè)應(yīng)用是不同開發(fā)者簽名的,可能有一個(gè)是李鬼。
?
Android應(yīng)用程序簽名驗(yàn)證過(guò)程分析
? 版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。
在前面的《Android應(yīng)用程序簽名過(guò)程分析》中,我大致分析了Android應(yīng)用程序簽名的過(guò)程,接下來(lái)我將結(jié)合源代碼,分析一下Android應(yīng)用程序在安裝過(guò)程中對(duì)簽名進(jìn)行驗(yàn)證的過(guò)程。
我們還是用前面的例子分析,假設(shè)簽名后,apk文件中多了一個(gè)META-INF目錄,里面有三個(gè)文件,分別是MANIFEST.MF、CERT.SF和CERT.RSA:
?
通過(guò)前面的分析,我們可以知道,MANIFEST.MF中記錄的是apk中所有文件的摘要值;CERT.SF中記錄的是對(duì)MANIFEST.MF的摘要值,包括整個(gè)文件的摘要,還有文件中每一項(xiàng)的摘要;而CERT.RSA中記錄的是對(duì)CERT.SF文件的簽名,以及簽名的公鑰。
大家知道,Android平臺(tái)上所有應(yīng)用程序安裝都是由PackageManangerService(代碼位于frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java)來(lái)管理的,Android的安裝流程非常復(fù)雜,與簽名驗(yàn)證相關(guān)的步驟位于installPackageLI函數(shù)中:
?
[java]?view plaincopy
private?void?installPackageLI(InstallArgs?args,?PackageInstalledInfo?res)?{??????……??????PackageParser?pp?=?new?PackageParser();??????……??????try?{??????????pp.collectCertificates(pkg,?parseFlags);??????????pp.collectManifestDigest(pkg);??????}?catch?(PackageParserException?e)?{??????????res.setError("Failed?collect?during?installPackageLI",?e);??????????return;??????}??????……?? PackageParser(代碼位于frameworks\base\core\java\android\content\pm\PackageParser.java,編譯后存在于framework.jar文件中)是一個(gè)apk包的解析器,接下來(lái)我們來(lái)看其collectCertificates函數(shù)的實(shí)現(xiàn):
?
?
[java]?view plaincopy
public?void?collectCertificates(Package?pkg,?int?flags)?throws?PackageParserException?{??????pkg.mCertificates?=?null;??????pkg.mSignatures?=?null;??????pkg.mSigningKeys?=?null;????????collectCertificates(pkg,?new?File(pkg.baseCodePath),?flags);??????……?? ?
接著調(diào)用了collectCertficates的一個(gè)重載版本:
[java]?view plaincopy
private?static?void?collectCertificates(Package?pkg,?File?apkFile,?int?flags)??????????????throws?PackageParserException?{??????final?String?apkPath?=?apkFile.getAbsolutePath();????????StrictJarFile?jarFile?=?null;??????try?{??????????jarFile?=?new?StrictJarFile(apkPath);??????????……?? 函數(shù)的開頭,首先創(chuàng)建了一個(gè)StrictJarFile(代碼位于libcore\luni\src\main\java\java\util\jar\StrictJarFile.java,編譯后存在于core.jar文件中)對(duì)象,先來(lái)看看其構(gòu)造函數(shù)中的內(nèi)容:
?
[java]?view plaincopy
public?StrictJarFile(String?fileName)?throws?IOException?{??????……??????try?{??????????HashMap<String,?byte[]>?metaEntries?=?getMetaEntries();??????????this.manifest?=?new?Manifest(metaEntries.get(JarFile.MANIFEST_NAME),?true);??????????this.verifier?=?new?JarVerifier(fileName,?manifest,?metaEntries);????????????isSigned?=?verifier.readCertificates()?&&?verifier.isSignedJar();??????????……?? ?
這里構(gòu)造了幾個(gè)重要的對(duì)象。首先,獲得了META-INF目錄下所有文件名及其字節(jié)流。然后是構(gòu)造了一個(gè)manifest對(duì)象,主要是用來(lái)處理對(duì)META-INF目錄下MANIFEST.MF文件的操作。接著,構(gòu)造了一個(gè)JarVeirifer(代碼位于libcore\luni\src\main\java\java\util\jar\JarVerifier.java文件中,編譯后存在于core.jar文件中)對(duì)象,這個(gè)對(duì)象主要實(shí)現(xiàn)了對(duì)Jar文件的驗(yàn)證工作,非常關(guān)鍵,后面的分析中會(huì)逐步提到。在構(gòu)造函數(shù)的最后,調(diào)用了JarVeirifer.readCertificates函數(shù):
[java]?view plaincopy
synchronized?boolean?readCertificates()?{??????if?(metaEntries.isEmpty())?{??????????return?false;??????}????????Iterator<String>?it?=?metaEntries.keySet().iterator();??????while?(it.hasNext())?{??????????String?key?=?it.next();??????????if?(key.endsWith(".DSA")?||?key.endsWith(".RSA")?||?key.endsWith(".EC"))?{??????????????verifyCertificate(key);??????????????it.remove();??????????}??????}??????return?true;??}?? 代碼遍歷所有META-INF目錄下的文件,找到以.DSA、.RSA或者.EC結(jié)尾的文件,以這些名字結(jié)尾的文件都是所謂的簽名證書文件。在本例中對(duì)應(yīng)的是META-INF目錄下的CERT.RSA簽名文件。然后調(diào)用JarVeirifer.verifyCertificate函數(shù):
?
[java]?view plaincopy
private?void?verifyCertificate(String?certFile)?{??????String?signatureFile?=?certFile.substring(0,?certFile.lastIndexOf('.'))?+?".SF";??????byte[]?sfBytes?=?metaEntries.get(signatureFile);??????if?(sfBytes?==?null)?{??????????return;??????}??????……??????byte[]?sBlockBytes?=?metaEntries.get(certFile);??????try?{?????????Certificate[]?signerCertChain?=?JarUtils.verifySignature(??????????????????????new?ByteArrayInputStream(sfBytes),??????????????????????new?ByteArrayInputStream(sBlockBytes));??????????if?(signerCertChain?!=?null)?{??????????????certificates.put(signatureFile,?signerCertChain);??????????}??????}?catch?(IOException?e)?{??????????return;??????}?catch?(GeneralSecurityException?e)?{??????????throw?failedVerification(jarName,?signatureFile);??????}??????……?? 函數(shù)開頭,首先找到與證書文件同名,但是以.SF結(jié)尾的簽名文件,本例中即為META-INF目錄下的CERT.SF文件。然后分別獲得簽名文件CERT.SF和證書文件CERT.RSA的字節(jié)流,調(diào)用JarUtils(代碼位于libcore\luni\src\main\java\org\apache\harmony\security\utils\JarUtils.java文件中,編譯后存在于core.jar文件中)的verifySignature函數(shù),驗(yàn)證CERT.RSA文件中包含的對(duì)CERT.SF文件的簽名是否正確。如果驗(yàn)證失敗,則會(huì)拋出GeneralSecurityException異常;而如果驗(yàn)證成功,則會(huì)返回簽名的證書鏈。回到JarVeirifer.verifyCertificate函數(shù),如果JarUtils.verifySignature驗(yàn)證失敗拋出異常,被捕獲后會(huì)接著向上拋出SecurityException異常;
?
?
[java]?view plaincopy
private?static?SecurityException?failedVerification(String?jarName,?String?signatureFile)?{??????throw?new?SecurityException(jarName?+?"?failed?verification?of?"?+?signatureFile);??}?? ?
而如果簽名驗(yàn)證成功的話,會(huì)將證書鏈保存在certifcates屬性變量中。而JarVerifier自己的isSignedJar函數(shù),就是判斷一下這個(gè)certificates屬性變量是否為空。
[java]?view plaincopy
boolean?isSignedJar()?{??????return?certificates.size()?>?0;??}?? 如果不為空就代表這個(gè)Jar是簽過(guò)名的,如果為空則代表其沒有簽過(guò)名。我們接著看JarVeirifer.verifyCertificate函數(shù):
?
[java]?view plaincopy
……??Attributes?attributes?=?new?Attributes();??HashMap<String,?Attributes>?entries?=?new?HashMap<String,?Attributes>();??try?{??????ManifestReader?im?=?new?ManifestReader(sfBytes,?attributes);??????im.readEntries(entries,?null);??}?catch?(IOException?e)?{??????return;??}????if?(attributes.get(Attributes.Name.SIGNATURE_VERSION)?==?null)?{??????return;??}????boolean?createdBySigntool?=?false;??String?createdBy?=?attributes.getValue("Created-By");??if?(createdBy?!=?null)?{??????createdBySigntool?=?createdBy.indexOf("signtool")?!=?-1;??}??……?? 函數(shù)接下來(lái)讀取了所謂簽名文件,也就是META-INF目錄下CERT.SF文件中的內(nèi)容。CERT.SF文件內(nèi)容大致如下:
?
接著,判斷了是否有“Signature-Version”屬性,如果沒有的話,直接返回。再下來(lái)判斷apk是否是由簽名工具簽的名,判斷條件就是在“Created-By”屬性值內(nèi)有沒有“signtool”字符串。本例中,簽名版本是“1.0”,并且不是用其它簽名工具簽的名。如果不是用其它工具簽名的話,接下來(lái)還會(huì)驗(yàn)證主屬性中是否有“SHA1-Digest-Manifest-Main-Attributes”屬性的值,這個(gè)屬性值記錄的是對(duì)META-INF目錄下MANIFEST.MF文件內(nèi),頭屬性塊的hash值。
?
[java]?view plaincopy
……??byte[]?manifestBytes?=?metaEntries.get(JarFile.MANIFEST_NAME);??if?(manifestBytes?==?null)?{??????return;??}??……??if?(mainAttributesEnd?>?0?&&?!createdBySigntool)?{??????String?digestAttribute?=?"-Digest-Manifest-Main-Attributes";??????if?(!verify(attributes,?digestAttribute,?manifestBytes,?0,?mainAttributesEnd,?false,?true))?{??????????throw?failedVerification(jarName,?signatureFile);??????}??}??……?? 接著調(diào)用了JarVerifier.verify對(duì)該摘要值進(jìn)行驗(yàn)證:
[java]?view plaincopy
private?boolean?verify(Attributes?attributes,?String?entry,?byte[]?data,??????????????int?start,?int?end,?boolean?ignoreSecondEndline,?boolean?ignorable)?{??????for?(int?i?=?0;?i?<?DIGEST_ALGORITHMS.length;?i++)?{??????????String?algorithm?=?DIGEST_ALGORITHMS[i];??????????String?hash?=?attributes.getValue(algorithm?+?entry);??????????if?(hash?==?null)?{??????????????continue;??????????}????????????MessageDigest?md;??????????try?{??????????????md?=?MessageDigest.getInstance(algorithm);??????????}?catch?(NoSuchAlgorithmException?e)?{??????????????continue;??????????}??????????if?(ignoreSecondEndline?&&?data[end?-?1]?==?'\n'?&&?data[end?-?2]?==?'\n')?{??????????????md.update(data,?start,?end?-?1?-?start);??????????}?else?{??????????????md.update(data,?start,?end?-?start);??????????}??????????byte[]?b?=?md.digest();??????????byte[]?hashBytes?=?hash.getBytes(StandardCharsets.ISO_8859_1);??????????return?MessageDigest.isEqual(b,?Base64.decode(hashBytes));??????}??????return?ignorable;??}?? JarVerifier.verify函數(shù)很簡(jiǎn)單,由于不知道到底是用什么算法算出的散列值,所以其會(huì)遍歷所有的可能算法。這些算法都預(yù)先定義在DIGEST_ALGORITHMS這個(gè)JarVerifier內(nèi)的靜態(tài)字符串?dāng)?shù)組變量中:
[java]?view plaincopy
private?static?final?String[]?DIGEST_ALGORITHMS?=?new?String[]?{??????"SHA-512",??????"SHA-384",??????"SHA-256",??????"SHA1",??};?? ?
可以看出,一共支持四種算法,本例中用到的是SHA1摘要算法。變量attributes表示的是一個(gè)屬性塊,而變量entry是要在attributes屬性塊中查找的屬性名的一部分,它會(huì)與摘要算法的名稱拼接成正真的屬性名。接著會(huì)將在屬性塊中,對(duì)應(yīng)屬性名的屬性值取出來(lái),與data數(shù)據(jù)塊中start到end之間的數(shù)據(jù),用同樣算法算出的摘要值進(jìn)行比較,如果一致就返回“true”,不一致則返回“false”。
而ignorable表示這個(gè)驗(yàn)證是否可忽略,也就是說(shuō)如果要查找的屬性不存在的情況下,如果可忽略,則仍然返回“true”。但如果屬性值確實(shí)存在則這項(xiàng)對(duì)判斷結(jié)果沒有任何影響。本例中,根本沒有這個(gè)屬性,但是驗(yàn)證任然是通過(guò)的,因?yàn)樵谡{(diào)用的時(shí)候,最后一個(gè)參數(shù)ignorable被設(shè)置成了“true”。
?
[java]?view plaincopy
????……??????String?digestAttribute?=?createdBySigntool???"-Digest"?:?"-Digest-Manifest";??????if?(!verify(attributes,?digestAttribute,?manifestBytes,?0,?manifestBytes.length,?false,?false))?{??????????Iterator<Map.Entry<String,?Attributes>>?it?=?entries.entrySet().iterator();??????????while?(it.hasNext())?{??????????????Map.Entry<String,?Attributes>?entry?=?it.next();??????????????Manifest.Chunk?chunk?=?manifest.getChunk(entry.getKey());??????????????if?(chunk?==?null)?{??????????????????return;??????????????}??????????????if?(!verify(entry.getValue(),?"-Digest",?manifestBytes,??????????????????????chunk.start,?chunk.end,?createdBySigntool,?false))?{??????????????????throw?invalidDigest(signatureFile,?entry.getKey(),?jarName);??????????????}??????????}??????}??????metaEntries.put(signatureFile,?null);??????signatures.put(signatureFile,?entries);??}?? ?
JarVeirifer.verifyCertificate剩下的代碼就很簡(jiǎn)單了,會(huì)比較MANIFEST.MF文件的整體摘要值和每一個(gè)屬性塊的摘要值,與CERT.SF文件中記錄的是否一致。如果都驗(yàn)證通過(guò)的話,會(huì)將該簽名文件的信息加到metaEntries和signatures屬性變量中去。
所以,在StrictJarFile構(gòu)造的過(guò)程中就已經(jīng)完成了兩步驗(yàn)證:一是通過(guò)在CERT.RSA文件中記錄的簽名信息,驗(yàn)證了CERT.SF沒有被篡改過(guò);二是通過(guò)CERT.SF文件中記錄的摘要值,驗(yàn)證了MANIFEST.MF沒有被修改過(guò)。
所以,到目前為止,還有一步?jīng)]有被驗(yàn)證,即apk內(nèi)文件的摘要值要與MANIFEST.MF文件中記錄的一致。接下來(lái),讓我們繼續(xù)回到PackageParser. collectCertificates函數(shù)中:
?
[java]?view plaincopy
……??final?ZipEntry?manifestEntry?=?jarFile.findEntry(ANDROID_MANIFEST_FILENAME);??if?(manifestEntry?==?null)?{??????throw?new?PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,??????????????????"Package?"?+?apkPath?+?"?has?no?manifest");??}????final?List<ZipEntry>?toVerify?=?new?ArrayList<>();??toVerify.add(manifestEntry);????if?((flags?&?PARSE_IS_SYSTEM)?==?0)?{??????final?Iterator<ZipEntry>?i?=?jarFile.iterator();??????while?(i.hasNext())?{??????????final?ZipEntry?entry?=?i.next();????????????if?(entry.isDirectory())?continue;??????????if?(entry.getName().startsWith("META-INF/"))?continue;??????????if?(entry.getName().equals(ANDROID_MANIFEST_FILENAME))?continue;????????????toVerify.add(entry);??????}??}??……?? 接下來(lái)的代碼主要是用來(lái)確定,到底哪些文件需要進(jìn)行驗(yàn)證。AndroidManifest.xml無(wú)論如何都要驗(yàn)證。如果不是系統(tǒng),也就是普通的應(yīng)用程序安裝,必須要驗(yàn)證除去位于META-INF目錄下所有文件之外的所有剩下的文件。
?
?
[java]?view plaincopy
……??for?(ZipEntry?entry?:?toVerify)?{??????final?Certificate[][]?entryCerts?=?loadCertificates(jarFile,?entry);??????if?(ArrayUtils.isEmpty(entryCerts))?{??????????throw?new?PackageParserException(??????????????????????INSTALL_PARSE_FAILED_NO_CERTIFICATES,??????????????????????"Package?"?+?apkPath?+?"?has?no?certificates?at?entry?"?+?entry.getName());??????}??????final?Signature[]?entrySignatures?=?convertToSignatures(entryCerts);??????……?? 接著是逐項(xiàng)驗(yàn)證前面羅列出的apk中的各個(gè)文件。對(duì)每個(gè)文件,都接著調(diào)用了PackageParser.loadCertificates函數(shù):
?
?
[java]?view plaincopy
private?static?Certificate[][]?loadCertificates(StrictJarFile?jarFile,?ZipEntry?entry)??????????????throws?PackageParserException?{??????InputStream?is?=?null;??????try?{??????????is?=?jarFile.getInputStream(entry);??????????readFullyIgnoringContents(is);??????????return?jarFile.getCertificateChains(entry);??????}?catch?(IOException?|?RuntimeException?e)?{??????????throw?new?PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,??????????????????????"Failed?reading?"?+?entry.getName()?+?"?in?"?+?jarFile,?e);??????}?finally?{??????????IoUtils.closeQuietly(is);??????}??}?? 貌似沒有什么特別的,只是對(duì)apk內(nèi)的文件創(chuàng)建了一個(gè)輸入流,并且通過(guò)函數(shù)PackageParser.readFullyIgnoringContents全讀了一遍,而且通過(guò)函數(shù)名可以看出,具體讀出什么內(nèi)容并不重要。我們先來(lái)看看StrictJarFile.getInputStream函數(shù):
?
?
[java]?view plaincopy
public?InputStream?getInputStream(ZipEntry?ze)?{??????final?InputStream?is?=?getZipInputStream(ze);????????if?(isSigned)?{??????????JarVerifier.VerifierEntry?entry?=?verifier.initEntry(ze.getName());??????????if?(entry?==?null)?{??????????????return?is;??????????}????????????return?new?JarFile.JarFileInputStream(is,?ze.getSize(),?entry);??????}????????return?is;??}?? 重點(diǎn)要關(guān)注兩個(gè)函數(shù)調(diào)用,一是JarVerifier.initEntry,二是JarFile.JarFileInputStream。好,我們先來(lái)看第一個(gè):
?
?
[java]?view plaincopy
VerifierEntry?initEntry(String?name)?{??????if?(manifest?==?null?||?signatures.isEmpty())?{??????????return?null;??????}????????Attributes?attributes?=?manifest.getAttributes(name);??????if?(attributes?==?null)?{??????????return?null;??????}????????ArrayList<Certificate[]>?certChains?=?new?ArrayList<Certificate[]>();??????Iterator<Map.Entry<String,?HashMap<String,?Attributes>>>?it?=?signatures.entrySet().iterator();??????while?(it.hasNext())?{??????????Map.Entry<String,?HashMap<String,?Attributes>>?entry?=?it.next();??????????HashMap<String,?Attributes>?hm?=?entry.getValue();??????????if?(hm.get(name)?!=?null)?{??????????????String?signatureFile?=?entry.getKey();??????????????Certificate[]?certChain?=?certificates.get(signatureFile);??????????????if?(certChain?!=?null)?{??????????????????certChains.add(certChain);??????????????}??????????}??????}????????if?(certChains.isEmpty())?{??????????return?null;??????}??????Certificate[][]?certChainsArray?=?certChains.toArray(new?Certificate[certChains.size()][]);????????for?(int?i?=?0;?i?<?DIGEST_ALGORITHMS.length;?i++)?{??????????final?String?algorithm?=?DIGEST_ALGORITHMS[i];??????????final?String?hash?=?attributes.getValue(algorithm?+?"-Digest");??????????if?(hash?==?null)?{??????????????continue;??????????}??????????byte[]?hashBytes?=?hash.getBytes(StandardCharsets.ISO_8859_1);????????????try?{??????????????return?new?VerifierEntry(name,?MessageDigest.getInstance(algorithm),?hashBytes,??????????????????????????certChainsArray,?verifiedEntries);??????????}?catch?(NoSuchAlgorithmException?ignored)?{??????????}??????}??????return?null;??}?? ?
該函數(shù)主要的用途就是構(gòu)造一個(gè)JarVerifer.VerifierEntry對(duì)象:
要構(gòu)造這個(gè)對(duì)象,必須事先準(zhǔn)備好參數(shù)。第一個(gè)參數(shù)很簡(jiǎn)單,就是要驗(yàn)證的文件名,直接將name傳進(jìn)來(lái)就好了。第二個(gè)參數(shù)是計(jì)算摘要的對(duì)象,可以通過(guò)MessageDigest.getInstance獲得,不過(guò)要先告知到底要用哪個(gè)摘要算法,同樣也是通過(guò)查看MANIFEST.MF文件中對(duì)應(yīng)名字的屬性值來(lái)決定的。本例中的MANIFEST.MF文件格式大致如下:
所以可以知道所用的摘要算法是SHA1。第三個(gè)參數(shù)是對(duì)應(yīng)文件的摘要值,這是通過(guò)讀取MANIFEST.MF文件獲得的。第四個(gè)參數(shù)是證書鏈,即對(duì)該apk文件簽名的所有證書鏈信息。為什么是二維數(shù)組呢?這是因?yàn)锳ndroid允許用多個(gè)證書對(duì)apk進(jìn)行簽名,但是它們的證書文件名必須不同。最后一個(gè)參數(shù)是已經(jīng)驗(yàn)證過(guò)的文件列表,VerifierEntry在完成了對(duì)指定文件的摘要驗(yàn)證之后會(huì)將該文件的信息加到其中。
生成好了entry之后,我們接下來(lái)看JarFile(代碼位于)中的JarFileInputStream函數(shù)的實(shí)現(xiàn):
?
[java]?view plaincopy
static?final?class?JarFileInputStream?extends?FilterInputStream?{??????private?final?JarVerifier.VerifierEntry?entry;????????private?long?count;??????private?boolean?done?=?false;????????JarFileInputStream(InputStream?is,?long?size,?JarVerifier.VerifierEntry?e)?{??????????super(is);??????????entry?=?e;????????????count?=?size;??????}??????……?? 其構(gòu)造函數(shù)沒有什么特別的,只是完成了賦值的操作。所以,調(diào)用StrictJarFile.getInputStream函數(shù)之后,實(shí)際返回的是一個(gè)JarFileInputStream對(duì)象。在獲得了這個(gè)輸入流對(duì)象后,緊接著,PackageParser.loadCertificates會(huì)調(diào)用PackageParser .readFullyIgnoringContents對(duì)這個(gè)輸入流進(jìn)行讀取的操作:
[java]?view plaincopy
public?static?long?readFullyIgnoringContents(InputStream?in)?throws?IOException?{??????byte[]?buffer?=?sBuffer.getAndSet(null);??????if?(buffer?==?null)?{??????????buffer?=?new?byte[4096];??????}????????int?n?=?0;??????int?count?=?0;??????while?((n?=?in.read(buffer,?0,?buffer.length))?!=?-1)?{??????????count?+=?n;??????}????????sBuffer.set(buffer);??????return?count;??}?? 沒什么特別的,只是調(diào)用了InputStream的read函數(shù),直到讀完為止,而且只是返回了讀到了多少個(gè)字節(jié),并沒有返回讀到的內(nèi)容,所以讀到什么內(nèi)容它并不關(guān)心。由于實(shí)際傳進(jìn)來(lái)的是InputStream的子類,這里也就是JarFileInputStream,它對(duì)read函數(shù)進(jìn)行了重載,看它是如何實(shí)現(xiàn)的:
?
?
[java]?view plaincopy
public?int?read(byte[]?buffer,?int?byteOffset,?int?byteCount)?throws?IOException?{??????if?(done)?{??????????return?-1;??????}??????if?(count?>?0)?{??????????int?r?=?super.read(buffer,?byteOffset,?byteCount);??????????if?(r?!=?-1)?{??????????????int?size?=?r;??????????????if?(count?<?size)?{??????????????????size?=?(int)?count;??????????????}??????????????entry.write(buffer,?byteOffset,?size);??????????????count?-=?size;??????????}?else?{??????????????count?=?0;??????????}??????????if?(count?==?0)?{??????????????done?=?true;??????????????entry.verify();??????????}??????????return?r;??????}?else?{??????????done?=?true;??????????entry.verify();??????????return?-1;??????}??}?? 玄機(jī)原來(lái)在這里,這里的JarFileInputStream.read確實(shí)會(huì)調(diào)用其父類的read讀取指定的apk內(nèi)文件的內(nèi)容,并且將其傳給JarVerifier.VerifierEntry.write函數(shù)。當(dāng)文件讀完后,會(huì)接著調(diào)用JarVerifier.VerifierEntry.verify函數(shù)對(duì)其進(jìn)行驗(yàn)證。JarVerifier.VerifierEntry.write函數(shù)非常簡(jiǎn)單:
?
?
[java]?view plaincopy
public?void?write(byte[]?buf,?int?off,?int?nbytes)?{??????digest.update(buf,?off,?nbytes);??}?? ?
就是將讀到的文件的內(nèi)容傳給digest,這個(gè)digest就是前面在構(gòu)造JarVerifier.VerifierEntry傳進(jìn)來(lái)的,對(duì)應(yīng)于在MANIFEST.MF文件中指定的摘要算法。萬(wàn)事具備,接下來(lái)想要驗(yàn)證就很簡(jiǎn)單了:
[java]?view plaincopy
void?verify()?{??????byte[]?d?=?digest.digest();??????if?(!MessageDigest.isEqual(d,?Base64.decode(hash)))?{??????????throw?invalidDigest(JarFile.MANIFEST_NAME,?name,?name);??????}??????verifiedEntries.put(name,?certChains);??}?? 通過(guò)digest就可以算出apk內(nèi)指定文件的真實(shí)摘要值。而記錄在MANIFEST.MF文件中對(duì)應(yīng)該文件的摘要值,也在構(gòu)造JarVerifier.VerifierEntry時(shí)傳遞給了hash變量。不過(guò)這個(gè)hash值是經(jīng)過(guò)Base64編碼的。所以在比較之前,必須通過(guò)Base64解碼。如果不一致的話,會(huì)拋出SecurityException異常:
?
[java]?view plaincopy
private?static?SecurityException?invalidDigest(String?signatureFile,?String?name,??????????String?jarName)?{??????throw?new?SecurityException(signatureFile?+?"?has?invalid?digest?for?"?+?name?+??????????????"?in?"?+?jarName);??}?? 至此,最后一步驗(yàn)證,即apk內(nèi)所有文件的摘要值要和在MANIFEST.MF文件中記錄的一致,也已經(jīng)完成了。這還沒完,PackageParser.collectCertificates還要接著驗(yàn)證apk文件中的每個(gè)文件對(duì)應(yīng)的簽名要和第一個(gè)文件一致:
?
?
[java]?view plaincopy
……??if?(pkg.mCertificates?==?null)?{??????pkg.mCertificates?=?entryCerts;??????pkg.mSignatures?=?entrySignatures;??????pkg.mSigningKeys?=?new?ArraySet<PublicKey>();??????for?(int?i=0;?i?<?entryCerts.length;?i++)?{??????????pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey());??????}??}?else?{??????if?(!Signature.areExactMatch(pkg.mSignatures,?entrySignatures))?{??????????throw?new?PackageParserException(??????????????INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,???????????????"Package?"?+?apkPath?+?"?has?mismatched?certificates?at?entry?"???????????????+?entry.getName());??????}??}??……?? ?
到這里,apk安裝時(shí)的簽名驗(yàn)證過(guò)程都已經(jīng)分析完了,來(lái)總結(jié)一下:
?
所有有關(guān)apk文件的簽名驗(yàn)證工作都是在JarVerifier里面做的,一共分成三步;JarVeirifer.verifyCertificate主要做了兩步。首先,使用證書文件(在META-INF目錄下,以.DSA、.RSA或者.EC結(jié)尾的文件)檢驗(yàn)簽名文件(在META-INF目錄下,和證書文件同名,但擴(kuò)展名為.SF的文件)是沒有被修改過(guò)的。然后,使用簽名文件,檢驗(yàn)MANIFEST.MF文件中的內(nèi)容也沒有被篡改過(guò);JarVerifier.VerifierEntry.verify做了最后一步驗(yàn)證,即保證apk文件中包含的所有文件,對(duì)應(yīng)的摘要值與MANIFEST.MF文件中記錄的一致。 ?
轉(zhuǎn)載于:https://www.cnblogs.com/mjblogs/p/5066880.html
總結(jié)
以上是生活随笔為你收集整理的Android应用程序签名过程和解析过程分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。