APK签名原理

原创 2014年01月14日 20:25:46

网上已有多篇分析签名的类似文章,但是都有一个共同的问题,就是概念混乱,混乱的一塌糊涂。

在了解APK签名原理之前,首先澄清几个概念:


消息摘要 -Message Digest

简称摘要,请看英文翻译,是摘要,不是签名,网上几乎所有APK签名分析的文章都混淆了这两个概念。

摘要的链接http://en.wikipedia.org/wiki/Message_digest

简单的说消息摘要就是在消息数据上,执行一个单向的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证书是自签名的,并不需要这个证书是第三方权威机构发布或者认证的,用户可以在本地机器自行生成这个自签名证书。


APK签名过程分析

摘要和签名的概念清楚后,我们就可以分析APK 签名过程了。Android提供了APK的签名工具signapk ,使用方法如下:

signapk [-w] publickey.x509[.pem] privatekey.pk8 input.jar output.jar

publickey.x509.pem包含证书和证书链,证书和证书链中包含了公钥和加密算法;privatekey.pk8是私钥;input.jar是需要签名的jar;output.jar是签名结果


signapk的实现在android/build/tools/signapk/SignApk.java中,主函数main实现如下

    public static void main(String[] args) {
        if (args.length != 4 && args.length != 5) {
            System.err.println("Usage: signapk [-w] " +
                    "publickey.x509[.pem] privatekey.pk8 " +
                    "input.jar output.jar");
            System.exit(2);
        }

        sBouncyCastleProvider = new BouncyCastleProvider();
        Security.addProvider(sBouncyCastleProvider);

        boolean signWholeFile = false;
        int argstart = 0;
        if (args[0].equals("-w")) {
            signWholeFile = true;
            argstart = 1;
        }

        JarFile inputJar = null;
        JarOutputStream outputJar = null;
        FileOutputStream outputFile = null;

        try {
            File publicKeyFile = new File(args[argstart+0]);
            X509Certificate publicKey = readPublicKey(publicKeyFile);

            // Assume the certificate is valid for at least an hour.
            long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;

            PrivateKey privateKey = readPrivateKey(new File(args[argstart+1]));
            inputJar = new JarFile(new File(args[argstart+2]), false);  // Don't verify.

            OutputStream outputStream = null;
            if (signWholeFile) {
                outputStream = new ByteArrayOutputStream();
            } else {
                outputStream = outputFile = new FileOutputStream(args[argstart+3]);
            }
            outputJar = new JarOutputStream(outputStream);

            // For signing .apks, use the maximum compression to make
            // them as small as possible (since they live forever on
            // the system partition).  For OTA packages, use the
            // default compression level, which is much much faster
            // and produces output that is only a tiny bit larger
            // (~0.1% on full OTA packages I tested).
            if (!signWholeFile) {
                outputJar.setLevel(9);
            }

            JarEntry je;

            Manifest manifest = addDigestsToManifest(inputJar);

            // Everything else
            copyFiles(manifest, inputJar, outputJar, timestamp);

            // otacert
            if (signWholeFile) {
                addOtacert(outputJar, publicKeyFile, timestamp, manifest);
            }

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

            // CERT.SF
            je = new JarEntry(CERT_SF_NAME);
            je.setTime(timestamp);
            outputJar.putNextEntry(je);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            writeSignatureFile(manifest, baos);
            byte[] signedData = baos.toByteArray();
            outputJar.write(signedData);

            // CERT.RSA
            je = new JarEntry(CERT_RSA_NAME);
            je.setTime(timestamp);
            outputJar.putNextEntry(je);
            writeSignatureBlock(new CMSProcessableByteArray(signedData),
                                publicKey, privateKey, outputJar);

            outputJar.close();
            outputJar = null;
            outputStream.flush();

            if (signWholeFile) {
                outputFile = new FileOutputStream(args[argstart+3]);
                signWholeOutputFile(((ByteArrayOutputStream)outputStream).toByteArray(),
                                    outputFile, publicKey, privateKey);
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        } finally {
            try {
                if (inputJar != null) inputJar.close();
                if (outputFile != null) outputFile.close();
            } catch (IOException e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    }


生成MAINFEST.MF文件

Manifest manifest = addDigestsToManifest(inputJar);

遍历inputJar中的每一个文件,利用SHA1算法生成这些文件的信息摘要。

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

生成MAINFEST.MF文件,这个文件包含了input jar包内所有文件内容的摘要值。注意,不会生成下面三个文件的摘要值MANIFEST.MF CERT.SF和CERT.RSA


生成CERT.SF

            // CERT.SF
            je = new JarEntry(CERT_SF_NAME);
            je.setTime(timestamp);
            outputJar.putNextEntry(je);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            writeSignatureFile(manifest, baos);
            byte[] signedData = baos.toByteArray();
            outputJar.write(signedData);

虽然writeSignatureFile字面上看起来是写签名文件,但是CERT.SF的生成和私钥没有一分钱的关系,实际上也不应该有一分钱的关系,这个文件自然不保存任何签名内容。

CERT.SF中保存的是MANIFEST.MF的摘要值,以及MANIFEST.MF中每一个摘要项的摘要值。恕我愚顿,没搞清楚为什么要引入CERT.SF,实际上我觉得签名完全可以用MANIFEST.MF生成。

signedData就是CERT.SF的内容,这个信息摘要在制作签名的时候会用到。


生成CERT.RSA

这个文件保存了签名和公钥证书。签名的生成一定会有私钥参与,签名用到的信息摘要就是CERT.SF内容。

            // CERT.RSA
            je = new JarEntry(CERT_RSA_NAME);
            je.setTime(timestamp);
            outputJar.putNextEntry(je);
            writeSignatureBlock(new CMSProcessableByteArray(signedData),
                                publicKey, privateKey, outputJar);

signedData这个数据会作为签名用到的摘要,writeSignatureBlock函数用privateKey对signedData加密生成签名,然后把签名和公钥证书一起保存到CERT.RSA中、


    /** Sign data and write the digital signature to 'out'. */
    private static void writeSignatureBlock(
        CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey,
        OutputStream out)
        throws IOException,
               CertificateEncodingException,
               OperatorCreationException,
               CMSException {
        ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>(1);
        certList.add(publicKey);
        JcaCertStore certs = new JcaCertStore(certList);

        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA")
            .setProvider(sBouncyCastleProvider)
            .build(privateKey);
        gen.addSignerInfoGenerator(
            new JcaSignerInfoGeneratorBuilder(
                new JcaDigestCalculatorProviderBuilder()
                .setProvider(sBouncyCastleProvider)
                .build())
            .setDirectSignature(true)
            .build(sha1Signer, publicKey));
        gen.addCertificates(certs);
        CMSSignedData sigData = gen.generate(data, false);

        ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
        DEROutputStream dos = new DEROutputStream(out);
        dos.writeObject(asn1.readObject());
    }


翻译下这个函数的注释:对参数data进行签名,然后把生成的数字签名写入参数out中

@data是生成签名的摘要

@publicKey; 是签名用到的私钥对应的证书

@privateKey: 是签名时用到的私钥

@out: 输出文件,也就是CERT.RSA


最终保存在CERT.RSA中的是CERT.SF的数字签名,签名使用privateKey生成的,签名算法会在publicKey中定义。同时还会把publicKey存放在CERT.RSA中,也就是说CERT.RSA包含了签名和签名用到的证书。并且要求这个证书是自签名的。






版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kickxxx/article/details/18252881

Android APK签名原理及方法

一 Android签名机制及原理 Android系统在安装APK的时候,首先会检验APK的签名,如果发现签名文件不存在或者校验签名失败,则会拒绝安装,所以应用程序在发布之前一定要进行签名。给AP...
  • YCL_666
  • YCL_666
  • 2016-06-14 09:00:15
  • 1269

Android签名机制之---签名过程详解

一、前言又是过了好长时间,没写文章的双手都有点难受了。今天是圣诞节,还是得上班。因为前几天有一个之前的同事,在申请微信SDK的时候,遇到签名的问题,问了我一下,结果把我难倒了。。我说Android中的...
  • jiangwei0910410003
  • jiangwei0910410003
  • 2015-12-25 15:46:21
  • 53943

Android签名与认证原理

一、Android签名概述 我们已经知道的是:Android对每一个Apk文件都会进行签名,在Apk文件安装时,系统会对其签名信息进行比对,判断程序的完整性,从而决定该Apk文件是否可以安装,在...
  • justFWD
  • justFWD
  • 2015-12-22 11:17:02
  • 3179

android 4.2 OutOfMemoryError

在Ubuntu 12.04 preparing update.zip packaging... signing... Exception in thread "main" java...
  • qilu0882
  • qilu0882
  • 2014-08-19 09:30:42
  • 532

Android APK签名有什么用呢?

很多Android开发者不明白APK签名有什么作用,其实APK签名用处主要有两种:  1. 使用特殊的key签名可以获取到一些不同的权限。  2. APK如果使用一个key签名,发布时另一个key...
  • z_hongliang
  • z_hongliang
  • 2016-04-08 23:39:42
  • 1850

Android APK的数字签名的作用和意义

1. 什么是数字签名? 数字签名就是为你的程序打上一种标记,来作为你自己的标识,当别人看到签名的时候会知道它是与你相关的     2. 为什么要数字签名? 最简单直接的回答: 系统要...
  • Me_Dong
  • Me_Dong
  • 2016-11-04 16:01:39
  • 395

android程序打包apk(签名的作用)

Android 程序打包及签名 为什么要签名???     开发Android的人这么多,完全有可能大家都把类名,包名起成了一个同样的名字,这时候如何区分?签名这时候就是起区分作用的。  ...
  • devilzy2656
  • devilzy2656
  • 2012-12-21 23:28:01
  • 1638

Android签名有什么作用-apk签名可以带来的好处

平时我们的程序可以在模拟器上安装并运行,是因为在应用程序开发期间,由于是以 Debug 面试进行编译的,因此ADT 根据会自动用默认的密钥和证书来进行签名,而在以发布模式编译时,apk 文件就不会得到...
  • qq_22120329
  • qq_22120329
  • 2016-03-21 17:47:28
  • 2498

Android应用数字签名详解

目录目录 概述 数字签名证书生成方法 命令行工具keytooljarsinger Android Studio  小结 查看应用签名信息 查看自签名证书的信息 查看第三方应用或Android系统应用签...
  • zinss26914
  • zinss26914
  • 2015-11-26 15:23:26
  • 9878

APK 数字签名

APK数字签名   一、    为什么要数字签名   Android系统要求每一个Android应用程序必须要经过数字签名才能够安装到系统中,也就是说如果一个Android应用程序没有经过数字签...
  • u010571866
  • u010571866
  • 2014-07-26 15:24:41
  • 496
收藏助手
不良信息举报
您举报文章:APK签名原理
举报原因:
原因补充:

(最多只允许输入30个字)