Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android簽名機制

android簽名機制

編輯:關於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喎?/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.xml
SHA1-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.0
Created-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.xml
SHA1-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中的公鑰和簽名信息




  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved