日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android应用程序签名过程和解析过程分析

發(fā)布時間:2023/12/31 Android 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android应用程序签名过程和解析过程分析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在正式解釋Android應(yīng)用程序簽名過程之前,作為鋪墊,還得先講講最基本的一些概念。

?

非對稱加密算法

?

非對稱加密算法需要兩個密鑰:公開密鑰(簡稱公鑰)和私有密鑰(簡稱私鑰)。公鑰與私鑰是一對,如果用公鑰對數(shù)據(jù)進(jìn)行加密,只有用對應(yīng)的私鑰才能解密;如果用私鑰對數(shù)據(jù)進(jìn)行加密,那么只有用對應(yīng)的公鑰才能解密。因為加密和解密使用的是兩個不同的密鑰,所以這種算法叫作非對稱加密算法。

非對稱加密算法是數(shù)字簽名和數(shù)字證書的基礎(chǔ),大家非常熟悉的RSA就是非對稱加密算法的一種實現(xiàn)。

消息摘要算法

消息摘要算法(Message Digest Algorithm)是一種能產(chǎn)生特殊輸出格式的算法,其原理是根據(jù)一定的運算規(guī)則對原始數(shù)據(jù)進(jìn)行某種形式的信息提取,被提取出的信息就被稱作原始數(shù)據(jù)的消息摘要。著名的摘要算法有RSA公司的MD5算法和SHA-1算法及其大量的變體。

消息摘要的主要特點有:

?

1)無論輸入的消息有多長,計算出來的消息摘要的長度總是固定的。例如應(yīng)用MD5算法摘要的消息有128個比特位,用SHA-1算法摘要的消息最終有160比特位的輸出。

2)一般來說(不考慮碰撞的情況下),只要輸入的原始數(shù)據(jù)不同,對其進(jìn)行摘要以后產(chǎn)生的消息摘要也必不相同,即使原始數(shù)據(jù)稍有改變,輸出的消息摘要便完全不同。但是,相同的輸入必會產(chǎn)生相同的輸出。

3)具有不可逆性,即只能進(jìn)行正向的信息摘要,而無法從摘要中恢復(fù)出任何的原始消息。

數(shù)字簽名和數(shù)字證書

其實數(shù)字簽名的概念很簡單。大家知道,要確保可靠通信,必須要解決兩個問題:首先,要確定消息的來源確實是其申明的那個人;其次,要保證信息在傳遞的過程中不被第三方篡改,即使被篡改了,也可以發(fā)覺出來。

所謂數(shù)字簽名,就是為了解決這兩個問題而產(chǎn)生的,它是對前面提到的非對稱加密技術(shù)與數(shù)字摘要技術(shù)的一個具體的應(yīng)用。

對于消息的發(fā)送者來說,先要生成一對公私鑰對,將公鑰給消息的接收者。

如果消息的發(fā)送者有一天想給消息接收者發(fā)消息,在發(fā)送的信息中,除了要包含原始的消息外,還要加上另外一段消息。這段消息通過如下兩步生成:

1)對要發(fā)送的原始消息提取消息摘要;

2)對提取的信息摘要用自己的私鑰加密。

通過這兩步得出的消息,就是所謂的原始信息的數(shù)字簽名。

而對于信息的接收者來說,他所收到的信息,將包含兩個部分,一是原始的消息內(nèi)容,二是附加的那段數(shù)字簽名。他將通過以下三步來驗證消息的真?zhèn)?#xff1a;

1)對原始消息部分提取消息摘要,注意這里使用的消息摘要算法要和發(fā)送方使用的一致;

2)對附加上的那段數(shù)字簽名,使用預(yù)先得到的公鑰解密;

3)比較前兩步所得到的兩段消息是否一致。如果一致,則表明消息確實是期望的發(fā)送者發(fā)的,且內(nèi)容沒有被篡改過;相反,如果不一致,則表明傳送的過程中一定出了問題,消息不可信。

