android签名机制

http://blog.csdn.net/feiyangxiaomi/article/details/40298155

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文件

先看一下源代码
[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  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 plaincopy在CODE上查看代码片派生到我的代码片
 
  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. <span style="white-space:pre">    </span>......  
  14.         for (JarEntry entry: byName.values()) {  
  15.             String name = entry.getName();  
  16.             if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&  
  17.                 !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&  
  18.                 (stripPattern == null ||  
  19.                  !stripPattern.matcher(name).matches())) {  
  20.                 InputStream data = jar.getInputStream(entry);  
  21.                 while ((num = data.read(buffer)) > 0) {  
  22.                     md.update(buffer, 0, num);  
  23.                 }  
  24.   
  25.                 Attributes attr = null;  
  26.                 if (input != null) attr = input.getAttributes(name);  
  27.                 attr = attr != null ? new Attributes(attr) : new Attributes();  
  28.                 attr.putValue("SHA1-Digest", base64.encode(md.digest()));  
  29.                 output.getEntries().put(name, attr);  
  30.             }  
  31.         }  
  32.   
  33.         return output;  
  34.     }  
遍历APK包中的每一个文件,利用SHA1算法生成这些文件的摘要信息。
验证是所有文件使用的SHA1算法
1.安装hashTab工具
2.打开MANIFEST.MF文件
举个例子:
[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. Name: AndroidManifest.xml  
  2. SHA1-Digest: Zovq4AVMcCjFkILZLlHgmeOLvnU=  
其中找到文件中的AndroidManifest.xml文件,查看其对应的hash值,如图3所示。
    这里取出16进制的668BEAE0054C7028C59082D92E51E099E38BBE75,将16进制通过在线转码网站:hex to base64转化为对应的base64编码,看见“Zovq4AVMcCjFkILZLlHgmeOLvnU=”与记录信息相对的。
 

2.CERT.SF文件

先看一下源码
 
[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
  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,  
  8.                     new SignatureOutputStream(outputJar, signature));  

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. /** Write a .SF file with a digest of the specified manifest. */  
  2.     private static void writeSignatureFile(Manifest manifest, SignatureOutputStream 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, Attributesentries = 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() + " ");  
  24.             for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {  
  25.                 print.print(att.getKey() + ": " + att.getValue() + " ");  
  26.             }  
  27.             print.print(" ");  
  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. <span style="white-space:pre">    </span>//签名信息在上面并没有使用的到  
  35.         sf.write(out);  
  36.   
  37.         // A bug in the java.util.jar implementation of Android platforms  
  38.         // up to version 1.6 will cause a spurious IOException to be thrown  
  39.         // if the length of the signature file is a multiple of 1024 bytes.  
  40.         // As a workaround, add an extra CRLF in this case.  
  41.         if ((out.size() % 1024) == 0) {  
  42.             out.write(' ');  
  43.             out.write(' ');  
  44.         }  
  45.     }  
