APP簽名原理詳解

一、前言

Android 簽名。

在說道Android簽名之前,我們需要了解的幾個(gè)知識(shí)點(diǎn)

1、數(shù)據(jù)摘要(數(shù)據(jù)指紋)、簽名文件,證書文件

2、jarsign工具簽名和signapk工具簽名

3、keystore文件和pk8文件,x509.pem文件的關(guān)系

4、如何手動(dòng)的簽名apk

上面介紹的四個(gè)知識(shí)點(diǎn),就是今天介紹的核心,我們來一一看這些問題。

二、準(zhǔn)備知識(shí)

首先來看一下數(shù)據(jù)摘要,簽名文件,證書文件的知識(shí)點(diǎn)

1、數(shù)據(jù)摘要

這個(gè)知識(shí)點(diǎn)很好理解,百度百科即可,其實(shí)他也是一種算法,就是對(duì)一個(gè)數(shù)據(jù)源進(jìn)行一個(gè)算法之后得到一個(gè)摘要,也叫作數(shù)據(jù)指紋,不同的數(shù)據(jù)源,數(shù)據(jù)指紋肯定不一樣,就和人一樣。

消息摘要算法(Message Digest Algorithm)是一種能產(chǎn)生特殊輸出格式的算法,其原理是根據(jù)一定的運(yùn)算規(guī)則對(duì)原始數(shù)據(jù)進(jìn)行某種形式的信息提取,被提取出的信息就被稱作原始數(shù)據(jù)的消息摘要。
著名的摘要算法有RSA公司的MD5算法和SHA-1算法及其大量的變體。
消息摘要的主要特點(diǎn)有:
1)無論輸入的消息有多長,計(jì)算出來的消息摘要的長度總是固定的。例如應(yīng)用MD5算法摘要的消息有128個(gè)比特位,用SHA-1算法摘要的消息最終有160比特位的輸出。
2)一般來說(不考慮碰撞的情況下),只要輸入的原始數(shù)據(jù)不同,對(duì)其進(jìn)行摘要以后產(chǎn)生的消息摘要也必不相同,即使原始數(shù)據(jù)稍有改變,輸出的消息摘要便完全不同。但是,相同的輸入必會(huì)產(chǎn)生相同的輸出。
3)具有不可逆性,即只能進(jìn)行正向的信息摘要,而無法從摘要中恢復(fù)出任何的原始消息。

2、簽名文件和證書

簽名文件和證書是成對(duì)出現(xiàn)了,二者不可分離,而且我們后面通過源碼可以看到,這兩個(gè)文件的名字也是一樣的,只是后綴名不一樣。