通過這種所謂的數(shù)字簽名技術(shù),確實可以有效解決可靠通信的問題。如果原始消息在傳送的過程中被篡改了,那么在消息接收者那里,對被篡改的消息提取的摘要肯定和原始的不一樣。并且,由于篡改者沒有消息發(fā)送方的私鑰,即使他可以重新算出被篡改消息的摘要,也不能偽造出數(shù)字簽名。

所以,綜上所述,數(shù)字簽名其實就是只有信息的發(fā)送者才能產(chǎn)生的別人無法偽造的一段數(shù)字串,這段數(shù)字串同時也是對信息的發(fā)送者發(fā)送信息真實性的一個有效證明。

不知道大家有沒有注意,前面講的這種數(shù)字簽名方法,有一個前提,就是消息的接收者必須要事先得到正確的公鑰。如果一開始公鑰就被別人篡改了,那壞人就會被你當(dāng)成好人,而真正的消息發(fā)送者給你發(fā)的消息會被你視作無效的。而且,很多時候根本就不具備事先溝通公鑰的信息通道。那么如何保證公鑰的安全可信呢?這就要靠數(shù)字證書來解決了。

所謂數(shù)字證書,一般包含以下一些內(nèi)容:

?

  • 證書的發(fā)布機構(gòu)(Issuer)
  • 證書的有效期(Validity)
  • 消息發(fā)送方的公鑰
  • 證書所有者(Subject)
  • 數(shù)字簽名所使用的算法
  • 數(shù)字簽名

?

可以看出,數(shù)字證書其實也用到了數(shù)字簽名技術(shù)。只不過要簽名的內(nèi)容是消息發(fā)送方的公鑰,以及一些其它信息。但與普通數(shù)字簽名不同的是,數(shù)字證書中簽名者不是隨隨便便一個普通的機構(gòu),而是要有一定公信力的機構(gòu)。這就好像你的大學(xué)畢業(yè)證書上簽名的一般都是德高望重的校長一樣。一般來說,這些有公信力機構(gòu)的根證書已經(jīng)在設(shè)備出廠前預(yù)先安裝到了你的設(shè)備上了。所以,數(shù)字證書可以保證數(shù)字證書里的公鑰確實是這個證書的所有者的,或者證書可以用來確認(rèn)對方的身份。數(shù)字證書主要是用來解決公鑰的安全發(fā)放問題。

綜上所述,總結(jié)一下,數(shù)字簽名和簽名驗證的大體流程如下圖所示:

?

Android應(yīng)用程序簽名流程

?

大家知道,Android采用的是開放的生態(tài)系統(tǒng),任何人都可以開發(fā)和發(fā)布應(yīng)用程序給別人使用。不像iOS,在沒有被破解的情況下只能通過App Store安裝應(yīng)用,Android在打開了“Unknow Sources”選項后,可以安裝任何來源的應(yīng)用程序,可以是第三方市場,可以是自己開發(fā)的應(yīng)用,也可以從論壇下載。

那么問題來了,對于有一些不懷好意的人,完全可以拿到一個原生的應(yīng)用,然后加入一些惡意的代碼,再發(fā)布出去,誘使別人去安裝,達(dá)到不可告人的目的。

有沒有什么辦法可以防止應(yīng)用程序在傳送的過程中被第三方惡意篡改呢?Google因此引入了應(yīng)用程序簽名機制。

它是如何工作的呢?我們先來看看簽名前后,一個apk文件到底發(fā)生了哪些變化。

首先,在沒簽名之前,apk文件內(nèi)的目錄結(jié)構(gòu)是這樣的:

?

而簽名之后,會變成這樣:

?

可以看到,多出來了一個META-INF目錄。可以肯定的是,簽名的機關(guān)就在這個目錄中,里面有三個文件:

?

其實,在Android的源代碼里包含了一個工具,可以對apk文件進(jìn)行簽名,具體的代碼位置在build\tools\signapk目錄下,通過分析其中的SignApk.java文件,可以大致了解簽名的過程。其流程大致有如下幾步:

