Android下载文件合法性完整性校验

转载自:http://blog.csdn.net/l2show/article/details/48182367

一.概述

       因为之前项目有动态热修复的功能,在修复的过程中会从服务器上下载一个新的dex文件来替换老的dex文件,所以就牵扯到文件身份效验的问题.通常接口会下发一个MD5值,只是一个MD5值的话就只能做一个完整性效验,并不能确定文件的合法性,如果攻击者模拟接口下发一个正确的MD5值,照样可以替换文件.所以这里就在效验MD5完整性之后再根据签名做合法性效验.

二.实现

1.文件完整性效验

       这里字符串取MD5就不做赘述了.既然要效验文件的完整性,那么就牵扯到取文件MD5摘要,这里是用JDK中的MessageDigest通过读取文件的二进制流,使用update累计更新文件流的MD5摘要来获取整个文件的MD5摘要.拿到文件MD5之后跟接口下发的对比就OK了.所以单纯做文件完整性效验还是很简单的.

[java]  view plain  copy
  1. /** 
  2.  * get file md5 
  3.  * @param file 
  4.  * @return 
  5.  * @throws NoSuchAlgorithmException 
  6.  * @throws IOException 
  7.  */  
  8. private static String getFileMD5(File file) throws NoSuchAlgorithmException, IOException {  
  9.     if (!file.isFile()) {  
  10.         return null;  
  11.     }  
  12.     MessageDigest digest;  
  13.     FileInputStream in;  
  14.     byte buffer[] = new byte[1024];  
  15.     int len;  
  16.     digest = MessageDigest.getInstance("MD5");  
  17.     in = new FileInputStream(file);  
  18.     while ((len = in.read(buffer, 01024)) != -1) {  
  19.         digest.update(buffer, 0, len);  
  20.     }  
  21.     in.close();  
  22.     BigInteger bigInt = new BigInteger(1, digest.digest());  
  23.     return bigInt.toString(16);  
  24. }  
2.文件合法性效验

       文件合法性的效验相比完整性效验就要复杂不少,这里合法性是根据签名来做的,所以起码要简单了解签名的过程,签名之后文件发生了什么变化和怎么获取文件的签名信息等.
       1)签名后产出

       生成签名文件和签名的过程这里就不细说了,主要分析一下签名之后的一些情况.解压签名之后的文件可以看到经过签名生成出来了一个META-INF文件夹,里面一般都是三个文件用来存放所有文件的效验以及签名信息.


       MANIFEST.MF:可以明文查看,对所有文件取BASE64哈希值.


       ANDROIDK.SF:可以明文查看,对所有文件的前三行取BASE64哈希值.


       ANDROIDK.RSA:前面两个文件都只是对文件做了一个哈希摘要,并没有签名公钥等信息,RSA文件里面就包含了我们所需要的信息,其中包含有开发者信息,开发者公钥以及CA根据前两个文件的摘要信息经过私钥加密后的密文.RSA文件是不能查看明文的,这里可以使用openssl的命令来输出该文件的信息.openssl pkcs7 -inform DER -in ANDROIDK.RSA -noout -print_certs -text, 从这个文件的数据结构来看本章需要用到的其实就是公钥,获取文件自身证书信息的公钥,然后对比app自身的签名公钥就可以判断文件的合法性.


       2)获取app自身签名

       通过上面的介绍可以了解到签过名的文件都可以获取到一个基于RSA算法的RSA public key,这里就通过效验这个公钥的方式来验证合法性,app自身的签名信息可以通过PackageInfo获取,获取到之后经过字符串转换和截取,将RSA public key部分摘取出来就OK了.

