android簽名機制
1.android為什麼要簽名 所有的Android應用程式都要求開發人員用一個認證進行數位簽章,anroid系統不會安裝沒有進行簽名的由於程式。平時我們的程式可以在模擬器上安裝並運行,是因為在應用程式開發期間,由於是以Debug面試進行編譯的,因此ADT根據會自動用預設的密鑰和認證來進行簽名,而在以發布模式編譯時間,apk檔案就不會得到自動簽名,這樣就需要進行手工簽名。
給apk簽名可以帶來以下好處:
1. 應用程式升級:如果你希望使用者無縫升級到新的版本,那麼你必須用同一個認證進行簽名。這是由於只有以同一個認證簽名,系統才會允許安裝升級的應用程式。如果你採用了不同的認證,那麼系統會要求你的應用程式採用不同的包名稱,在這種情況下相當於安裝了一個全新的應用程式。如果想升級應用程式,簽署憑證要相同,包名稱要相同!
2.應用程式模組化:Android系統可以允許同一個認證簽名的多個應用程式在一個進程裡運行,系統實際把他們作為一個單個的應用程式,此時就可以把我們的應用程式以模組的方式進行部署,而使用者可以獨立的升級其中的一個模組。
3.代碼或者資料共用:Android提供了基於簽名的許可權機制,那麼一個應用程式就可以為另一個以相同認證簽名的應用程式公開自己的功能。以同一個認證對多個應用程式進行簽名,利用基於簽名的許可權檢查,你就可以在應用程式間以安全的方式共用代碼和資料了。
不同的應用程式之間,想共用資料,或者共用代碼,那麼要讓他們運行在同一個進程中,而且要讓他們用相同的認證簽名。
2.簽名的方法 參考:簽名的方法 ,這裡就不詳述簽名的方法
3.簽名機制的原理
3.1基本知識
訊息摘要 -Message Digest
簡稱摘要,請看英文翻譯,是摘要,不是簽名,網上幾乎所有APK簽名分析的文章都混淆了這兩個概念。簡單的說訊息摘要就是在訊息資料上,執行一個單向的Hash函數,產生一個固定長度的Hash值,這個Hash值即是訊息摘要也稱為數位指紋,訊息摘要有以下特點:
1. 通過摘要無法推算得出訊息本身
2. 如果修改了訊息,那麼摘要一定會變化(實際上,由於長明文產生短摘要的Hash必然會產生碰撞),所以這句話並不準確,我們可以改為:很難找到一種模式,修改了訊息,而它的摘要不會變化(抗衝突性)。
訊息摘要的這種特性,很適合來驗證資料的完整性,比如在網路傳輸過程中下載一個大檔案BigFile,我們會同時從網路下載BigFile和BigFile.md5,BigFile.md5儲存BigFile的摘要,我們在本地產生BigFile的訊息摘要,和BigFile.md5比較,如果內容相同,則表示下載過程正確。
注意,訊息摘要只能保證訊息的完整性,並不能保證訊息的不可篡改性。
MD5/SHA-0 SHA-1
這些都是摘要產生演算法,和簽名沒有關係。如果非要說他們和簽名有關係,那就是簽名是要藉助於摘要技術。
數位簽章 - Signature
數位簽章,百度百科對數位簽章有非常清楚的介紹。數位簽章就是資訊的寄件者用自己的私密金鑰對訊息摘要加密產生一個字串,密碼編譯演算法確保別人無法偽造產生這段字串,這段數字串也是對資訊的寄件者發送資訊真實性的一個有效證明。數位簽章是 非對稱金鑰密碼編譯技術 + 數字摘要技術 的結合。
數位簽章技術是將資訊摘要用寄件者的私密金鑰加密,與原文一起傳送給接收者。接收者只有用寄件者的公開金鑰才能解密被加密的資訊摘要,然後接收者用相同的Hash函數對收到的原文產生一個資訊摘要,與解密的資訊摘要做比對。如果相同,則說明收到的資訊是完整的,在傳輸過程中沒有被修改;不同則說明資訊被修改過,因此數位簽章能保證資訊的完整性。並且由於只有寄件者才有密碼編譯摘要的私密金鑰,所以我們可以確定資訊一定是寄件者發送的。
數位憑證 - Certificate
數位憑證是一個經認證授權 中心數位簽章的包含公開密鑰擁有者資訊以及公開密鑰的檔案。CERT.RSA包含了一個數位簽章以及一個數位憑證。
需要注意的是Android APK中的CERT.RSA認證是自簽名的,並不需要這個認證是第三方權威機構發布或者認證的,使用者可以在本地機器自行產生這個自我簽署憑證。
3.2 Android簽名分析我們將DF_SDM_1008.apk(自己任選)檔案改為DF_SDM_1008.zip檔案,開啟DF_SDM_1008.zip檔案,1所示。
圖1 DF_SDM_1008.zip檔案
1. META-INF\ (註:簽名後的資訊);
2. res\ (註:存放資源檔的目錄) ;
3. AndroidManifest.xml (註:程式全域設定檔) ;
4. classes.dex (註:Dalvik位元組碼);
5. resources.arsc (註:編譯後的二進位資源檔)。
接下來針對META-INF\檔案進行分析。
3.3META-INF\檔案META-INF\檔案中有三個檔案,分別是MANIFEST.MF, CERT.SF, CERT.RSA,2所示。現在有一個問題就是,三個檔案怎麼產生的的——簽名產生的,第二個問題籤名是怎麼做的呢?這裡Android提供了APK的簽名工具signapk,通過xxx.keystZ喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcmWjqGphdmG1xMPc1L+/4qGi08PAtL340NDNqNDFvNPD3NPDtcShorHIyOfK/dfWx6nD+6Gja2V5c3RvcmW+zcrH08PAtLGjtObD3NS/ttS1xKOsscjI57mr1L+6zcu91L+jqczhuam1xNDFz6KjrLbUQVBLvfjQ0Mepw/ujrMn6s8m1xE1FVEEtSU5GXM7EvP6jrLLOv7zOxNXCNKGjCjxicj4KCjxoMz4xLk1BTklGRVNULk1GzsS8/jwvaDM+CjxzdHJvbmc+z8i/tNK7z8LUtLT6wus8L3N0cm9uZz6jugo8cHJlIGNsYXNzPQ=="brush:java;">// MANIFEST.MF Manifest manifest = addDigestsToManifest(inputJar); je = new JarEntry(JarFile.MANIFEST_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); manifest.write(outputJar);
/** 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)"); }...... 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; }遍曆APK包中的每一個檔案,利用SHA1演算法產生這些檔案的摘要資訊。
驗證是所有檔案使用的SHA1演算法:1.安裝hashTab工具
2.開啟MANIFEST.MF檔案舉個例子:
Name: AndroidManifest.xmlSHA1-Digest: Zovq4AVMcCjFkILZLlHgmeOLvnU=
其中找到檔案中的AndroidManifest.xml檔案,查看其對應的hash值,3所示。 這裡取出16進位的668BEAE0054C7028C59082D92E51E099E38BBE75,將16進位通過線上轉碼網站:hex to base64轉化為對應的base64編碼,看見“Zovq4AVMcCjFkILZLlHgmeOLvnU=”與記錄資訊相對的。
2.CERT.SF檔案
先看一下源碼:
// 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));
/** Write a .SF file with a digest of the specified manifest. */ private static void writeSignatureFile(Manifest manifest, SignatureOutputStream 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 entries = manifest.getEntries(); for (Map.Entry entry : entries.entrySet()) { // Digest of the manifest stanza for this entry. print.print("Name: " + entry.getKey() + "\r\n"); for (Map.Entry 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); // A bug in the java.util.jar implementation of Android platforms // up to version 1.6 will cause a spurious IOException to be thrown // if the length of the signature file is a multiple of 1024 bytes. // As a workaround, add an extra CRLF in this case. if ((out.size() % 1024) == 0) { out.write('\r'); out.write('\n'); } }雖然writeSignatureFile字面上看起來是寫簽名檔案,但是CERT.SF的產生和私密金鑰沒有一分錢的關係,實際上也不應該有一分錢的關係,這個檔案自然不儲存任何簽名內容。CERT.SF中儲存的是MANIFEST.MF的摘要值(第一項),
Signature-Version: 1.0Created-By: 1.0 (Android)SHA1-Digest-Manifest: nGpBbfOirA4fsY0pn0dBONop5bQ=
以及MANIFEST.MF中每一個摘要項的摘要值。我也沒搞清楚為什麼要引入CERT.SF,實際上我也覺得簽名完全可以用MANIFEST.MF產生。
驗證所有的摘要都是MANIFEST.MF條目:
首先:
對應MANIFEST.MF檔案,對應的訊息摘要為SHA1-Digest-Manifest: nGpBbfOirA4fsY0pn0dBONop5bQ=,對應的實際訊息摘要4所示。
圖4 MANIFEST.MF訊息摘要和對應的base64編碼
其次:
驗證條目的訊息摘要,根據條目訊息摘要的演算法知道內容格式為SHA1("Name: filename"+CR+LF+"SHA1-Digest: "+SHA1(file_content)+CR+LF+CR+LF)我先把CERT.SF中的一項取出來,然後驗證,取出
Name: AndroidManifest.xmlSHA1-Digest: PJblxooLyYkHHlr/0lKZkk2DkM0=
在將MANIFEST.MF條目取出,儲存為“建立文字文件.txt”,查看對應的訊息摘要,並將其轉換為base64編碼,5所示。圖5 建立文字文件.txt的訊息摘要和對應的base64編碼這裡完成了對CERT.SF的驗證。
3.CERT.RSA檔案
代碼為:
// CERT.RSA je = new JarEntry(CERT_RSA_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); writeSignatureBlock(signature, publicKey, outputJar);
/** 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); }這個檔案儲存了簽名和密鑰憑證。簽名的產生一定會有私密金鑰參與,簽名用到的資訊摘要就是CERT.SF內容。
signature這個資料會作為簽名用到的摘要,writeSignatureBlock函數用privateKey對signature加密產生簽名,然後把簽名和密鑰憑證一起儲存到CERT.RSA中。
最終儲存在CERT.RSA中的是CERT.SF的數位簽章,簽名使用privateKey產生的,簽名演算法會在publicKey中定義。同時還會把publicKey存放在CERT.RSA中,也就是說CERT.RSA包含了簽名和簽名用到的認證。並且要求這個認證是自簽名的。
提取CERT.RSA資訊:參考有RSA公開金鑰資訊、subject資訊、對應的簽名資訊。
Certificate: Data: Version: 3 (0x2) Serial Number: 1281971851 (0x4c69568b) Signature Algorithm: sha1WithRSAEncryption Issuer: CN=Michael Liu Validity Not Before: Aug 16 15:17:31 2010 GMT Not After : Aug 10 15:17:31 2035 GMT Subject: CN=Michael Liu Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public Key: (1024 bit) Modulus (1024 bit): 00:8d:04:84:a2:1e:c6:56:39:f2:cd:a6:f0:48:a5: f7:5e:71:8f:e1:a8:af:a7:dc:66:92:a2:b9:cf:da: 0f:32:42:ce:83:fe:bc:e1:4f:0a:fd:d9:a8:b3:73: f4:ff:97:15:17:87:d6:d0:3c:da:01:fc:11:40:7d: 04:da:31:cc:cd:da:d0:e7:7b:e3:c1:84:30:9f:21: 93:95:20:48:b1:2d:24:02:d2:b9:3c:87:0d:fa:b8: e1:b1:45:f4:8d:90:0a:3b:9d:d8:8a:9a:96:d1:51: 23:0e:8e:c4:09:68:7d:95:be:c6:42:e9:54:a1:5c: 5d:3f:25:d8:5c:c3:42:73:21 Exponent: 65537 (0x10001) Signature Algorithm: sha1WithRSAEncryption 78:3c:6b:ef:71:70:55:68:28:80:4d:f8:b5:cd:83:a9:01:21: 2a:c1:e4:96:ad:bc:5f:67:0c:cd:c3:34:51:6d:63:90:a9:f9: d5:5e:c7:ef:34:43:86:7d:68:e1:99:87:92:86:34:91:6d:67: 6d:b2:22:e9:5e:28:aa:e8:05:52:04:6e:4e:d4:7f:0f:b0:d6: 28:f5:2b:11:38:d5:15:cb:e3:e4:c9:99:23:c1:84:4f:ce:69: e9:b1:59:7b:8e:30:01:1c:e1:92:ee:0d:54:61:29:f5:8e:9e: 42:72:26:2b:aa:c7:af:d9:c9:d1:85:95:8e:4c:8d:5c:77:c5: ce:4e
參考文章:
1.Android為什麼要為app簽名
2.簽名的方法
3.訊息摘要、數位簽章、數位憑證
4.signApk項目
5.提取CERT.RSA中的公開金鑰和簽名資訊