其實(shí)數(shù)字簽名的概念很簡單。大家知道,要確??煽客ㄐ牛仨氁鉀Q兩個(gè)問題:首先,要確定消息的來源確實(shí)是其申明的那個(gè)人;其次,要保證信息在傳遞的過程中不被第三方篡改,即使被篡改了,也可以發(fā)覺出來。
所謂數(shù)字簽名,就是為了解決這兩個(gè)問題而產(chǎn)生的,它是對(duì)前面提到的非對(duì)稱加密技術(shù)與數(shù)字摘要技術(shù)的一個(gè)具體的應(yīng)用。
對(duì)于消息的發(fā)送者來說,先要生成一對(duì)公私鑰對(duì),將公鑰給消息的接收者。
如果消息的發(fā)送者有一天想給消息接收者發(fā)消息,在發(fā)送的信息中,除了要包含原始的消息外,還要加上另外一段消息。這段消息通過如下兩步生成:
1)對(duì)要發(fā)送的原始消息提取消息摘要;
2)對(duì)提取的信息摘要用自己的私鑰加密。
通過這兩步得出的消息,就是所謂的原始信息的數(shù)字簽名。
而對(duì)于信息的接收者來說,他所收到的信息,將包含兩個(gè)部分,一是原始的消息內(nèi)容,二是附加的那段數(shù)字簽名。他將通過以下三步來驗(yàn)證消息的真?zhèn)危?br> 1)對(duì)原始消息部分提取消息摘要,注意這里使用的消息摘要算法要和發(fā)送方使用的一致;
2)對(duì)附加上的那段數(shù)字簽名,使用預(yù)先得到的公鑰解密;
3)比較前兩步所得到的兩段消息是否一致。如果一致,則表明消息確實(shí)是期望的發(fā)送者發(fā)的,且內(nèi)容沒有被篡改過;相反,如果不一致,則表明傳送的過程中一定出了問題,消息不可信。
通過這種所謂的數(shù)字簽名技術(shù),確實(shí)可以有效解決可靠通信的問題。如果原始消息在傳送的過程中被篡改了,那么在消息接收者那里,對(duì)被篡改的消息提取的摘要肯定和原始的不一樣。并且,由于篡改者沒有消息發(fā)送方的私鑰,即使他可以重新算出被篡改消息的摘要,也不能偽造出數(shù)字簽名。
所以,綜上所述,數(shù)字簽名其實(shí)就是只有信息的發(fā)送者才能產(chǎn)生的別人無法偽造的一段數(shù)字串,這段數(shù)字串同時(shí)也是對(duì)信息的發(fā)送者發(fā)送信息真實(shí)性的一個(gè)有效證明。
不知道大家有沒有注意,前面講的這種數(shù)字簽名方法,有一個(gè)前提,就是消息的接收者必須要事先得到正確的公鑰。如果一開始公鑰就被別人篡改了,那壞人就會(huì)被你當(dāng)成好人,而真正的消息發(fā)送者給你發(fā)的消息會(huì)被你視作無效的。而且,很多時(shí)候根本就不具備事先溝通公鑰的信息通道。那么如何保證公鑰的安全可信呢?這就要靠數(shù)字證書來解決了。
所謂數(shù)字證書,一般包含以下一些內(nèi)容:
證書的發(fā)布機(jī)構(gòu)(Issuer)
證書的有效期(Validity)
消息發(fā)送方的公鑰
證書所有者(Subject)
數(shù)字簽名所使用的算法
數(shù)字簽名
可以看出,數(shù)字證書其實(shí)也用到了數(shù)字簽名技術(shù)。只不過要簽名的內(nèi)容是消息發(fā)送方的公鑰,以及一些其它信息。但與普通數(shù)字簽名不同的是,數(shù)字證書中簽名者不是隨隨便便一個(gè)普通的機(jī)構(gòu),而是要有一定公信力的機(jī)構(gòu)。這就好像你的大學(xué)畢業(yè)證書上簽名的一般都是德高望重的校長一樣。一般來說,這些有公信力機(jī)構(gòu)的根證書已經(jīng)在設(shè)備出廠前預(yù)先安裝到了你的設(shè)備上了。所以,數(shù)字證書可以保證數(shù)字證書里的公鑰確實(shí)是這個(gè)證書的所有者的,或者證書可以用來確認(rèn)對(duì)方的身份。數(shù)字證書主要是用來解決公鑰的安全發(fā)放問題。
綜上所述,總結(jié)一下,數(shù)字簽名和簽名驗(yàn)證的大體流程如下圖所示:

image.png

3、jarsign和signapk工具

了解到完了簽名中的三個(gè)文件的知識(shí)點(diǎn)之后,下面繼續(xù)來看看Android中簽名的兩個(gè)工具:jarsign和signapk

關(guān)于這兩個(gè)工具開始的時(shí)候很容易混淆,感覺他們兩到底有什么區(qū)別嗎?

其實(shí)這兩個(gè)工具很好理解,jarsign是Java本生自帶的一個(gè)工具,他可以對(duì)jar進(jìn)行簽名的。而signapk是后面專門為了Android應(yīng)用程序apk進(jìn)行簽名的工具,他們兩的簽名算法沒什么區(qū)別,主要是簽名時(shí)使用的文件不一樣,這個(gè)就要引出第三個(gè)問題了。

4、keystore文件和pk8,x509.pem文件的區(qū)別

我們上面了解到了jarsign和signapk兩個(gè)工具都可以進(jìn)行Android中的簽名,那么他們的區(qū)別在于簽名時(shí)使用的文件不一樣

jarsign工具簽名時(shí)使用的是keystore文件

signapk工具簽名時(shí)使用的是pk8,x509.pem文件