[java]  view plain  copy
  1. /** 
  2.  * get local app rsa public key 
  3.  * @param ctx 
  4.  * @return 
  5.  * @throws IOException 
  6.  * @throws PackageManager.NameNotFoundException 
  7.  * @throws CertificateException 
  8.  */  
  9. private static String getLocalSignature(Context ctx) throws IOException,  
  10.         PackageManager.NameNotFoundException, CertificateException {  
  11.     String signCode = null;  
  12.     //get signature info depends on package name  
  13.     PackageInfo packageInfo = ctx.getPackageManager().getPackageInfo(  
  14.             ctx.getPackageName(), PackageManager.GET_SIGNATURES);  
  15.     Signature[] signs = packageInfo.signatures;  
  16.     Signature sign = signs[0];  
  17.     CertificateFactory certFactory = CertificateFactory  
  18.             .getInstance("X.509");  
  19.     X509Certificate cert = (X509Certificate) certFactory  
  20.             .generateCertificate(new ByteArrayInputStream(sign.toByteArray()));  
  21.     String pubKey = cert.getPublicKey().toString();  
  22.     String ss = subPublicSignature(pubKey);  
  23.     ss = ss.replace(",""");  
  24.     ss = ss.toLowerCase();  
  25.     int aa = ss.indexOf("modulus");  
  26.     int bb = ss.indexOf("publicexponent");  
  27.     signCode = ss.substring(aa + 8, bb);  
  28.   
  29.     return signCode;  
  30. }  
       3)获取外部文件签名
       获取外部文件签名的过程其实可以参考Android内部效验apk文件的过程,Android源码中的PackageParser类会在安装apk之前对apk文件做合法性效验,但是遗憾的是这个类被标注为hide了,所以我们不能直接使用.那么就只剩下两个方法了,一个是通过反射使用PackageParser的方法,一个看源码中效验这部分的实现然后抠出来自己实现.

[java]  view plain  copy
  1. /** 
  2.  * Package archive parsing 
  3.  * 
  4.  * {@hide} 
  5.  */  
  6. public class PackageParser {  
  7.     //source/frameworks/base/core/java/android/content/pm  
  8. }  
       在当前的使用场景下不推荐使用反射,一是使用反射降低效率还有风险,二是效验这部分并没有依赖Android其他部分,只是依赖了JDK中的JarFile.所以扣源码自己实现来得比较实在,这里就不分析使用反射验证的过程了,直接上源码.

       从PackageParser类中的collectCertificates方法中可以看到如下代码片段.首先根据文件路径将签名后的apk,jar或zip文件装载到JarFile中(JarFile是继承自ZipFile),然后获取文件内容部的某个文件(这部分代码块是获取的manifest文件),再获取到该文件的证书信息.只要能拿到证书信息,那么拿到公钥什么的都是小case了.

[java]  view plain  copy
  1. public boolean collectCertificates(Package pkg, int flags) {  
  2.     //.................  
  3.     JarFile jarFile = new JarFile(mArchiveSourcePath);  
  4.     //.................  
  5.     JarEntry jarEntry = jarFile.getJarEntry(ANDROID_MANIFEST_FILENAME);  
  6.     //.................  
  7.     certs = loadCertificates(jarFile, jarEntry, readBuffer);  
  8.     pkg.mSignatures = null;  
  9.     //.................  
  10. }  
       这个loadCertificates方法需要特别说一下,因为一开始我是看完源码之后自己写实现的,看这个方法的时候每注意注释,就把读文件流什么也没干的步骤略过了,直接通过JarEntry.getCertificates获取证书.结果换了好几个签过名的文件都获取不到证书,重新看了下源码才发现注释中的必须使用JarEntry读文件流才能接收到证书信息......不作死就不会死.拿到证书之后就跟之前2)中的步骤一样了,直接get公钥,然后截取字符串将RSA public key截出来,最后跟2)中的结果比对就可以做合法性效验了.

[java]  view plain  copy
  1. private static Certificate[] loadCertificates(JarFile jarFile, JarEntry je,  
  2.                                               byte[] readBuffer) throws IOException {  
  3.     // We must read the stream for the JarEntry to retrieve  
  4.     // its certificates.  
  5.     InputStream is = new BufferedInputStream(jarFile.getInputStream(je));  
  6.     while (is.read(readBuffer, 0, readBuffer.length) != -1) {  
  7.         // not using  
  8.     }  
  9.     is.close();  
  10.     return je != null ? je.getCertificates() : null;  
  11. }  
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值