虽然writeSignatureFile字面上看起来是写签名文件,但是CERT.SF的生成和私钥没有一分钱的关系,实际上也不应该有一分钱的关系,这个文件自然不保存任何签名内容。CERT.SF中保存的是MANIFEST.MF的摘要值(第一项),
[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. Signature-Version: 1.0  
  2. Created-By: 1.0 (Android)  
  3. 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中的一项取出来,然后验证,取出
[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. Name: AndroidManifest.xml  
  2. SHA1-Digest: PJblxooLyYkHHlr/0lKZkk2DkM0=  
在将MANIFEST.MF条目取出,保存为“新建文本文档.txt”,查看对应的消息摘要,并将其转换为base64编码,如图5所示。
图5 新建文本文档.txt的消息摘要和对应的base64编码
这里完成了对CERT.SF的验证。
 

3.CERT.RSA文件

代码为
[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. // CERT.RSA  
  2. je = new JarEntry(CERT_RSA_NAME);  
  3. je.setTime(timestamp);  
  4. outputJar.putNextEntry(je);  
  5. writeSignatureBlock(signature, publicKey, outputJar);  
 
[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
  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内容。
signature这个数据会作为签名用到的摘要,writeSignatureBlock函数用privateKey对signature加密生成签名,然后把签名和公钥证书一起保存到CERT.RSA中。
最终保存在CERT.RSA中的是CERT.SF的数字签名,签名使用privateKey生成的,签名算法会在publicKey中定义。同时还会把publicKey存放在CERT.RSA中,也就是说CERT.RSA包含了签名和签名用到的证书。并且要求这个证书是自签名的。
提取CERT.RSA信息
参考有RSA公钥信息、subject信息、对应的签名信息。
[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. Certificate:  
  2.     Data:  
  3.         Version: 3 (0x2)  
  4.         Serial Number: 1281971851 (0x4c69568b)  
  5.         Signature Algorithm: sha1WithRSAEncryption  
  6.         Issuer: CN=Michael Liu  
  7.         Validity  
  8.             Not Before: Aug 16 15:17:31 2010 GMT  
  9.             Not After : Aug 10 15:17:31 2035 GMT  
  10.         Subject: CN=Michael Liu  
  11.         Subject Public Key Info:  
  12.             Public Key Algorithm: rsaEncryption  
  13.             RSA Public Key: (1024 bit)  
  14.                 Modulus (1024 bit):  
  15.                     00:8d:04:84:a2:1e:c6:56:39:f2:cd:a6:f0:48:a5:  
  16.                     f7:5e:71:8f:e1:a8:af:a7:dc:66:92:a2:b9:cf:da:  
  17.                     0f:32:42:ce:83:fe:bc:e1:4f:0a:fd:d9:a8:b3:73:  
  18.                     f4:ff:97:15:17:87:d6:d0:3c:da:01:fc:11:40:7d:  
  19.                     04:da:31:cc:cd:da:d0:e7:7b:e3:c1:84:30:9f:21:  
  20.                     93:95:20:48:b1:2d:24:02:d2:b9:3c:87:0d:fa:b8:  
  21.                     e1:b1:45:f4:8d:90:0a:3b:9d:d8:8a:9a:96:d1:51:  
  22.                     23:0e:8e:c4:09:68:7d:95:be:c6:42:e9:54:a1:5c:  
  23.                     5d:3f:25:d8:5c:c3:42:73:21  
  24.                 Exponent: 65537 (0x10001)  
  25.     Signature Algorithm: sha1WithRSAEncryption  
  26.         78:3c:6b:ef:71:70:55:68:28:80:4d:f8:b5:cd:83:a9:01:21:  
  27.         2a:c1:e4:96:ad:bc:5f:67:0c:cd:c3:34:51:6d:63:90:a9:f9:  
  28.         d5:5e:c7:ef:34:43:86:7d:68:e1:99:87:92:86:34:91:6d:67:  
  29.         6d:b2:22:e9:5e:28:aa:e8:05:52:04:6e:4e:d4:7f:0f:b0:d6:  
  30.         28:f5:2b:11:38:d5:15:cb:e3:e4:c9:99:23:c1:84:4f:ce:69:  
  31.         e9:b1:59:7b:8e:30:01:1c:e1:92:ee:0d:54:61:29:f5:8e:9e:  
  32.         42:72:26:2b:aa:c7:af:d9:c9:d1:85:95:8e:4c:8d:5c:77:c5:  
  33.         ce:4e  

参考文章:

1.Android为什么要为app签名

2.签名的方法

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

4.signApk项目

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

APK签名原理

分类: Android

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

在了解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 ,使用方法如下:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. 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实现如下

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. public static void main(String[] args) {  
  2.     if (args.length != 4 && args.length != 5) {  
  3.         System.err.println("Usage: signapk [-w] " +  
  4.                 "publickey.x509[.pem] privatekey.pk8 " +  
  5.                 "input.jar output.jar");  
  6.         System.exit(2);  
  7.     }  
  8.   
  9.     sBouncyCastleProvider = new BouncyCastleProvider();  
  10.     Security.addProvider(sBouncyCastleProvider);  
  11.   
  12.     boolean signWholeFile = false;  
  13.     int argstart = 0;  
  14.     if (args[0].equals("-w")) {  
  15.         signWholeFile = true;  
  16.         argstart = 1;  
  17.     }  
  18.   
  19.     JarFile inputJar = null;  
  20.     JarOutputStream outputJar = null;  
  21.     FileOutputStream outputFile = null;  
  22.   
  23.     try {  
  24.         File publicKeyFile = new File(args[argstart+0]);  
  25.         X509Certificate publicKey = readPublicKey(publicKeyFile);  
  26.   
  27.         // Assume the certificate is valid for at least an hour.  
  28.         long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;  
  29.   
  30.         PrivateKey privateKey = readPrivateKey(new File(args[argstart+1]));  
  31.         inputJar = new JarFile(new File(args[argstart+2]), false);  // Don't verify.  
  32.   
  33.         OutputStream outputStream = null;  
  34.         if (signWholeFile) {  
  35.             outputStream = new ByteArrayOutputStream();  
  36.         } else {  
  37.             outputStream = outputFile = new FileOutputStream(args[argstart+3]);  
  38.         }  
  39.         outputJar = new JarOutputStream(outputStream);  
  40.   
  41.         // For signing .apks, use the maximum compression to make  
  42.         // them as small as possible (since they live forever on  
  43.         // the system partition).  For OTA packages, use the  
  44.         // default compression level, which is much much faster  
  45.         // and produces output that is only a tiny bit larger  
  46.         // (~0.1% on full OTA packages I tested).  
  47.         if (!signWholeFile) {  
  48.             outputJar.setLevel(9);  
  49.         }  
  50.   
  51.         JarEntry je;  
  52.   
  53.         Manifest manifest = addDigestsToManifest(inputJar);  
  54.   
  55.         // Everything else  
  56.         copyFiles(manifest, inputJar, outputJar, timestamp);  
  57.   
  58.         // otacert  
  59.         if (signWholeFile) {  
  60.             addOtacert(outputJar, publicKeyFile, timestamp, manifest);  
  61.         }  
  62.   
  63.         // MANIFEST.MF  
  64.         je = new JarEntry(JarFile.MANIFEST_NAME);  
  65.         je.setTime(timestamp);  
  66.         outputJar.putNextEntry(je);  
  67.         manifest.write(outputJar);  
  68.   
  69.         // CERT.SF  
  70.         je = new JarEntry(CERT_SF_NAME);  
  71.         je.setTime(timestamp);  
  72.         outputJar.putNextEntry(je);  
  73.         ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  74.         writeSignatureFile(manifest, baos);  
  75.         byte[] signedData = baos.toByteArray();  
  76.         outputJar.write(signedData);  
  77.   
  78.         // CERT.RSA  
  79.         je = new JarEntry(CERT_RSA_NAME);  
  80.         je.setTime(timestamp);  
  81.         outputJar.putNextEntry(je);  
  82.         writeSignatureBlock(new CMSProcessableByteArray(signedData),  
  83.                             publicKey, privateKey, outputJar);  
  84.   
  85.         outputJar.close();  
  86.         outputJar = null;  
  87.         outputStream.flush();  
  88.   
  89.         if (signWholeFile) {  
  90.             outputFile = new FileOutputStream(args[argstart+3]);  
  91.             signWholeOutputFile(((ByteArrayOutputStream)outputStream).toByteArray(),  
  92.                                 outputFile, publicKey, privateKey);  
  93.         }  
  94.     } catch (Exception e) {  
  95.         e.printStackTrace();  
  96.         System.exit(1);  
  97.     } finally {  
  98.         try {  
  99.             if (inputJar != null) inputJar.close();  
  100.             if (outputFile != null) outputFile.close();  
  101.         } catch (IOException e) {  
  102.             e.printStackTrace();  
  103.             System.exit(1);  
  104.         }  
  105.     }  
  106. }  

生成MAINFEST.MF文件

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. Manifest manifest = addDigestsToManifest(inputJar);  

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

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. // MANIFEST.MF  
  2. je = new JarEntry(JarFile.MANIFEST_NAME);  
  3. je.setTime(timestamp);  
  4. outputJar.putNextEntry(je);  
  5. manifest.write(outputJar);  

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

生成CERT.SF

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. // CERT.SF  
  2. je = new JarEntry(CERT_SF_NAME);  
  3. je.setTime(timestamp);  
  4. outputJar.putNextEntry(je);  
  5. ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  6. writeSignatureFile(manifest, baos);  
  7. byte[] signedData = baos.toByteArray();  
  8. outputJar.write(signedData);  

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

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

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

生成CERT.RSA

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

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. // CERT.RSA  
  2. je = new JarEntry(CERT_RSA_NAME);  
  3. je.setTime(timestamp);  
  4. outputJar.putNextEntry(je);  
  5. writeSignatureBlock(new CMSProcessableByteArray(signedData),  
  6.                     publicKey, privateKey, outputJar);  

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

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. /** Sign data and write the digital signature to 'out'. */  
  2. private static void writeSignatureBlock(  
  3.     CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey,  
  4.     OutputStream out)  
  5.     throws IOException,  
  6.            CertificateEncodingException,  
  7.            OperatorCreationException,  
  8.            CMSException {  
  9.     ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>(1);  
  10.     certList.add(publicKey);  
  11.     JcaCertStore certs = new JcaCertStore(certList);  
  12.   
  13.     CMSSignedDataGenerator gen = new CMSSignedDataGenerator();  
  14.     ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA")  
  15.         .setProvider(sBouncyCastleProvider)  
  16.         .build(privateKey);  
  17.     gen.addSignerInfoGenerator(  
  18.         new JcaSignerInfoGeneratorBuilder(  
  19.             new JcaDigestCalculatorProviderBuilder()  
  20.             .setProvider(sBouncyCastleProvider)  
  21.             .build())  
  22.         .setDirectSignature(true)  
  23.         .build(sha1Signer, publicKey));  
  24.     gen.addCertificates(certs);  
  25.     CMSSignedData sigData = gen.generate(data, false);  
  26.   
  27.     ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());  
  28.     DEROutputStream dos = new DEROutputStream(out);  
  29.     dos.writeObject(asn1.readObject());  
  30. }  

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

@data是生成签名的摘要

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

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

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

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

原文地址:https://www.cnblogs.com/bigben0123/p/4245636.html