1)打開待簽名的apk文件(由于apk其實是一個用zip壓縮的文件,其實就是用zip解壓整個apk文件),逐一遍歷里面的所有條目,如果是目錄就跳過,如果是一個文件,就用SHA1(或者SHA256)消息摘要算法提取出該文件的摘要然后進(jìn)行BASE64編碼后,作為“SHA1-Digest”屬性的值寫入到MANIFEST.MF文件中的一個塊中。該塊有一個“Name”屬性,其值就是該文件在apk包中的路徑。


2)計算這個MANIFEST.MF文件的整體SHA1值,再經(jīng)過BASE64編碼后,記錄在CERT.SF主屬性塊(在文件頭上)的“SHA1-Digest-Manifest”屬性值值下。

然后,再逐條計算MANIFEST.MF文件中每一個塊的SHA1,并經(jīng)過BASE64編碼后,記錄在CERT.SF中的同名塊中,屬性的名字是“SHA1-Digest”。

3)把之前生成的?CERT.SF文件,?用私鑰計算出簽名, 然后將簽名以及包含公鑰信息的數(shù)字證書一同寫入??CERT.RSA??中保存。CERT.RSA是一個滿足PKCS7格式的文件,可以通過openssl工具來查看簽名證書的信息。在Ubuntu或者在Windows上使用Cygwin,敲入以下命令:

?