其中我們?cè)谑褂肊clipse工具寫程序的時(shí)候,出Debug包的時(shí)候,默認(rèn)用的是jarsign工具進(jìn)行簽名的,而且Eclipse中有一個(gè)默認(rèn)簽名文件:

image.png

我們可以看到這個(gè)默認(rèn)簽名的keystore文件,當(dāng)然我們可以選擇我們自己指定的keystore文件。

這里還有一個(gè)知識(shí)點(diǎn):

我們看到上面有MD5和SHA1的摘要,這個(gè)就是keystore文件中私鑰的數(shù)據(jù)摘要,這個(gè)信息也是我們?cè)谏暾?qǐng)很多開發(fā)平臺(tái)賬號(hào)的時(shí)候需要填入的信息,比如申請(qǐng)百度地圖,微信SDK等,會(huì)需要填寫應(yīng)用的MD5或者是SHA1信息。

5、手動(dòng)的簽名Apk包

1》使用keytool和jarsigner來進(jìn)行簽名

當(dāng)然,我們?cè)谡胶灻巖elease包的時(shí)候,我們需要?jiǎng)?chuàng)建一個(gè)自己的keystore文件:

image.png
image.png

這里我們可以對(duì)keystore文件起自己的名字,而且后綴名也是無關(guān)緊要的。創(chuàng)建完文件之后,也會(huì)生成MD5和SHA1的值,這個(gè)值可以不用記錄的,可以通過命令查看keystore文件的MD5和SHA1的值。

keytool -list -keystore debug.keystore

image.png

當(dāng)然我們都知道這個(gè)keytstore文件的重要性,說白了就相當(dāng)于你的銀行卡密碼。你懂得。

這里我們看到用Eclipse自動(dòng)簽名和生成一個(gè)keystore文件,我們也可以使用keytool工具生成一個(gè)keystore文件。這個(gè)方法網(wǎng)上有,這里就不做太多的介紹了。然后我們可以使用jarsign來對(duì)apk包進(jìn)行簽名了。

我們可以手動(dòng)的生成一個(gè)keystore文件:

keytool -genkeypair -v -keyalg DSA -keysize 1024 -sigalg SHA1withDSA -validity 20000 -keystore D:\jiangwei.keystore -alias jiangwei -keypass jiangwei -storepass jiangwei

image.png

這個(gè)命令有點(diǎn)長,有幾個(gè)重要的參數(shù)需要說明:

-alias是定義別名,這里為debug

-keyalg是規(guī)定簽名算法,這里是DSA,這里的算法直接關(guān)系到后面apk中簽名文件的后綴名,到后面會(huì)詳細(xì)說明

在用jarsigner工具進(jìn)行簽名

jarsigner -verbose -sigalg SHA1withDSA -digestalg SHA1 -keystore D:\jiangwei.keystore -storepass jiangwei D:\123.apk jiangwei

image.png

這樣我們就成功的對(duì)apk進(jìn)行簽名了。

簽名的過程中遇到的問題:

1》證書鏈找不到的問題

image.png

這個(gè)是因?yàn)樽詈笠粋€(gè)參數(shù)alias,是keystore的別名輸錯(cuò)了。

2》生成keystore文件的時(shí)候提示密碼錯(cuò)誤

image.png

這個(gè)原因是因?yàn)樵诋?dāng)前目錄已經(jīng)有debug.ketystore了,在生成一個(gè)debug.keystore的話,就會(huì)報(bào)錯(cuò)

3》找不到別名的問題

image.png

這個(gè)問題的原因是因?yàn)槲覀冊(cè)谑褂胟eytool生成keystore的時(shí)候,起了debug的別名,這個(gè)問題困擾了我很久,最后做了很多例子才發(fā)現(xiàn)的,就是只要我們的keystore文件的別名是debug的話,就會(huì)報(bào)這樣的錯(cuò)誤。這個(gè)應(yīng)該和系統(tǒng)默認(rèn)的簽名debug.keystore中的別名是debug有關(guān)系吧?沒有找到j(luò)arsigner的源碼,所以只能猜測了,但是這三個(gè)問題在這里標(biāo)注一下,以防以后在遇到。

