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

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

  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实现如下

  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. sBouncyCastleProvider = new BouncyCastleProvider();
  9. Security.addProvider(sBouncyCastleProvider);
  10. boolean signWholeFile = false;
  11. int argstart = 0;
  12. if (args[0].equals("-w")) {
  13. signWholeFile = true;
  14. argstart = 1;
  15. }
  16. JarFile inputJar = null;
  17. JarOutputStream outputJar = null;
  18. FileOutputStream outputFile = null;
  19. try {
  20. File publicKeyFile = new File(args[argstart+0]);
  21. X509Certificate publicKey = readPublicKey(publicKeyFile);
  22. // Assume the certificate is valid for at least an hour.
  23. long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
  24. PrivateKey privateKey = readPrivateKey(new File(args[argstart+1]));
  25. inputJar = new JarFile(new File(args[argstart+2]), false);  // Don't verify.
  26. OutputStream outputStream = null;
  27. if (signWholeFile) {
  28. outputStream = new ByteArrayOutputStream();
  29. } else {
  30. outputStream = outputFile = new FileOutputStream(args[argstart+3]);
  31. }
  32. outputJar = new JarOutputStream(outputStream);
  33. // For signing .apks, use the maximum compression to make
  34. // them as small as possible (since they live forever on
  35. // the system partition).  For OTA packages, use the
  36. // default compression level, which is much much faster
  37. // and produces output that is only a tiny bit larger
  38. // (~0.1% on full OTA packages I tested).
  39. if (!signWholeFile) {
  40. outputJar.setLevel(9);
  41. }
  42. JarEntry je;
  43. Manifest manifest = addDigestsToManifest(inputJar);
  44. // Everything else
  45. copyFiles(manifest, inputJar, outputJar, timestamp);
  46. // otacert
  47. if (signWholeFile) {
  48. addOtacert(outputJar, publicKeyFile, timestamp, manifest);
  49. }
  50. // MANIFEST.MF
  51. je = new JarEntry(JarFile.MANIFEST_NAME);
  52. je.setTime(timestamp);
  53. outputJar.putNextEntry(je);
  54. manifest.write(outputJar);
  55. // CERT.SF
  56. je = new JarEntry(CERT_SF_NAME);
  57. je.setTime(timestamp);
  58. outputJar.putNextEntry(je);
  59. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  60. writeSignatureFile(manifest, baos);
  61. byte[] signedData = baos.toByteArray();
  62. outputJar.write(signedData);
  63. // CERT.RSA
  64. je = new JarEntry(CERT_RSA_NAME);
  65. je.setTime(timestamp);
  66. outputJar.putNextEntry(je);
  67. writeSignatureBlock(new CMSProcessableByteArray(signedData),
  68. publicKey, privateKey, outputJar);
  69. outputJar.close();
  70. outputJar = null;
  71. outputStream.flush();
  72. if (signWholeFile) {
  73. outputFile = new FileOutputStream(args[argstart+3]);
  74. signWholeOutputFile(((ByteArrayOutputStream)outputStream).toByteArray(),
  75. outputFile, publicKey, privateKey);
  76. }
  77. } catch (Exception e) {
  78. e.printStackTrace();
  79. System.exit(1);
  80. } finally {
  81. try {
  82. if (inputJar != null) inputJar.close();
  83. if (outputFile != null) outputFile.close();
  84. } catch (IOException e) {
  85. e.printStackTrace();
  86. System.exit(1);
  87. }
  88. }
  89. }

生成MAINFEST.MF文件

  1. Manifest manifest = addDigestsToManifest(inputJar);

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

  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

  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内容。

  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中、

  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. CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
  13. ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA")
  14. .setProvider(sBouncyCastleProvider)
  15. .build(privateKey);
  16. gen.addSignerInfoGenerator(
  17. new JcaSignerInfoGeneratorBuilder(
  18. new JcaDigestCalculatorProviderBuilder()
  19. .setProvider(sBouncyCastleProvider)
  20. .build())
  21. .setDirectSignature(true)
  22. .build(sha1Signer, publicKey));
  23. gen.addCertificates(certs);
  24. CMSSignedData sigData = gen.generate(data, false);
  25. ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
  26. DEROutputStream dos = new DEROutputStream(out);
  27. dos.writeObject(asn1.readObject());
  28. }

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

@data是生成签名的摘要

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

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

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

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

