zoukankan      html  css  js  c++  java
  • 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.keystore(java的密钥库、用来进行通信加密用的、比方数字签名。keystore就是用来保存密钥对的,比方公钥和私钥)提供的信息,对APK进行签名,生成的META-INF文件,參考文章4。

    1.MANIFEST.MF文件

    先看一下源码
    // 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)");
            }
    <span style="white-space:pre">	</span>......
            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<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() + "
    ");
                for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
                    print.print(att.getKey() + ": " + att.getValue() + "
    ");
                }
                print.print("
    ");
                print.flush();
    
                Attributes sfAttr = new Attributes();
                sfAttr.putValue("SHA1-Digest", base64.encode(md.digest()));
                sf.getEntries().put(entry.getKey(), sfAttr);
            }
    <span style="white-space:pre">	</span>//签名信息在上面并没有使用的到
            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('
    ');
                out.write('
    ');
            }
        }
    尽管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中的公钥和签名信息




  • 相关阅读:
    localStorage cache
    webpack的学习过程
    npm install --save/--save-dev的区别
    .gitignore常见问题
    jQuery的优点与缺点
    JSONP是什么
    Node.js-Usage & Example
    【转】Swig Getting Started
    【转】使用Spring MVC统一异常处理实战
    C++-Qt【5】-QT的QString,char*,QByteArray转化以及中文乱码的问题
  • 原文地址:https://www.cnblogs.com/jhcelue/p/7147777.html
Copyright © 2011-2022 走看看