android签名_android签名机制 - CSDN
精华内容
参与话题
  •   一、前言   又是过了好长时间,没写文章的双手都有点难受了。今天是圣诞节,还是得上班。因为前几天有一个之前的同事,在申请微信SDK的时候...我说Android中的签名大家都会熟悉的,就是为了安全,不让别人修...

    转载自:http://blog.csdn.net/jiangwei0910410003/article/details/50402000

     

    一、前言

     

    又是过了好长时间,没写文章的双手都有点难受了。今天是圣诞节,还是得上班。因为前几天有一个之前的同事,在申请微信SDK的时候,遇到签名的问题,问了我一下,结果把我难倒了。。我说Android中的签名大家都会熟悉的,就是为了安全,不让别人修改你的apk,但是我们真正的有了解多少呢?所以准备两篇文章好好介绍一下Android中签名机制。

    在说道Android签名之前,我们需要了解的几个知识点

    1、数据摘要(数据指纹)、签名文件,证书文件

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

    3、keystore文件和pk8文件,x509.pem文件的关系

    4、如何手动的签名apk

    上面介绍的四个知识点,就是今天介绍的核心,我们来一一看这些问题。

     

    二、准备知识

    首先来看一下数据摘要,签名文件,证书文件的知识点

    1、数据摘要

    这个知识点很好理解,百度百科即可,其实他也是一种算法,就是对一个数据源进行一个算法之后得到一个摘要,也叫作数据指纹,不同的数据源,数据指纹肯定不一样,就和人一样。

    消息摘要算法(Message Digest Algorithm)是一种能产生特殊输出格式的算法,其原理是根据一定的运算规则对原始数据进行某种形式的信息提取,被提取出的信息就被称作原始数据的消息摘要。
    著名的摘要算法有RSA公司的MD5算法和SHA-1算法及其大量的变体。
    消息摘要的主要特点有:
    1)无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如应用MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出。
    2)一般来说(不考虑碰撞的情况下),只要输入的原始数据不同,对其进行摘要以后产生的消息摘要也必不相同,即使原始数据稍有改变,输出的消息摘要便完全不同。但是,相同的输入必会产生相同的输出。
    3)具有不可逆性,即只能进行正向的信息摘要,而无法从摘要中恢复出任何的原始消息。

     

    2、签名文件和证书

    签名文件和证书是成对出现了,二者不可分离,而且我们后面通过源码可以看到,这两个文件的名字也是一样的,只是后缀名不一样。

    其实数字签名的概念很简单。大家知道,要确保可靠通信,必须要解决两个问题:首先,要确定消息的来源确实是其申明的那个人;其次,要保证信息在传递的过程中不被第三方篡改,即使被篡改了,也可以发觉出来。
    所谓数字签名,就是为了解决这两个问题而产生的,它是对前面提到的非对称加密技术与数字摘要技术的一个具体的应用。
    对于消息的发送者来说,先要生成一对公私钥对,将公钥给消息的接收者。
    如果消息的发送者有一天想给消息接收者发消息,在发送的信息中,除了要包含原始的消息外,还要加上另外一段消息。这段消息通过如下两步生成:
    1)对要发送的原始消息提取消息摘要;
    2)对提取的信息摘要用自己的私钥加密。
    通过这两步得出的消息,就是所谓的原始信息的数字签名。
    而对于信息的接收者来说,他所收到的信息,将包含两个部分,一是原始的消息内容,二是附加的那段数字签名。他将通过以下三步来验证消息的真伪:
    1)对原始消息部分提取消息摘要,注意这里使用的消息摘要算法要和发送方使用的一致;
    2)对附加上的那段数字签名,使用预先得到的公钥解密;
    3)比较前两步所得到的两段消息是否一致。如果一致,则表明消息确实是期望的发送者发的,且内容没有被篡改过;相反,如果不一致,则表明传送的过程中一定出了问题,消息不可信。
    通过这种所谓的数字签名技术,确实可以有效解决可靠通信的问题。如果原始消息在传送的过程中被篡改了,那么在消息接收者那里,对被篡改的消息提取的摘要肯定和原始的不一样。并且,由于篡改者没有消息发送方的私钥,即使他可以重新算出被篡改消息的摘要,也不能伪造出数字签名。
    所以,综上所述,数字签名其实就是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。
    不知道大家有没有注意,前面讲的这种数字签名方法,有一个前提,就是消息的接收者必须要事先得到正确的公钥。如果一开始公钥就被别人篡改了,那坏人就会被你当成好人,而真正的消息发送者给你发的消息会被你视作无效的。而且,很多时候根本就不具备事先沟通公钥的信息通道。那么如何保证公钥的安全可信呢?这就要靠数字证书来解决了。
    所谓数字证书,一般包含以下一些内容:
    证书的发布机构(Issuer)
    证书的有效期(Validity)
    消息发送方的公钥
    证书所有者(Subject)
    数字签名所使用的算法
    数字签名
    可以看出,数字证书其实也用到了数字签名技术。只不过要签名的内容是消息发送方的公钥,以及一些其它信息。但与普通数字签名不同的是,数字证书中签名者不是随随便便一个普通的机构,而是要有一定公信力的机构。这就好像你的大学毕业证书上签名的一般都是德高望重的校长一样。一般来说,这些有公信力机构的根证书已经在设备出厂前预先安装到了你的设备上了。所以,数字证书可以保证数字证书里的公钥确实是这个证书的所有者的,或者证书可以用来确认对方的身份。数字证书主要是用来解决公钥的安全发放问题。
    综上所述,总结一下,数字签名和签名验证的大体流程如下图所示:

     

    3、jarsign和signapk工具

    了解到完了签名中的三个文件的知识点之后,下面继续来看看Android中签名的两个工具:jarsign和signapk

    关于这两个工具开始的时候很容易混淆,感觉他们两到底有什么区别吗?

    其实这两个工具很好理解,jarsign是Java本生自带的一个工具,他可以对jar进行签名的。而signapk是后面专门为了Android应用程序apk进行签名的工具,他们两的签名算法没什么区别,主要是签名时使用的文件不一样,这个就要引出第三个问题了。

     

    4、keystore文件和pk8,x509.pem文件的区别

    我们上面了解到了jarsign和signapk两个工具都可以进行Android中的签名,那么他们的区别在于签名时使用的文件不一样

    jarsign工具签名时使用的是keystore文件

    signapk工具签名时使用的是pk8,x509.pem文件

    其中我们在使用Eclipse工具写程序的时候,出Debug包的时候,默认用的是jarsign工具进行签名的,而且Eclipse中有一个默认签名文件:

    我们可以看到这个默认签名的keystore文件,当然我们可以选择我们自己指定的keystore文件。

    这里还有一个知识点:

    我们看到上面有MD5和SHA1的摘要,这个就是keystore文件中私钥的数据摘要,这个信息也是我们在申请很多开发平台账号的时候需要填入的信息,比如申请百度地图,微信SDK等,会需要填写应用的MD5或者是SHA1信息

     

    5、手动的签名Apk包

    1》使用keytool和jarsigner来进行签名

    当然,我们在正式签名处release包的时候,我们需要创建一个自己的keystore文件:

    这里我们可以对keystore文件起自己的名字,而且后缀名也是无关紧要的。创建完文件之后,也会生成MD5和SHA1的值,这个值可以不用记录的,可以通过命令查看keystore文件的MD5和SHA1的值。

    keytool -list -keystore debug.keystore

    当然我们都知道这个keytstore文件的重要性,说白了就相当于你的银行卡密码。你懂得。

    这里我们看到用Eclipse自动签名和生成一个keystore文件,我们也可以使用keytool工具生成一个keystore文件。这个方法网上有,这里就不做太多的介绍了。然后我们可以使用jarsign来对apk包进行签名了。

    我们可以手动的生成一个keystore文件:

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

    这个命令有点长,有几个重要的参数需要说明:

    -alias是定义别名,这里为debug

    -keyalg是规定签名算法,这里是DSA,这里的算法直接关系到后面apk中签名文件的后缀名,到后面会详细说明

     

    在用jarsigner工具进行签名

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

    这样我们就成功的对apk进行签名了。

    签名的过程中遇到的问题:

    1》证书链找不到的问题

    这个是因为最后一个参数alias,是keystore的别名输错了。

     

    2》生成keystore文件的时候提示密码错误

    这个原因是因为在当前目录已经有debug.ketystore了,在生成一个debug.keystore的话,就会报错

     

    3》找不到别名的问题

    这个问题的原因是因为我们在使用keytool生成keystore的时候,起了debug的别名,这个问题困扰了我很久,最后做了很多例子才发现的,就是只要我们的keystore文件的别名是debug的话,就会报这样的错误。这个应该和系统默认的签名debug.keystore中的别名是debug有关系吧?没有找到jarsigner的源码,所以只能猜测了,但是这三个问题在这里标注一下,以防以后在遇到。

     

     

    注意:Android中是允许使用多个keystore对apk进行签名的,这里我就不在粘贴命令了,我又创建了几个keystore对apk进行签名:

    这里我把签名之后的apk进行解压之后,发现有三个签名文件和证书(.SF/.DSA)

    这里我也可以注意到,我们签名时用的是DSA算法,这里的文件后缀名就是DSA

    而且文件名是keystore的别名

    哎,这里算是理清楚了我们上面的如何使用keytool产生keystore以及,用jarsigner来进行签名。

     

    2》使用signapk来进行签名

    下面我们再来看看signapk工具进行签名:

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

    这里需要两个文件:.pk8和.x509.pem这两个文件

    pk8是私钥文件

    x509.pem是含有公钥的文件

    这里签名的话就不在演示了,这里没什么问题的。

    但是这里需要注意的是:signapk签名之后的apk中的META-INF文件夹中的三个文件的名字是这样的,因为signapk在前面的时候不像jarsigner会自动使用别名来命名文件,这里就是写死了是CERT的名字,不过文件名不影响的,后面分析Android中的Apk校验过程中会说道,只会通过后缀名来查找文件。

     

    3》两种的签名方式有什么区别

    那么问题来了,jarsigner签名时用的是keystore文件,signapk签名时用的是pk8和x509.pem文件,而且都是给apk进行签名的,那么keystore文件和pk8,x509.pem他们之间是不是有什么联系呢?答案是肯定的,网上搜了一下,果然他们之间是可以转化的,这里就不在分析如何进行转化的,网上的例子貌似很多,有专门的的工具可以进行转化:

    那么到这里我们就弄清楚了这两个签名工具的区别和联系。

     

    三、分析Android中签名流程机制

    下面我们开始从源码的角度去看看Android中的签名机制和原理流程

    因为网上没有找到jarsigner的源码,但是找到了signapk的源码,那么下面我们就来看看signapk的源码吧:

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

    通过上面的签名时我们可以看到,Android签名apk之后,会有一个META-INF文件夹,这里有三个文件:

    MANIFEST.MF

    CERT.RSA

    CERT.SF

    下面来看看这三个文件到底是干啥的?

    1、MANIFEST.MF

    我们来看看源码:

     

    [java] view plain copy

    1. public static void main(String[] args) {  
    2.     if (args.length != 4) {  
    3.         System.err.println("Usage: signapk " +  
    4.                 "publickey.x509[.pem] privatekey.pk8 " +  
    5.                 "input.jar output.jar");  
    6.         System.exit(2);  
    7.     }  
    8.   
    9.     JarFile inputJar = null;  
    10.     JarOutputStream outputJar = null;  
    11.   
    12.     try {  
    13.         X509Certificate publicKey = readPublicKey(new File(args[0]));  
    14.   
    15.         // Assume the certificate is valid for at least an hour.  
    16.         long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;  
    17.   
    18.         PrivateKey privateKey = readPrivateKey(new File(args[1]));  
    19.         inputJar = new JarFile(new File(args[2]), false);  // Don't verify.  
    20.         outputJar = new JarOutputStream(new FileOutputStream(args[3]));  
    21.         outputJar.setLevel(9);  
    22.   
    23.         JarEntry je;  
    24.   
    25.         // MANIFEST.MF  
    26.         Manifest manifest = addDigestsToManifest(inputJar);  
    27.         je = new JarEntry(JarFile.MANIFEST_NAME);  
    28.         je.setTime(timestamp);  
    29.         outputJar.putNextEntry(je);  
    30.         manifest.write(outputJar);  
    31.   
    32.         // CERT.SF  
    33.         Signature signature = Signature.getInstance("SHA1withRSA");  
    34.         signature.initSign(privateKey);  
    35.         je = new JarEntry(CERT_SF_NAME);  
    36.         je.setTime(timestamp);  
    37.         outputJar.putNextEntry(je);  
    38.         writeSignatureFile(manifest,  
    39.                 new SignatureOutputStream(outputJar, signature));  
    40.   
    41.         // CERT.RSA  
    42.         je = new JarEntry(CERT_RSA_NAME);  
    43.         je.setTime(timestamp);  
    44.         outputJar.putNextEntry(je);  
    45.         writeSignatureBlock(signature, publicKey, outputJar);  
    46.   
    47.         // Everything else  
    48.         copyFiles(manifest, inputJar, outputJar, timestamp);  
    49.     } catch (Exception e) {  
    50.         e.printStackTrace();  
    51.         System.exit(1);  
    52.     } finally {  
    53.         try {  
    54.             if (inputJar != null) inputJar.close();  
    55.             if (outputJar != null) outputJar.close();  
    56.         } catch (IOException e) {  
    57.             e.printStackTrace();  
    58.             System.exit(1);  
    59.         }  
    60.     }  
    61. }  

    在main函数中,我们看到需要输入四个参数,然后就做了三件事:

     

    写MANIFEST.MF

    [java] view plain copy

    1. //MANIFEST.MF  
    2. Manifest manifest = addDigestsToManifest(inputJar);  
    3. je = new JarEntry(JarFile.MANIFEST_NAME);  
    4. je.setTime(timestamp);  
    5. outputJar.putNextEntry(je);  
    6. manifest.write(outputJar);  

    在进入方法看看:

     

     

    [java] view plain copy

    1. /** Add the SHA1 of every file to the manifest, creating it if necessary. */  
    2. private static Manifest addDigestsToManifest(JarFile jar)  
    3.         throws IOException, GeneralSecurityException {  
    4.     Manifest input = jar.getManifest();  
    5.     Manifest output = new Manifest();  
    6.     Attributes main = output.getMainAttributes();  
    7.     if (input != null) {  
    8.         main.putAll(input.getMainAttributes());  
    9.     } else {  
    10.         main.putValue("Manifest-Version", "1.0");  
    11.         main.putValue("Created-By", "1.0 (Android SignApk)");  
    12.     }  
    13.   
    14.     BASE64Encoder base64 = new BASE64Encoder();  
    15.     MessageDigest md = MessageDigest.getInstance("SHA1");  
    16.     byte[] buffer = new byte[4096];  
    17.     int num;  
    18.   
    19.     // We sort the input entries by name, and add them to the  
    20.     // output manifest in sorted order.  We expect that the output  
    21.     // map will be deterministic.  
    22.   
    23.     TreeMap<String, JarEntry> byName = new TreeMap<String, JarEntry>();  
    24.   
    25.     for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {  
    26.         JarEntry entry = e.nextElement();  
    27.         byName.put(entry.getName(), entry);  
    28.     }  
    29.   
    30.     for (JarEntry entry: byName.values()) {  
    31.         String name = entry.getName();  
    32.         if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&  
    33.             !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&  
    34.             (stripPattern == null ||  
    35.              !stripPattern.matcher(name).matches())) {  
    36.             InputStream data = jar.getInputStream(entry);  
    37.             while ((num = data.read(buffer)) > 0) {  
    38.                 md.update(buffer, 0, num);  
    39.             }  
    40.   
    41.             Attributes attr = null;  
    42.             if (input != null) attr = input.getAttributes(name);  
    43.             attr = attr != null ? new Attributes(attr) : new Attributes();  
    44.             attr.putValue("SHA1-Digest", base64.encode(md.digest()));  
    45.             output.getEntries().put(name, attr);  
    46.         }  
    47.     }  
    48.   
    49.     return output;  
    50. }  

    代码逻辑还是很简单的,主要看那个循环的意思:

     

    除了三个文件(MANIFEST.MF,CERT.RSA,CERT.SF),其他的文件都会对文件内容做一次SHA1算法,就是计算出文件的摘要信息,然后用Base64进行编码即可,下面我们用工具来做个案例看看是不是这样:

    首先安装工具: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

    然后还有一个网站就是在线计算Base64:http://tomeko.net/online_tools/hex_to_base64.php?lang=en

    那下面就开始我们的验证工作吧:

    我们就来验证一下AndroidManifest.xml文件,首先在MANIFEST.MF文件中找到这个条目,记录SHA1的值

    然后我们安装HashTab之后,找到AndroidManifest.xml文件,右击,选择Hashtab:



    复制SHA-1的值:9C64812DE7373B201C294101473636A3697FD73C,到上面的那个Base64转化网站,转化一下:

    nGSBLec3OyAcKUEBRzY2o2l/1zw=

    和MANIFEST.MF中的条目内容一模一样啦啦

    那么从上面的分析我们就知道了,其实MANIFEST.MF中存储的是:

    逐一遍历里面的所有条目,如果是目录就跳过,如果是一个文件,就用SHA1(或者SHA256)消息摘要算法提取出该文件的摘要然后进行BASE64编码后,作为“SHA1-Digest”属性的值写入到MANIFEST.MF文件中的一个块中。该块有一个“Name”属性,其值就是该文件在apk包中的路径。

    注:这里增加理解,这是AndroidManifest.xml文件的sha-1 16进制表示,即9C64812DE7373B201C294101473636A3697FD73C ;

    将MANIFEST.MF文件中的SHA1-Digest取出来nGSBLec3OyAcKUEBRzY2o2l/1zw=,这是对应AndroidManifest.xml的SHA-1 Base64表示,将其转化成16进制为9C64812DE7373B201C294101473636A3697FD73C,两者相等。

    2、下面再来看一下CERT.SF文件内容

    这里的内容感觉和MANIFEST.MF的内容差不多,来看看代码吧:

     

    [java] view plain copy

    1. //CERT.SF  
    2. Signature signature = Signature.getInstance("SHA1withRSA");  
    3. signature.initSign(privateKey);  
    4. je = new JarEntry(CERT_SF_NAME);  
    5. je.setTime(timestamp);  
    6. outputJar.putNextEntry(je);  
    7. writeSignatureFile(manifest,new SignatureOutputStream(outputJar, signature));  

    进入到writeSignatureFile方法中:

     

     

    [java] view plain copy

    1. /** Write a .SF file with a digest the specified manifest. */  
    2. private static void writeSignatureFile(Manifest manifest, OutputStream out)  
    3.         throws IOException, GeneralSecurityException {  
    4.     Manifest sf = new Manifest();  
    5.     Attributes main = sf.getMainAttributes();  
    6.     main.putValue("Signature-Version", "1.0");  
    7.     main.putValue("Created-By", "1.0 (Android SignApk)");  
    8.   
    9.     BASE64Encoder base64 = new BASE64Encoder();  
    10.     MessageDigest md = MessageDigest.getInstance("SHA1");  
    11.     PrintStream print = new PrintStream(  
    12.             new DigestOutputStream(new ByteArrayOutputStream(), md),  
    13.             true, "UTF-8");  
    14.   
    15.     // Digest of the entire manifest  
    16.     manifest.write(print);  
    17.     print.flush();  
    18.     main.putValue("SHA1-Digest-Manifest", base64.encode(md.digest()));  
    19.   
    20.     Map<String, Attributes> entries = manifest.getEntries();  
    21.     for (Map.Entry<String, Attributes> entry : entries.entrySet()) {  
    22.         // Digest of the manifest stanza for this entry.  
    23.         print.print("Name: " + entry.getKey() + "\r\n");  
    24.         for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {  
    25.             print.print(att.getKey() + ": " + att.getValue() + "\r\n");  
    26.         }  
    27.         print.print("\r\n");  
    28.         print.flush();  
    29.   
    30.         Attributes sfAttr = new Attributes();  
    31.         sfAttr.putValue("SHA1-Digest", base64.encode(md.digest()));  
    32.         sf.getEntries().put(entry.getKey(), sfAttr);  
    33.     }  
    34.   
    35.     sf.write(out);  
    36. }  

     

    首先我们可以看到,需要对之前的MANIFEST.MF文件整个内容做一个SHA1放到SHA1-Digest-Manifest字段中:

    我们看看出入的manifest变量就是刚刚写入了MANIFEST.MF文件的


    这个我们可以验证一下:

    然后转化一下

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

    下面我们继续看代码,有一个循环:

     

    [java] view plain copy

    1. Map<String, Attributes> entries = manifest.getEntries();  
    2. for (Map.Entry<String, Attributes> entry : entries.entrySet()) {  
    3.     // Digest of the manifest stanza for this entry.  
    4.     print.print("Name: " + entry.getKey() + "\r\n");  
    5.     for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {  
    6.         print.print(att.getKey() + ": " + att.getValue() + "\r\n");  
    7.     }  
    8.     print.print("\r\n");  
    9.     print.flush();  
    10.   
    11.     Attributes sfAttr = new Attributes();  
    12.     sfAttr.putValue("SHA1-Digest", base64.encode(md.digest()));  
    13.     sf.getEntries().put(entry.getKey(), sfAttr);  
    14. }  
    15.   
    16. sf.write(out);  

    这里还是用到了刚刚传入的mainfest变量,遍历他的条目内容,然后进行SHA算法计算在Base64一下:

     

    其实就是对MANIFEST.MF文件中的每个条目内容做一次SHA,在保存一下即可,做个例子验证一下:

    用AndroidManifest.xml为例,我们把MANIFEST.MF文件中的条目拷贝保存到txt文档中:


    这里需要注意的是,我们保存之后,需要添加两个换行,我们可以在代码中看到逻辑:

    然后我们计算txt文档的SHA值:

    看到了吧,这里计算的值是一样的啦啦

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

    1》计算这个MANIFEST.MF文件的整体SHA1值,再经过BASE64编码后,记录在CERT.SF主属性块(在文件头上)的“SHA1-Digest-Manifest”属性值值下

    2》逐条计算MANIFEST.MF文件中每一个块的SHA1,并经过BASE64编码后,记录在CERT.SF中的同名块中,属性的名字是“SHA1-Digest

     

    3、最后我们在来看一下CERT.RSA文件

    这里我们看到的都是二进制文件,因为RSA文件加密了,所以我们需要用openssl命令才能查看其内容

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

    关于这些信息,可以看下面这张图:

    我们来看一下代码:

     

    [java] view plain copy

    1. /** Write a .RSA file with a digital signature. */  
    2. private static void writeSignatureBlock(  
    3.         Signature signature, X509Certificate publicKey, OutputStream out)  
    4.         throws IOException, GeneralSecurityException {  
    5.     SignerInfo signerInfo = new SignerInfo(  
    6.             new X500Name(publicKey.getIssuerX500Principal().getName()),  
    7.             publicKey.getSerialNumber(),  
    8.             AlgorithmId.get("SHA1"),  
    9.             AlgorithmId.get("RSA"),  
    10.             signature.sign());  
    11.   
    12.     PKCS7 pkcs7 = new PKCS7(  
    13.             new AlgorithmId[] { AlgorithmId.get("SHA1") },  
    14.             new ContentInfo(ContentInfo.DATA_OID, null),  
    15.             new X509Certificate[] { publicKey },  
    16.             new SignerInfo[] { signerInfo });  
    17.   
    18.     pkcs7.encodeSignedData(out);  
    19. }  

    我们看到,这里会把之前生成的 CERT.SF文件, 用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入  CERT.RSA  中保存。CERT.RSA是一个满足PKCS7格式的文件。

    这个文件保存了签名和公钥证书。签名的生成一定会有私钥参与,签名用到的信息摘要就是CERT.SF内容。
    signature这个数据会作为签名用到的摘要,writeSignatureBlock函数用privateKey对signature加密生成签名,然后把签名和公钥证书一起保存到CERT.RSA中。
    最终保存在CERT.RSA中的是CERT.SF的数字签名,签名使用privateKey生成的,签名算法会在publicKey中定义。同时还会把publicKey存放在CERT.RSA中,也就是说CERT.RSA包含了签名和签名用到的证书。并且要求这个证书是自签名的。

     

     

    四、为何要这么来签名

    上面我们就介绍了签名apk之后的三个文件的详细内容,那么下面来总结一下,Android中为何要用这种方式进行加密签名,这种方加密是不是最安全的呢?下面我们来分析一下,如果apk文件被篡改后会发生什么。

    首先,如果你改变了apk包中的任何文件,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同,于是验证失败,程序就不能成功安装。
    其次,如果你对更改的过的文件相应的算出新的摘要值,然后更改MANIFEST.MF文件里面对应的属性值,那么必定与CERT.SF文件中算出的摘要值不一样,照样验证失败。
    最后,如果你还不死心,继续计算MANIFEST.MF的摘要值,相应的更改CERT.SF里面的值,那么数字签名值必定与CERT.RSA文件中记录的不一样,还是失败。
    那么能不能继续伪造数字签名呢?不可能,因为没有数字证书对应的私钥。
    所以,如果要重新打包后的应用程序能再Android设备上安装,必须对其进行重签名。

    从上面的分析可以得出,只要修改了Apk中的任何内容,就必须重新签名,不然会提示安装失败,当然这里不会分析,后面一篇文章会注重分析为何会提示安装失败。

     

    五、知识点梳理

    1、数据指纹,签名文件,证书文件的含义

    1》数据指纹就是对一个数据源做SHA/MD5算法,这个值是唯一的

    2》签名文件技术就是:数据指纹+RSA算法

    3》证书文件中包含了公钥信息和其他信息

    4》在Android签名之后,其中SF就是签名文件,RSA就是证书文件我们可以使用openssl来查看RSA文件中的证书信息和公钥信息

    2、我们了解了Android中的签名有两种方式:jarsigner和signapk 这两种方式的区别是:

    1》jarsigner签名时,需要的是keystore文件,而signapk签名的时候是pk8,x509.pem文件

    2》jarsigner签名之后的SF和RSA文件名默认是keystore的别名,而signapk签名之后文件名是固定的:CERT

    3》Eclipse中我们在跑Debug程序的时候,默认用的是jarsigner方式签名的,用的也是系统默认的debug.keystore签名文件

    4》keystore文件和pk8,x509.pem文件之间可以互相转化

     

    六、思考

    我们在分析了签名技术之后,无意中发现一个问题,就是CERT.SF,MANIFEST.MF,这两个文件中的内容的name字段都是apk中的资源名,那么就有一个问题了,如果资源名很长,而且apk中的资源很多,那么这两个文件就会很大,那么这里我们是不是可以优化呢?后面在分析如何减小apk大小的文章中会继续讲解,这里先提出这个问题。

     

    singapk源码

    参考文章:

    1.Android为什么要为app签名

    2.签名的方法

    3.消息摘要、数字签名、数字证书

    4.signApk项目

    5.提取CERT.RSA中的公钥和签名信息

    展开全文
  • android 为什么需要签名

    万次阅读 2011-10-27 20:27:38
    所有的Android应用程序都要求开发人员用一个证书进行数字签名,anroid系统不会安装没有进行签名的由于程序。  平时我们的程序可以在模拟器上安装并运行,是因为在应用程序开发期间,由于是以Debug面试进行编译的,...

     所有的Android应用程序都要求开发人员用一个证书进行数字签名,anroid系统不会安装没有进行签名的由于程序。
        平时我们的程序可以在模拟器上安装并运行,是因为在应用程序开发期间,由于是以Debug面试进行编译的,因此ADT根据会自动用默认的密钥和证书来进行签名,而在以发布模式编译时,apk文件就不会得到自动签名,这样就需要进行手工签名。
        给apk签名可以带来以下好处:
        1. 应用程序升级:如果你希望用户无缝升级到新的版本,那么你必须用同一个证书进行签名。这是由于只有以同一个证书签名,系统才会允许安装升级的应用程序。如果你采用了不同的证书,那么系统会要求你的应用程序采用不同的包名称,在这种情况下相当于安装了一个全新的应用程序。如果想升级应用程序,签名证书要相同,包名称要相同!
        2.应用程序模块化:Android系统可以允许同一个证书签名的多个应用程序在一个进程里运行,系统实际把他们作为一个单个的应用程序,此时就可以把我们的应用程序以模块的方式进行部署,而用户可以独立的升级其中的一个模块
        3.代码或者数据共享:Android提供了基于签名的权限机制,那么一个应用程序就可以为另一个以相同证书签名的应用程序公开自己的功能。以同一个证书对多个应用程序进行签名,利用基于签名的权限检查,你就可以在应用程序间以安全的方式共享代码和数据了。
        不同的应用程序之间,想共享数据,或者共享代码,那么要让他们运行在同一个进程中,而且要让他们用相同的证书签名。

        怎样签名呢?用java自带的keytool和jarsigner工具。
    1. 通过以下命令可以获取证书和私钥:keytool -genkey -v -keystore doumiw.keystore -alias doumiw -keyalg RSA -validity 10000
    2. 通过以下命令可以对apk签名:jarsigner -verbose -keystore doumiw.keystore doumiw.apk doumiw

     

     

     

    一下是android 签名的转载:

    http://ijavagos.iteye.com/blog/1057666

     

    1. 为什么要签名

    1) 发送者的身份认证
    由于开发商可能通过使用相同的 Package Name 来混淆替换已经安装的程序,以此保证签名不同的包不被替换

    2) 保证信息传输的完整性
    签名对于包中的每个文件进行处理,以此确保包中内容不被替换

    3) 防止交易中的抵赖发生, Market 对软件的要求

    2. 签名的说明

    1) 所有的应用程序都必须有数字证书, Android 系统不会安装一个没有数字证书的应用程序

    2) Android 程序包使用的数字证书可以是自签名的,不需要一个权威的数字证书机构签名认证

    3) 如果要正式发布一个 Android 应用,必须使用一个合适的私钥生成的数字证书来给程序签名,而不能使用 adt 插件或者 ant 工具生成的调试证书来发布

    4) 数字证书都是有有效期的, Android 只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中,即使证书过期也不会影响程序的正常功能

    5) 签名后需使用 zipalign 优化程序

    6) Android 将数字证书用来标识应用程序的作者和在应用程序之间建立信任关系,而不是用来决定最终用户可以安装哪些应用程序

    3. 签名的方法



    4. 签名的相关文件


    1) apk 包中签名相关的文件在 META_INF 目录下
    CERT.SF
    :生成每个文件相对的密钥
    MANIFEST.MF
    :数字签名信息
    xxx.SF
    :这是 JAR 文件的签名文件,占位符 xxx 标识了签名者
    xxx.DSA
    :对输出文件的签名和公钥

    2) 相关源码
    development/tools/jarutils/src/com.anroid.jarutils/SignedJarBuilder.java
    frameworks/base/services/java/com/android/server/PackageManagerService.java
    frameworks/base/core/java/android/content/pm/PackageManager.java
    frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java
    dalvik/libcore/security/src/main/java/java/security/Sign*
    build/target/product/security/platform.*
    build/tools/signapk/*



    5. 签名的相关问题

    一般在安装时提示出错: INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES

    1) 两个应用,名字相同,签名不同

    2) 升级时前一版本签名,后一版本没签名

    3) 升级时前一版本为 DEBUG 签名,后一个为自定义签名

    4) 升级时前一版本为 Android 源码中的签名,后一个为 DEBUG 签名或自定义签名

    5) 安装未签名的程序

    6) 安装升级已过有效期的程序



    6. 相关工具



    7. 参考
    http://developer.android.com/guide/publishing/app-signing.html
    http://www.pgcw.com.cn/Newsdetail.asp?id=257565010
    http://www.eoeandroid.com/thread-23010-1-1.html
    http://pepa.javaeye.com/blog/250991

    1) 查看某个 x509 证书的的有效日期
    SignApk.java 中打印出 publicKey.getNotAfter() 即可

    a) 使用源码中的默认签名
    在源码中编译一般都使用默认签名的,在某源码目录中用运行
    $ mm showcommands
    能看到签名命令
    Android
    提供了签名的程序 signapk.jar ,用法如下:
    $ signapk publickey.x509[.pem] privatekey.pk8 input.jar output.jar
    *.x509.pem
    x509 格式公钥, pk8 为私钥
    build/target/product/security
    目录中有四组默认签名可选: testkey, platform, shared, media (具体见 README.txt ),应用程序中 Android.mk 中有一个 LOCAL_CERTIFICATE 字段,由它指定用哪个 key 签名,未指定的默认用 testkey.

    b) 在源码中自签名
    Android
    提供了一个脚本 mkkey.sh build/target/product/security/mkkey.sh ),用于生成密钥,生成后在应用程序中通过 Android.mk 中的 LOCAL_CERTIFICATE 字段指名用哪个签名

    c) mkkey.sh 介绍

    i. 生成公钥
    openssl genrsa -3 -out testkey.pem 2048
    其中 -3 是算法的参数, 2048 是密钥长度, testkey.pem 是输出的文件

    ii. 转成 x509 格式(含作者有效期等)
    openssl req -new -x509 -key testkey.pem -out testkey.x509.pem -days 10000 -subj ‘/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com

    iii. 生成私钥
    openssl pkcs8 -in testkey.pem -topk8 -outform DER -out testkey.pk8 -nocrypt
    把的格式转换成 PKCS #8 ,这里指定了 -nocryp ,表示不加密,所以签名时不用输入密码


     

    展开全文
  • Android APP的签名

    千次阅读 2018-09-01 18:17:14
    Android APP的签名 Android项目以它的包名作为唯一的标识,如果在同一部手机上安装两个包名相同的APP,后者就会覆盖前面安装的应用。为了避免Android APP被随意覆盖,Android要求对APP进行签名。下面介绍对APP进行...

    Android APP的签名

    Android项目以它的包名作为唯一的标识,如果在同一部手机上安装两个包名相同的APP,后者就会覆盖前面安装的应用。为了避免Android APP被随意覆盖,Android要求对APP进行签名。下面介绍对APP进行签名的步骤

    1、选择builder菜单下的Generate Signed APK

    2、弹出签名向导对话框

    3、在该对话框中选择数字证书,如果没有数字证书,可以点击Create new按钮,创建数字证书如下图所示:

    4、输入证书的存储路径及文件名称,密码,有效年份,发布人员的姓名,单位,所在城市,省份,国家等信息,后点击OK按钮,如下图所示,系统会自动带入密码

    5、点击Next选择签名后的安装包存放路径,构建类型,点击finish完成安装包的构建

    注意:

    1. v2是Android 7.0中引入了签名版本,v1是jar Signature来自JDK,只勾选v1签名并不会影响什么,但是在7.0上不会使用更安全的验证方式,只勾选V2签名7.0以下会直接安装完显示未安装,7.0以上则使用了V2的方式验证,为了保证兼容性,可以同时勾选V1和V2。
    2. 在Debug调试版本中,默认会调用调试用的签名证书debug.keystore,该证书默认存放在C:\Users\<你的用户名>\.android下。
    3. 包名和签名都相同的APP才可以覆盖安装

    (张伟:2018年9月1日)

    (转载时请注明来源)

    展开全文
  • Android 签名打包

    千次阅读 2018-06-05 11:37:11
    Android 要求所有已安装的应用程序都使用数字证书做数字签名,数字证书的私钥由开发者持有。Android 使用证书作为标识应用程序作者的一种方式,证书不需要由证书认证中心签名,使用自制签名证书。Android 系统不会...


    什么是签名?

    Android 要求所有已安装的应用程序都使用数字证书做数字签名,数字证书的私钥由开发者持有。

    Android 使用证书作为标识应用程序作者的一种方式,证书不需要由证书认证中心签名,使用自制签名证书。

    Android 系统不会安装或运行没有正确签名的应用,此规则适用于任何地方运行的Android系统。因此在真机或模拟器上运行或者调试应用前,必须为其设置好签名。

    两种签名:

    1、调试模式下签名   (sdk 为应用主动生成一个签名证书,调试模式下签名的应用不能对外发布,因为由构建工具创建的证书是不安全的,应用商店不接受调试证书签名的apk)

    2、公布模式下签名 (需要生成自己的证书)

    注:给自己开发的app签名,就代表着我们自己的版权,之后要进行升级,也必须要使用相同的签名才可以,签名代表着自己的身份(即 keystore,是一个包括私人秘钥集合的二进制文件),创建的keystore 多个app可以使用同一签名。


    为什么要有签名?

    开发Android的人众多,完全有可能把雷鸣、包名命名成相同的名字,需要签名来区分,由于开发商可能通过使用相同包名来混淆替换已经安装的程序,签名可以保证相同名字,但是签名不同的包不被替换。

    签名机制在Android应用和框架中有着十分重要的作用,例如:Android系统禁止跟新安装签名不一致的apk,若应用需要使用system权限,必须保证apk签名与framwork签名一致。


    为什么要这么做?

    •  应用程序升级 -  当发布应用的更新时,如果想染给用户无缝的升级到新版本,需要继续使用相同的某个或某套证书来签名更新包,当系统安装应用的更新时,它会比较现在的版本和新版本的证书,如果证书吻合,包括证书数据和顺序都吻合,那么系统允许更新,如果新版本所做的签名不是匹配的,那么将需要给用起一个不同的包名 - 在这种情况下,用户相当于安装了一个完全新的程序。
    • 用用程序模块化 - Android允许相同证书签名的应用程序运行在相同的进程中,此时系统会将它们作为耽搁应用程序对待,在这种方式中,可以按模块化的凡事部署应用,用户可以根据需要独立的跟新每一个模块。
    • 代码、数据的授权共享 - Android提供模式匹配的权限控制机制,因此一个应用可以暴露功能给另一个用指定证书签名的签名的应用,通过用相同证书签名多个应用,以及使用模式匹配的权限检查,应用程序可以以安全的方式共享代码和数据。


    公钥和私钥的概念

    在现代密码体制中加密和解密是采用不同的秘钥(公开秘钥),也就是公开秘钥算法(也叫非对称算法、双钥算法),每个通信方均需要两个秘钥,即公钥和私钥,这两个秘钥可以互为加解密,公钥是公开的,不要保密,而私钥是由个人自己持有,并且必须妥善保管和注意保密的。


    证书的概念

    数字证书是由证书认证机构(CA)对证书申请者真实身份验证之后,用CA的根证书对申请人的一些基本信息以及申请人的公钥进行签名(相当于加盖发证书机构的公章)后形成的数字文件。CA完成签发证书后,会将证书发布在CA的证书库(目录服务器)中,任何人都可以查询和下载,因此数字证书和公钥一样是公开的,实际上数字证书就是经过CA认证的公钥。

    原则:

    • 一个公钥对应一个私钥
    • 秘钥对中,让大家都知道的是公钥,不告诉大家只有自己知道的是私钥
    • 如果用其中一个秘钥可以解密,那么该数据必须是对应的秘钥进行的加密
    • 非对称秘钥密码的主要应用就是公钥加密和公钥认证,而公钥加密的过程和公钥认证的过程是不一样的

    例:两个用户 A 和 B ,A要把一段明文通过双钥加密的技术发送给 B , B有一对公钥和私钥,那么加密解密过程如下

        1. B 将他的公钥传送给A

        2. A 用 B 的公钥加密的他的消息,然后传送给 B

        3. B 用他的私钥解密 A 的消息


    调试版相关问题

    证书到期问题:用来签署apk调试的自签名证书有效期365天,到期后只需删除该debug.keystore文件。

    文件存储位置 --- OS 和 Linux 系统:~/.android /

                         --- windows 7, 8, 10 : C:\Users\<user>\.android\


    发布版的签名

    1. 手动生成签名的 apk (使用 Android Studio 手动生成签名的 apk,每次发布不同版本的时候都需要手动生成一次,比较麻烦)

    步骤如下:

        1> 在Android Studio菜单栏中,Build --> Generate Signed APK

            

        2>如果你已经有一个秘钥库,请转到步骤4,如果你想创建一个新的秘钥库,单击新建

            

        注:Key store path: 秘钥库存储位置

               Key store password:秘钥库安全密码

               Key alias:秘钥标识名称

               Key password: 秘钥安全密码

               秘钥安全密码应当与秘钥库安全密码不同


        3>创建新的秘钥库如下图

             

        

        注:秘钥的有效时间设置以年为单位,应至少为25年,以便您可以在应用的整个生命周期内使用相同的秘钥签署应用更新

        Certificate 部分是为证书输入关于您自己的信息,此信息不会显示在应用中,但会作为apk的一部分包含在您的证书中

        Firstand Last Name 秘钥颁发者姓名

        OrganizationalUnit 秘钥颁发者组织单位

        Organization 组织

        Cityor Locality 城市

        State or Province 市或洲

        CountryCodeXX 国家代码


        4>在生成签名apk窗口中,选择秘钥库、秘钥,并输入两个密码(如果是新创秘钥,这些字段会自动填充)然后单击xiayi

        

        


        5>选择签署的apk目的地,构建类型,产品风味,单击完成


        

        

        APK Destination Folder 为签署的APK选择一个目的地


        Build Type 选择构建的类型(两种类型调试和正式


        Flavors 选择产品风味(即我们平时所说的发布平台,Android将为选择的每个产品风味生成单独的apk

        Signature Versions 签名版本勾选 Android7.0中引入了APKSignature Scheme v2

        

        说明:v1:应用是通过zip条目进行验证,这样apk签署后可进行许多修改 - 可以移动甚至重新压缩文件

        v2:验证压缩文件的所有字节,而不是单个zip条目,签名后无法再更改


    2. 配置 build.gradle 文件自动签名apk

    步骤如下:

        1>Android Studio 菜单栏 File --> Project Structure ( 快捷键 ctrl+alt +shift +s)

        2>选中app这个module,然后切换到singning标签栏,然后点击添加,生成release签名信息,点击ok

        

        3>切换到Build Types 标签,将Signing config 选为“release”,即将刚刚生成的release签名信息配置进去

        

        

        4>随后我们可以看到app这个module的build.gradle文件多出了如下部分代码

          


        5>然后执行菜单栏的“build --> clean Project”



    6>生成release版本的apk,在命令行terminal输入gradlew assembleRelease (AS已经将命令行Terminal 继承到了软件当中)运行成功的话,效果如下:



    7> 生成签名好的文件在如下位置:




    展开全文
  • Android 系统签名实现的三种方式

    万次阅读 2019-07-20 12:29:03
    常用的系统签名方式包括在ubuntu环境下、手动签名和在AndroidStudio环境配置,三种方式中,实现最简单的是通过AndroidStudo方式,该方式的签名实现与正常的APK签名相同,唯一不同的就是签名文件是通过系统生成的。...
  • Android签名

    2017-12-14 16:02:54
    1. 为什么要签名1) 发送者的身份认证 由于开发商可能通过使用相同的Package Name来混淆替换已经安装的程序,... 签名的说明1) 所有的应用程序都必须有数字证书,Android系统不会安装一个没有数字证书的应用程序2) A
  • Android 签名过程

    2017-10-26 13:48:46
    前提想要使用签名需要弄懂以下几个问题: 什么是签名签名需要什么? 如何生成签名文件? 如何签名? 如何验证apk是否签名? 1.什么是签名这个大家应该都熟悉,可以理解为就是给自己要发布的apk签个名,代表apk是...
  • 一,前言dai'zhe一,生成签名文件
  • Android Studio系列-签名打包

    万次阅读 多人点赞 2015-03-25 21:07:43
    Android Studio系列-签名打包前言 本篇博客纪录使用Android Studio对项目进行签名打包,跟Eclipse大同小异,读者朋友注意其中到差别。 第一步 创建签名文件 第二步 填写签名参数 第三步 选择构建类型 第四步 查看...
  • 查看Android应用签名信息

    万次阅读 2014-12-03 17:58:58
    本文档介绍在Android下如何查看自己的应用签名及三方APK或系统APK签名信息,包含其中的MD5、SHA1、SHA256值和签名算法等信息。 1、查看自己的应用签名 可以通过两种方式查看 (1) 通过Eclipse查看默认的...
  • Android Studio 导出未签名 apk

    万次阅读 2016-08-21 18:25:08
    According to Android: Build Unsigned APK with Gradle you can simply build your application with gradle.In order to do that:click on the drop down menu on the toolbar at the top (usually with android ...
  • 利用Android Studio查看自己的应用签名: 1.首先在Terminal控制台中,输入:keytool -list -keystore -v -keystore + jks文件的绝对路径,keytool -list -keystore -v -keystore C:\Users\Administrator\Desktop\...
  • Android studio 创建签名文件

    千次阅读 2016-01-24 17:32:48
    Android studio创建数字签名: 这里是设置应用程序数字签名和创建数字签名的选项。点击后进来。 创建一个新的或者选择之前创建的数字签名,这里创建一个新的。 注意要记住保存的路径和密码。 根据自己的信息...
  • android studio打包apk,生成签名签名

    万次阅读 2017-05-09 16:48:27
    文本以2种方式讲述了在android studio打包apk的方式,如何打包签名、不签名、debug、test模式。
  • Android 签名时 v2 与 v1 的选择

    万次阅读 2017-07-06 13:01:09
    注:对于 v2 与 v1 的对比不在此文讨论范围结论: ... 如果要支持 Android 7.0 以下版本,那么尽量同时选择两种签名方式,但是一旦遇到签名问题,可以只使用 v1 签名方案 签名打包时签名版本(Signature
  • Android Studio自动生成带系统签名的apk

    万次阅读 热门讨论 2016-06-01 17:32:12
    如果你需要开发一个带有系统权限的app,往往需要配置SharedUserId,比如: <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... android:sharedUserId="android.uid.shell">
  • 切换到Android Studio后,如何查看呢?找了半天没找到。那就老办法命令行。 第一步、打开Android Studio的Tools->Open Terminal...(新版本已经调整了位置,如下图所示) 第二步、输入命令:keytool -v -...
  • Android Studio debug使用release的签名

    万次阅读 2015-08-03 16:09:15
    当我们在做微信微博sdk分享的时候调试非常麻烦,因为要使用对应的签名版本才能调用sdk成功。 当我们使用AndroidStudio的Gradle之后会很简单的解决这个问题。 1.我们把签名文件放到工程根目录下(这样做是为了保持...
  • 首先,要想生成Android App的签名文件必须先配好Android开发环境,因为签名文件的生成需要进入jdk中的bin目录,如果还未配好开发环境,请自行百度。 下面,我们开始生成自己的签名文件, 第一步,打开cmd,进入到...
  • Android Studio 使用正式签名进行调试

    万次阅读 2016-03-21 17:02:03
    Android Studio中,可以使用Gradle进行打包时自动签名。其实Android Studio默认会给调试应用加上Debug签名,但有时候调一些第三方SDK时,需要正式签名才能调起来,所以接下来分享一下使用Gradle自动签名的方法。 ...
1 2 3 4 5 ... 20
收藏数 77,770
精华内容 31,108
关键字:

android签名