[plain]?view plaincopy
  • openssl?pkcs7?-inform?DER?-in?CERT.RSA?-noout?-print_certs?–text??
  • 可以得到如下輸出:

    ?

    下面我們來看看,如果apk文件被篡改后會發(fā)生什么。

    ?

    首先,如果你改變了apk包中的任何文件,那么在apk安裝校驗時,改變后的文件摘要信息與MANIFEST.MF的檢驗信息不同,于是驗證失敗,程序就不能成功安裝。

    其次,如果你對更改的過的文件相應(yīng)的算出新的摘要值,然后更改MANIFEST.MF文件里面對應(yīng)的屬性值,那么必定與CERT.SF文件中算出的摘要值不一樣,照樣驗證失敗。

    最后,如果你還不死心,繼續(xù)計算MANIFEST.MF的摘要值,相應(yīng)的更改CERT.SF里面的值,那么數(shù)字簽名值必定與CERT.RSA文件中記錄的不一樣,還是失敗。

    那么能不能繼續(xù)偽造數(shù)字簽名呢?不可能,因為沒有數(shù)字證書對應(yīng)的私鑰。

    所以,如果要重新打包后的應(yīng)用程序能再Android設(shè)備上安裝,必須對其進(jìn)行重簽名。

    總結(jié)

    1)Android應(yīng)用程序簽名只是用來解決發(fā)布的應(yīng)用不被別人篡改的,其并不會對應(yīng)用程序本身進(jìn)行加密,這點不同于Windows Phone和iOS。

    2)Android并不要求所有應(yīng)用程序的簽名證書都由可信任CA的根證書簽名,通過這點保證了其生態(tài)系統(tǒng)的開放性,所有人都可以用自己生成的證書對應(yīng)用程序簽名。

    3)如果想修改一個已經(jīng)發(fā)布的應(yīng)用程序,哪怕是修改一張圖片,都必須對其進(jìn)行重新簽名。但是,簽原始應(yīng)用的私鑰一般是拿不到的(肯定在原始應(yīng)用程序開發(fā)者的手上,且不可能公布出去),所以只能用另外一組公私鑰對,生成一個新的證書,對重打包的應(yīng)用進(jìn)行簽名。所以重打包的apk中所帶證書的公鑰肯定和原始應(yīng)用不一樣。同時,在手機上如果想安裝一個應(yīng)用程序,應(yīng)用程序安裝器會先檢查相同包名的應(yīng)用是否已經(jīng)被安裝過,如果已經(jīng)安裝過,會繼續(xù)判斷已經(jīng)安裝的應(yīng)用和將要安裝的應(yīng)用,其所攜帶的數(shù)字證書中的公鑰是否一致。如果相同,則繼續(xù)安裝;而如果不同,則會提示用戶先卸載前面已安裝的應(yīng)用。通過這種方式來提示用戶,前后兩個應(yīng)用是不同開發(fā)者簽名的,可能有一個是李鬼。

    ?

     

    Android應(yīng)用程序簽名驗證過程分析

    ?

    在前面的《Android應(yīng)用程序簽名過程分析》中,我大致分析了Android應(yīng)用程序簽名的過程,接下來我將結(jié)合源代碼,分析一下Android應(yīng)用程序在安裝過程中對簽名進(jìn)行驗證的過程。

    我們還是用前面的例子分析,假設(shè)簽名后,apk文件中多了一個META-INF目錄,里面有三個文件,分別是MANIFEST.MF、CERT.SF和CERT.RSA:

    ?

    通過前面的分析,我們可以知道,MANIFEST.MF中記錄的是apk中所有文件的摘要值;CERT.SF中記錄的是對MANIFEST.MF的摘要值,包括整個文件的摘要,還有文件中每一項的摘要;而CERT.RSA中記錄的是對CERT.SF文件的簽名,以及簽名的公鑰。

    大家知道,Android平臺上所有應(yīng)用程序安裝都是由PackageManangerService(代碼位于frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java)來管理的,Android的安裝流程非常復(fù)雜,與簽名驗證相關(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文件中)是一個apk包的解析器,接下來我們來看其collectCertificates函數(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的一個重載版本:

    [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)建了一個StrictJarFile(代碼位于libcore\luni\src\main\java\java\util\jar\StrictJarFile.java,編譯后存在于core.jar文件中)對象,先來看看其構(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)造了幾個重要的對象。首先,獲得了META-INF目錄下所有文件名及其字節(jié)流。然后是構(gòu)造了一個manifest對象,主要是用來處理對META-INF目錄下MANIFEST.MF文件的操作。接著,構(gòu)造了一個JarVeirifer(代碼位于libcore\luni\src\main\java\java\util\jar\JarVerifier.java文件中,編譯后存在于core.jar文件中)對象,這個對象主要實現(xiàn)了對Jar文件的驗證工作,非常關(guān)鍵,后面的分析中會逐步提到。在構(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é)尾的文件都是所謂的簽名證書文件。在本例中對應(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ù),驗證CERT.RSA文件中包含的對CERT.SF文件的簽名是否正確。如果驗證失敗,則會拋出GeneralSecurityException異常;而如果驗證成功,則會返回簽名的證書鏈。回到JarVeirifer.verifyCertificate函數(shù),如果JarUtils.verifySignature驗證失敗拋出異常,被捕獲后會接著向上拋出SecurityException異常;

    ?

    ?

    [java]?view plaincopy
  • private?static?SecurityException?failedVerification(String?jarName,?String?signatureFile)?{??
  • ????throw?new?SecurityException(jarName?+?"?failed?verification?of?"?+?signatureFile);??
  • }??
  • ?

    而如果簽名驗證成功的話,會將證書鏈保存在certifcates屬性變量中。而JarVerifier自己的isSignedJar函數(shù),就是判斷一下這個certificates屬性變量是否為空。

    [java]?view plaincopy
  • boolean?isSignedJar()?{??
  • ????return?certificates.size()?>?0;??
  • }??
  • 如果不為空就代表這個Jar是簽過名的,如果為空則代表其沒有簽過名。我們接著看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ù)接下來讀取了所謂簽名文件,也就是META-INF目錄下CERT.SF文件中的內(nèi)容。CERT.SF文件內(nèi)容大致如下:

    ?


    接著,判斷了是否有“Signature-Version”屬性,如果沒有的話,直接返回。再下來判斷apk是否是由簽名工具簽的名,判斷條件就是在“Created-By”屬性值內(nèi)有沒有“signtool”字符串。本例中,簽名版本是“1.0”,并且不是用其它簽名工具簽的名。如果不是用其它工具簽名的話,接下來還會驗證主屬性中是否有“SHA1-Digest-Manifest-Main-Attributes”屬性的值,這個屬性值記錄的是對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對該摘要值進(jì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ù)很簡單,由于不知道到底是用什么算法算出的散列值,所以其會遍歷所有的可能算法。這些算法都預(yù)先定義在DIGEST_ALGORITHMS這個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表示的是一個屬性塊,而變量entry是要在attributes屬性塊中查找的屬性名的一部分,它會與摘要算法的名稱拼接成正真的屬性名。接著會將在屬性塊中,對應(yīng)屬性名的屬性值取出來,與data數(shù)據(jù)塊中start到end之間的數(shù)據(jù),用同樣算法算出的摘要值進(jìn)行比較,如果一致就返回“true”,不一致則返回“false”。

    而ignorable表示這個驗證是否可忽略,也就是說如果要查找的屬性不存在的情況下,如果可忽略,則仍然返回“true”。但如果屬性值確實存在則這項對判斷結(jié)果沒有任何影響。本例中,根本沒有這個屬性,但是驗證任然是通過的,因為在調(diào)用的時候,最后一個參數(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剩下的代碼就很簡單了,會比較MANIFEST.MF文件的整體摘要值和每一個屬性塊的摘要值,與CERT.SF文件中記錄的是否一致。如果都驗證通過的話,會將該簽名文件的信息加到metaEntries和signatures屬性變量中去。

    所以,在StrictJarFile構(gòu)造的過程中就已經(jīng)完成了兩步驗證:一是通過在CERT.RSA文件中記錄的簽名信息,驗證了CERT.SF沒有被篡改過;二是通過CERT.SF文件中記錄的摘要值,驗證了MANIFEST.MF沒有被修改過。

    所以,到目前為止,還有一步?jīng)]有被驗證,即apk內(nèi)文件的摘要值要與MANIFEST.MF文件中記錄的一致。接下來,讓我們繼續(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);??
  • ????}??
  • }??
  • ……??
  • 接下來的代碼主要是用來確定,到底哪些文件需要進(jìn)行驗證。AndroidManifest.xml無論如何都要驗證。如果不是系統(tǒng),也就是普通的應(yīng)用程序安裝,必須要驗證除去位于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);??
  • ????……??
  • 接著是逐項驗證前面羅列出的apk中的各個文件。對每個文件,都接著調(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);??
  • ????}??
  • }??
  • 貌似沒有什么特別的,只是對apk內(nèi)的文件創(chuàng)建了一個輸入流,并且通過函數(shù)PackageParser.readFullyIgnoringContents全讀了一遍,而且通過函數(shù)名可以看出,具體讀出什么內(nè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;??
  • }??
  • 重點要關(guān)注兩個函數(shù)調(diào)用,一是JarVerifier.initEntry,二是JarFile.JarFileInputStream。好,我們先來看第一個:

    ?

    ?

    [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)造一個JarVerifer.VerifierEntry對象:

    要構(gòu)造這個對象,必須事先準(zhǔn)備好參數(shù)。第一個參數(shù)很簡單,就是要驗證的文件名,直接將name傳進(jìn)來就好了。第二個參數(shù)是計算摘要的對象,可以通過MessageDigest.getInstance獲得,不過要先告知到底要用哪個摘要算法,同樣也是通過查看MANIFEST.MF文件中對應(yīng)名字的屬性值來決定的。本例中的MANIFEST.MF文件格式大致如下:

    所以可以知道所用的摘要算法是SHA1。第三個參數(shù)是對應(yīng)文件的摘要值,這是通過讀取MANIFEST.MF文件獲得的。第四個參數(shù)是證書鏈,即對該apk文件簽名的所有證書鏈信息。為什么是二維數(shù)組呢?這是因為Android允許用多個證書對apk進(jìn)行簽名,但是它們的證書文件名必須不同。最后一個參數(shù)是已經(jīng)驗證過的文件列表,VerifierEntry在完成了對指定文件的摘要驗證之后會將該文件的信息加到其中。

    生成好了entry之后,我們接下來看JarFile(代碼位于)中的JarFileInputStream函數(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ù)之后,實際返回的是一個JarFileInputStream對象。在獲得了這個輸入流對象后,緊接著,PackageParser.loadCertificates會調(diào)用PackageParser .readFullyIgnoringContents對這個輸入流進(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ù),直到讀完為止,而且只是返回了讀到了多少個字節(jié),并沒有返回讀到的內(nèi)容,所以讀到什么內(nèi)容它并不關(guān)心。由于實際傳進(jìn)來的是InputStream的子類,這里也就是JarFileInputStream,它對read函數(shù)進(jìn)行了重載,看它是如何實現(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;??
  • ????}??
  • }??
  • 玄機原來在這里,這里的JarFileInputStream.read確實會調(diào)用其父類的read讀取指定的apk內(nèi)文件的內(nèi)容,并且將其傳給JarVerifier.VerifierEntry.write函數(shù)。當(dāng)文件讀完后,會接著調(diào)用JarVerifier.VerifierEntry.verify函數(shù)對其進(jìn)行驗證。JarVerifier.VerifierEntry.write函數(shù)非常簡單:

    ?

    ?

    [java]?view plaincopy
  • public?void?write(byte[]?buf,?int?off,?int?nbytes)?{??
  • ????digest.update(buf,?off,?nbytes);??
  • }??
  • ?

    就是將讀到的文件的內(nèi)容傳給digest,這個digest就是前面在構(gòu)造JarVerifier.VerifierEntry傳進(jìn)來的,對應(yīng)于在MANIFEST.MF文件中指定的摘要算法。萬事具備,接下來想要驗證就很簡單了:

    [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);??
  • }??
  • 通過digest就可以算出apk內(nèi)指定文件的真實摘要值。而記錄在MANIFEST.MF文件中對應(yīng)該文件的摘要值,也在構(gòu)造JarVerifier.VerifierEntry時傳遞給了hash變量。不過這個hash值是經(jīng)過Base64編碼的。所以在比較之前,必須通過Base64解碼。如果不一致的話,會拋出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);??
  • }??
  • 至此,最后一步驗證,即apk內(nèi)所有文件的摘要值要和在MANIFEST.MF文件中記錄的一致,也已經(jīng)完成了。這還沒完,PackageParser.collectCertificates還要接著驗證apk文件中的每個文件對應(yīng)的簽名要和第一個文件一致:

    ?

    ?

    [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安裝時的簽名驗證過程都已經(jīng)分析完了,來總結(jié)一下:

    ?

  • 所有有關(guān)apk文件的簽名驗證工作都是在JarVerifier里面做的,一共分成三步;
  • JarVeirifer.verifyCertificate主要做了兩步。首先,使用證書文件(在META-INF目錄下,以.DSA、.RSA或者.EC結(jié)尾的文件)檢驗簽名文件(在META-INF目錄下,和證書文件同名,但擴展名為.SF的文件)是沒有被修改過的。然后,使用簽名文件,檢驗MANIFEST.MF文件中的內(nèi)容也沒有被篡改過;
  • JarVerifier.VerifierEntry.verify做了最后一步驗證,即保證apk文件中包含的所有文件,對應(yīng)的摘要值與MANIFEST.MF文件中記錄的一致。
  • ?

    轉(zhuǎn)載于:https://www.cnblogs.com/mjblogs/p/5066880.html

    總結(jié)

    以上是生活随笔為你收集整理的Android应用程序签名过程和解析过程分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。