APK签名原理的更多相关文章

  1. [转]Android APK签名原理及方法

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

  2. apk签名原理及实现

    发布过Android应用的朋友们应该都知道,Android APK的发布是需要签名的.签名机制在Android应用和框架中有着十分重要的作用. 例如,Android系统禁止更新安装签名不一致的APK: ...

  3. android apk签名原理

    //这个md5跟腾讯的对应 public Signature getPackageSignature( ){ Context context=getContext(); String packageN ...

  4. eclipse将android项目生成apk并且给apk签名

    转载:http://www.cnblogs.com/tianguook/archive/2012/09/27/2705724.html 生成apk最懒惰的方法是:只要你运行过android项目,到工作 ...

  5. 关于APK签名的一些东西

    什么是APK 了解APK签名之前,首先要知道什么是apk文件:APK是AndroidPackage的缩写,即Android安装包(apk),APK文件其实就是zip格式的文件,只是后缀被改为了apk, ...

  6. 为App签名(为apk签名)

    为App签名(为apk签名) 原文地址 这篇文章是Android开发人员的必备知识,是我特别为大家整理和总结的,不求完美,但是有用. 1.签名的意义 为了保证每个应用程序开发商合法ID,防止部分开放商 ...

  7. Android学习系列(1)--为App签名(为apk签名)

    写博客是一种快乐,前提是你有所写,与人分享,是另一种快乐,前提是你有舞台展示,博客园就是这样的舞台.这篇文章是android开发人员的必备知识,是我特别为大家整理和总结的,不求完美,但是有用. 1.签 ...

  8. 【转】Android学习系列(1)--为App签名(为apk签名)

    原文网址:http://www.cnblogs.com/qianxudetianxia/archive/2011/04/09/2010468.html 写博客是一种快乐,前提是你有所写,与人分享,是另 ...

  9. 与apk签名有关的那些概念与命令

    一.概念篇 1.消息摘要-Message Digest 消息摘要:在消息数据上,执行一个单向的hash函数,生成一个固定长度的hash值,这个Hash值就是消息摘要,也成为数字指纹. 消息摘要特点: ...

随机推荐

  1. Spread 之自定义对角线cellType源码: DiagonalCellType

    最新的SpreadWinform提供了多达24种CellType类型,下面的这2篇博文对新增了GcTextBoxCellType和GcDateTimeCellType单元格格式做了比较详细的说明. & ...

  2. redis 实践—— sorted set, hash set

    在这里就不谈redis的安装与启动啦,网上太多人写这个了. 从最近的一个项目[钻石夺宝]说起,如果大家有玩过一元夺宝或者全名夺宝的话,大概会知道如果参与人数多的话,每隔几秒.快的话每隔一秒都会新生成一 ...

  3. 【Java咬文嚼字】关键字(一):super和this

    这段时间一直在学Java,看了辣么多书以及博客,心痒也是着写写自己的学习心得. 这也算是新手篇:咬文嚼字Java中的关键字. 以关键字为第一篇博文也是考虑再三:1.本人基础也是薄弱 2.集跬步至千里 ...

  4. 初学dorado

    初学dorado 1.dorado使用视图来写界面代码,超级轻松:还需要画流程,页面间的跳转应该很轻松了. 2.先新建dorado项目,再创建dorado视图 3.在Servers里双击tomacat ...

  5. [LCA & RMQ] [NOIP2013] 货车运输

    首先看到这题, 由于要最大, 肯定是求最大生成树 那么 o(n2) dfs 求任意点对之间的最小边是可以想到的 但是看看数据范围肯定TLE 于是暴力出来咯, 不过要注意query的时候判断的时候要 m ...

  6. 第12条:考虑实现Comparable接口

    CompareTo方法没有在Object中声明,它是Comparable接口中的唯一的方法,不但允许进行简单的等同性比较,而且允许执行顺序比较.类实现了Comparable接口,就表明它的实例具有内在 ...

  7. Linux 系统结构详解

    Linux 系统结构详解 Linux系统一般有4个主要部分: 内核.shell.文件系统和应用程序.内核.shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序.管理文件并使用系统 ...

  8. 计算序列中第k小的数

    作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree/p/4046399.html 使用分治算法,首先选择随机选择轴值pivot,并使的序列中比pivot ...

  9. jsonp使用规范

    这两天花了很多时间弄研究jsonp这个东西, 可是无论我怎么弄..TMD就是不进入success函数,并且一直进入error函数...让我着实DT啊. 可以看下我之间的提问(这就是我遇到的烦恼).. ...

  10. cocos2d-x学习笔记------动画人物跑起来吧!

    学习总结: 1.sprintf用来格式化字符串 2.CCSpriteFrame:: frameWithTexture通过图片名创建的时候需要的参数Texture2D创建使用CCTextureCache ...