注意:Android中是允許使用多個(gè)keystore對(duì)apk進(jìn)行簽名的,這里我就不在粘貼命令了,我又創(chuàng)建了幾個(gè)keystore對(duì)apk進(jìn)行簽名:

image.png

這里我把簽名之后的apk進(jìn)行解壓之后,發(fā)現(xiàn)有三個(gè)簽名文件和證書(.SF/.DSA)

這里我也可以注意到,我們簽名時(shí)用的是DSA算法,這里的文件后綴名就是DSA

而且文件名是keystore的別名

哎,這里算是理清楚了我們上面的如何使用keytool產(chǎn)生keystore以及,用jarsigner來進(jìn)行簽名。

2》使用signapk來進(jìn)行簽名

下面我們?cè)賮砜纯磗ignapk工具進(jìn)行簽名:

java -jar signapk.jar .testkey.x509.pem testkey.pk8 debug.apk debug.sig.apk

這里需要兩個(gè)文件:.pk8和.x509.pem這兩個(gè)文件

pk8是私鑰文件

x509.pem是含有公鑰的文件

這里簽名的話就不在演示了,這里沒什么問題的。

但是這里需要注意的是:signapk簽名之后的apk中的META-INF文件夾中的三個(gè)文件的名字是這樣的,因?yàn)閟ignapk在前面的時(shí)候不像jarsigner會(huì)自動(dòng)使用別名來命名文件,這里就是寫死了是CERT的名字,不過文件名不影響的,后面分析Android中的Apk校驗(yàn)過程中會(huì)說道,只會(huì)通過后綴名來查找文件。

image.png

3》兩種的簽名方式有什么區(qū)別

那么問題來了,jarsigner簽名時(shí)用的是keystore文件,signapk簽名時(shí)用的是pk8和x509.pem文件,而且都是給apk進(jìn)行簽名的,那么keystore文件和pk8,x509.pem他們之間是不是有什么聯(lián)系呢?答案是肯定的,網(wǎng)上搜了一下,果然他們之間是可以轉(zhuǎn)化的,這里就不在分析如何進(jìn)行轉(zhuǎn)化的,網(wǎng)上的例子貌似很多,有專門的的工具可以進(jìn)行轉(zhuǎn)化:

image.png

那么到這里我們就弄清楚了這兩個(gè)簽名工具的區(qū)別和聯(lián)系。

三、分析Android中簽名流程機(jī)制

下面我們開始從源碼的角度去看看Android中的簽名機(jī)制和原理流程

因?yàn)榫W(wǎng)上沒有找到j(luò)arsigner的源碼,但是找到了signapk的源碼,那么下面我們就來看看signapk的源碼吧:

源碼位置:com/android/signapk/sign.java

通過上面的簽名時(shí)我們可以看到,Android簽名apk之后,會(huì)有一個(gè)META-INF文件夾,這里有三個(gè)文件:

MANIFEST.MF

CERT.RSA

CERT.SF

下面來看看這三個(gè)文件到底是干啥的?

1、MANIFEST.MF

image.png

我們來看看源碼:

