APK签名原理
网上已有多篇分析签名的类似文章,但是都有一个共同的问题,就是概念混乱,混乱的一塌糊涂。
在了解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包含了签名和签名用到的证书。并且要求这个证书是自签名的。
APK签名原理的更多相关文章
- [转]Android APK签名原理及方法
准备知识:数据摘要 这个知识点很好理解,百度百科即可,其实他也是一种算法,就是对一个数据源进行一个算法之后得到一个摘要,也叫作数据指纹,不同的数据源,数据指纹肯定不一样,就和人一样. 消息摘要算法(M ...
- apk签名原理及实现
发布过Android应用的朋友们应该都知道,Android APK的发布是需要签名的.签名机制在Android应用和框架中有着十分重要的作用. 例如,Android系统禁止更新安装签名不一致的APK: ...
- android apk签名原理
//这个md5跟腾讯的对应 public Signature getPackageSignature( ){ Context context=getContext(); String packageN ...
- eclipse将android项目生成apk并且给apk签名
转载:http://www.cnblogs.com/tianguook/archive/2012/09/27/2705724.html 生成apk最懒惰的方法是:只要你运行过android项目,到工作 ...
- 关于APK签名的一些东西
什么是APK 了解APK签名之前,首先要知道什么是apk文件:APK是AndroidPackage的缩写,即Android安装包(apk),APK文件其实就是zip格式的文件,只是后缀被改为了apk, ...
- 为App签名(为apk签名)
为App签名(为apk签名) 原文地址 这篇文章是Android开发人员的必备知识,是我特别为大家整理和总结的,不求完美,但是有用. 1.签名的意义 为了保证每个应用程序开发商合法ID,防止部分开放商 ...
- Android学习系列(1)--为App签名(为apk签名)
写博客是一种快乐,前提是你有所写,与人分享,是另一种快乐,前提是你有舞台展示,博客园就是这样的舞台.这篇文章是android开发人员的必备知识,是我特别为大家整理和总结的,不求完美,但是有用. 1.签 ...
- 【转】Android学习系列(1)--为App签名(为apk签名)
原文网址:http://www.cnblogs.com/qianxudetianxia/archive/2011/04/09/2010468.html 写博客是一种快乐,前提是你有所写,与人分享,是另 ...
- 与apk签名有关的那些概念与命令
一.概念篇 1.消息摘要-Message Digest 消息摘要:在消息数据上,执行一个单向的hash函数,生成一个固定长度的hash值,这个Hash值就是消息摘要,也成为数字指纹. 消息摘要特点: ...
随机推荐
- cocos2d-x实战 C++卷 学习笔记--第4章 win32平台下中文乱码问题
前言: 将GBK编码的字符串转为UTF-8编码.(通俗点说就是解决中文乱码问题) 简要介绍: 在Win32平台下通过 log 输出中文字符时,会出现中文乱码问题.同样的代码在 ios 和 Androi ...
- Java的基本数据类型
java的基本数据类型是四类八种: 整型 byte 1字节 8位 short 2字节 16位 int 4字节 32位 long 8字节 64位 在hibernate自动映射中会根据数字长度,选 ...
- The method getContextPath() is undefined for the type ServletContext
我出错时,到网上说得是版本问题,我找到对应的包javax-servlet5.1.12.jar,把它删了,居然不报错了,原来是和包servlet-2_5-api.jar冲突了
- mysql innodb 数据打捞(三)innodb 簇不连接页的扫描提取(计划)
操作系统簇大小一般是4K,而innoDB的页大小一般是16K,那么就有可能16K的页没有存储在连续的簇中,这样扫描软件就不会扫描出来这样的页面.为了解决这个问题,决定给软件增加半页扫描功能. 在第一次 ...
- forever start Error: Cannot find module './daemon.v0.10.26'
我的技术博客经常被流氓网站恶意爬取转载.请移步原文:http://www.cnblogs.com/hamhog/p/3590158.html,享受整齐的排版.有效的链接.正确的代码缩进.更好的阅读体验 ...
- sgu 110 Dungeon
这道题是计算几何,这是写的第一道计算几何,主要是难在如何求入射光线的反射光线. 我们可以用入射光线 - 入射光线在法线(交点到圆心的向量)上的投影*2 来计算反射光线,自己画一个图,非常清晰明了. 具 ...
- Executors 构建线程池
Executors包含一系列静态方法,可以用于构建线程池. 返回实现了 ExecutorService 接口的对象: newCachedThreadPool newFixedThreadPool(in ...
- 关于使用工具类org.apache.commons.collections.ListUtils合并List的问题
今天在做项目时,需要将几个List进行合并,于是就用到了apache提供关于List操作的工具类ListUtils,但是在使用的过程中发现一些问题. public static void main(S ...
- Linux内核中的通用双向循环链表
开发中接触Linux越来越多,休息放松之余,免不了翻看翻看神秘的Linux的内核.看到双向链表时,觉得挺有意思的,此文记下. 作为众多基础数据结构中的一员,双向循环链表在各种“教科书”中的实现是相当的 ...
- C语言中的程序终止函数
在C语言的标准库<stdlib.h>中提供了一些与正常或者不正常的程序终止有关的函数,下面分别对其进行简单介绍. 参考文献: [1] C和指针,P298,342 [2] C程序设计语言现代 ...