public static void main(String[] args) {    if (args.length != 4) {        System.err.println("Usage: signapk " +                "publickey.x509[.pem] privatekey.pk8 " +                "input.jar output.jar");        System.exit(2);    }     JarFile inputJar = null;    JarOutputStream outputJar = null;     try {        X509Certificate publicKey = readPublicKey(new File(args[0]));         // Assume the certificate is valid for at least an hour.        long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;         PrivateKey privateKey = readPrivateKey(new File(args[1]));        inputJar = new JarFile(new File(args[2]), false);  // Don't verify.        outputJar = new JarOutputStream(new FileOutputStream(args[3]));        outputJar.setLevel(9);         JarEntry je;         // MANIFEST.MF        Manifest manifest = addDigestsToManifest(inputJar);        je = new JarEntry(JarFile.MANIFEST_NAME);        je.setTime(timestamp);        outputJar.putNextEntry(je);        manifest.write(outputJar);         // CERT.SF        Signature signature = Signature.getInstance("SHA1withRSA");        signature.initSign(privateKey);        je = new JarEntry(CERT_SF_NAME);        je.setTime(timestamp);        outputJar.putNextEntry(je);        writeSignatureFile(manifest,                new SignatureOutputStream(outputJar, signature));         // CERT.RSA        je = new JarEntry(CERT_RSA_NAME);        je.setTime(timestamp);        outputJar.putNextEntry(je);        writeSignatureBlock(signature, publicKey, outputJar);         // Everything else        copyFiles(manifest, inputJar, outputJar, timestamp);    } catch (Exception e) {        e.printStackTrace();        System.exit(1);    } finally {        try {            if (inputJar != null) inputJar.close();            if (outputJar != null) outputJar.close();        } catch (IOException e) {            e.printStackTrace();            System.exit(1);        }    }}

在main函數(shù)中,我們看到需要輸入四個(gè)參數(shù),然后就做了三件事:

寫MANIFEST.MF

//MANIFEST.MFManifest manifest = addDigestsToManifest(inputJar);je = new JarEntry(JarFile.MANIFEST_NAME);je.setTime(timestamp);outputJar.putNextEntry(je);manifest.write(outputJar);

在進(jìn)入方法看看:

/** Add the SHA1 of every file to the manifest, creating it if necessary. */private static Manifest addDigestsToManifest(JarFile jar)        throws IOException, GeneralSecurityException {    Manifest input = jar.getManifest();    Manifest output = new Manifest();    Attributes main = output.getMainAttributes();    if (input != null) {        main.putAll(input.getMainAttributes());    } else {        main.putValue("Manifest-Version", "1.0");        main.putValue("Created-By", "1.0 (Android SignApk)");    }     BASE64Encoder base64 = new BASE64Encoder();    MessageDigest md = MessageDigest.getInstance("SHA1");    byte[] buffer = new byte[4096];    int num;     // We sort the input entries by name, and add them to the    // output manifest in sorted order.  We expect that the output    // map will be deterministic.     TreeMap<String, JarEntry> byName = new TreeMap<String, JarEntry>();     for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {        JarEntry entry = e.nextElement();        byName.put(entry.getName(), entry);    }     for (JarEntry entry: byName.values()) {        String name = entry.getName();        if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&            !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&            (stripPattern == null ||             !stripPattern.matcher(name).matches())) {            InputStream data = jar.getInputStream(entry);            while ((num = data.read(buffer)) > 0) {                md.update(buffer, 0, num);            }             Attributes attr = null;            if (input != null) attr = input.getAttributes(name);            attr = attr != null ? new Attributes(attr) : new Attributes();            attr.putValue("SHA1-Digest", base64.encode(md.digest()));            output.getEntries().put(name, attr);        }    }     return output;}

代碼邏輯還是很簡單的,主要看那個(gè)循環(huán)的意思:

除了三個(gè)文件(MANIFEST.MF,CERT.RSA,CERT.SF),其他的文件都會(huì)對(duì)文件內(nèi)容做一次SHA1算法,就是計(jì)算出文件的摘要信息,然后用Base64進(jìn)行編碼即可,下面我們用工具來做個(gè)案例看看是不是這樣:

首先安裝工具:HashTab

下載地址:http://www.baidu.com/s?wd=hashtab&rsv_spt=1&issp=1&f=8&rsv_bp=0&ie=utf-8&tn=baiduhome_pg&bs=hashtable

然后還有一個(gè)網(wǎng)站就是在線計(jì)算Base64:http://tomeko.net/online_tools/hex_to_base64.php?lang=en

那下面就開始我們的驗(yàn)證工作吧:

我們就來驗(yàn)證一下AndroidManifest.xml文件,首先在MANIFEST.MF文件中找到這個(gè)條目,記錄SHA1的值

image.png

然后我們安裝HashTab之后,找到AndroidManifest.xml文件,右擊,選擇Hashtab:

image.png

復(fù)制SHA-1的值:9C64812DE7373B201C294101473636A3697FD73C,到上面的那個(gè)Base64轉(zhuǎn)化網(wǎng)站,轉(zhuǎn)化一下:

image.png

nGSBLec3OyAcKUEBRzY2o2l/1zw=

和MANIFEST.MF中的條目內(nèi)容一模一樣啦啦

那么從上面的分析我們就知道了,其實(shí)MANIFEST.MF中存儲(chǔ)的是:

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

2、下面再來看一下CERT.SF文件內(nèi)容

image.png

這里的內(nèi)容感覺和MANIFEST.MF的內(nèi)容差不多,來看看代碼吧:

//CERT.SFSignature signature = Signature.getInstance("SHA1withRSA");signature.initSign(privateKey);je = new JarEntry(CERT_SF_NAME);je.setTime(timestamp);outputJar.putNextEntry(je);writeSignatureFile(manifest,new SignatureOutputStream(outputJar, signature));

進(jìn)入到writeSignatureFile方法中:

/** Write a .SF file with a digest the specified manifest. */private static void writeSignatureFile(Manifest manifest, OutputStream out)        throws IOException, GeneralSecurityException {    Manifest sf = new Manifest();    Attributes main = sf.getMainAttributes();    main.putValue("Signature-Version", "1.0");    main.putValue("Created-By", "1.0 (Android SignApk)");     BASE64Encoder base64 = new BASE64Encoder();    MessageDigest md = MessageDigest.getInstance("SHA1");    PrintStream print = new PrintStream(            new DigestOutputStream(new ByteArrayOutputStream(), md),            true, "UTF-8");     // Digest of the entire manifest    manifest.write(print);    print.flush();    main.putValue("SHA1-Digest-Manifest", base64.encode(md.digest()));     Map<String, Attributes> entries = manifest.getEntries();    for (Map.Entry<String, Attributes> entry : entries.entrySet()) {        // Digest of the manifest stanza for this entry.        print.print("Name: " + entry.getKey() + "\r\n");        for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {            print.print(att.getKey() + ": " + att.getValue() + "\r\n");        }        print.print("\r\n");        print.flush();         Attributes sfAttr = new Attributes();        sfAttr.putValue("SHA1-Digest", base64.encode(md.digest()));        sf.getEntries().put(entry.getKey(), sfAttr);    }     sf.write(out);}

首先我們可以看到,需要對(duì)之前的MANIFEST.MF文件整個(gè)內(nèi)容做一個(gè)SHA1放到SHA1-Digest-Manifest字段中:

image.png

我們看看出入的manifest變量就是剛剛寫入了MANIFEST.MF文件的

image.png

這個(gè)我們可以驗(yàn)證一下:

image.png

然后轉(zhuǎn)化一下

image.png

看到了吧,和文件中的值是一樣的啦啦

image.png

下面我們繼續(xù)看代碼,有一個(gè)循環(huán):

Map<String, Attributes> entries = manifest.getEntries();for (Map.Entry<String, Attributes> entry : entries.entrySet()) {    // Digest of the manifest stanza for this entry.    print.print("Name: " + entry.getKey() + "\r\n");    for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {        print.print(att.getKey() + ": " + att.getValue() + "\r\n");    }    print.print("\r\n");    print.flush();     Attributes sfAttr = new Attributes();    sfAttr.putValue("SHA1-Digest", base64.encode(md.digest()));    sf.getEntries().put(entry.getKey(), sfAttr);} sf.write(out);

這里還是用到了剛剛傳入的mainfest變量,遍歷他的條目內(nèi)容,然后進(jìn)行SHA算法計(jì)算在Base64一下:

其實(shí)就是對(duì)MANIFEST.MF文件中的每個(gè)條目內(nèi)容做一次SHA,在保存一下即可,做個(gè)例子驗(yàn)證一下:

用AndroidManifest.xml為例,我們把MANIFEST.MF文件中的條目拷貝保存到txt文檔中:

image.png

這里需要注意的是,我們保存之后,需要添加兩個(gè)換行,我們可以在代碼中看到邏輯:

image.png

然后我們計(jì)算txt文檔的SHA值:

image.png
image.png

看到了吧,這里計(jì)算的值是一樣的啦啦

image.png

到這里我們就知道CERT.SF文件做了什么:

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

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

3、最后我們?cè)趤砜匆幌翪ERT.RSA文件

image.png

這里我們看到的都是二進(jìn)制文件,因?yàn)镽SA文件加密了,所以我們需要用openssl命令才能查看其內(nèi)容

openssl pkcs7 -inform DER -in CERT.RSA -noout -print_certs –text

image.png

關(guān)于這些信息,可以看下面這張圖:

image.png

我們來看一下代碼:

/** Write a .RSA file with a digital signature. */private static void writeSignatureBlock(        Signature signature, X509Certificate publicKey, OutputStream out)        throws IOException, GeneralSecurityException {    SignerInfo signerInfo = new SignerInfo(            new X500Name(publicKey.getIssuerX500Principal().getName()),            publicKey.getSerialNumber(),            AlgorithmId.get("SHA1"),            AlgorithmId.get("RSA"),            signature.sign());     PKCS7 pkcs7 = new PKCS7(            new AlgorithmId[] { AlgorithmId.get("SHA1") },            new ContentInfo(ContentInfo.DATA_OID, null),            new X509Certificate[] { publicKey },            new SignerInfo[] { signerInfo });     pkcs7.encodeSignedData(out);}

我們看到,這里會(huì)把之前生成的 CERT.SF文件, 用私鑰計(jì)算出簽名, 然后將簽名以及包含公鑰信息的數(shù)字證書一同寫入 CERT.RSA 中保存。CERT.RSA是一個(gè)滿足PKCS7格式的文件。

四、為何要這么來簽名

上面我們就介紹了簽名apk之后的三個(gè)文件的詳細(xì)內(nèi)容,那么下面來總結(jié)一下,Android中為何要用這種方式進(jìn)行加密簽名,這種方加密是不是最安全的呢?下面我們來分析一下,如果apk文件被篡改后會(huì)發(fā)生什么。

首先,如果你改變了apk包中的任何文件,那么在apk安裝校驗(yàn)時(shí),改變后的文件摘要信息與MANIFEST.MF的檢驗(yàn)信息不同,于是驗(yàn)證失敗,程序就不能成功安裝。
其次,如果你對(duì)更改的過的文件相應(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)行重簽名。

從上面的分析可以得出,只要修改了Apk中的任何內(nèi)容,就必須重新簽名,不然會(huì)提示安裝失敗,當(dāng)然這里不會(huì)分析,后面一篇文章會(huì)注重分析為何會(huì)提示安裝失敗。

五、知識(shí)點(diǎn)梳理

1、數(shù)據(jù)指紋,簽名文件,證書文件的含義

1》數(shù)據(jù)指紋就是對(duì)一個(gè)數(shù)據(jù)源做SHA/MD5算法,這個(gè)值是唯一的

2》簽名文件技術(shù)就是:數(shù)據(jù)指紋+RSA算法

3》證書文件中包含了公鑰信息和其他信息

4》在Android簽名之后,其中SF就是簽名文件,RSA就是證書文件我們可以使用openssl來查看RSA文件中的證書信息和公鑰信息

2、我們了解了Android中的簽名有兩種方式:jarsigner和signapk 這兩種方式的區(qū)別是:

1》jarsigner簽名時(shí),需要的是keystore文件,而signapk簽名的時(shí)候是pk8,x509.pem文件

2》jarsigner簽名之后的SF和RSA文件名默認(rèn)是keystore的別名,而signapk簽名之后文件名是固定的:CERT

3》Eclipse中我們?cè)谂蹹ebug程序的時(shí)候,默認(rèn)用的是jarsigner方式簽名的,用的也是系統(tǒng)默認(rèn)的debug.keystore簽名文件

4》keystore文件和pk8,x509.pem文件之間可以互相轉(zhuǎn)化

六、思考

我們?cè)诜治隽撕灻夹g(shù)之后,無意中發(fā)現(xiàn)一個(gè)問題,就是CERT.SF,MANIFEST.MF,這兩個(gè)文件中的內(nèi)容的name字段都是apk中的資源名,那么就有一個(gè)問題了,如果資源名很長,而且apk中的資源很多,那么這兩個(gè)文件就會(huì)很大,那么這里我們是不是可以優(yōu)化呢?后面在分析如何減小apk大小的文章中會(huì)繼續(xù)講解,這里先提出這個(gè)問題。

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容