Android 运行中效验文件完整合法性

一.概述

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

二.实现

1.文件完整性效验

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

    /**
     * get file md5
     * @param file
     * @return
     * @throws NoSuchAlgorithmException
     * @throws IOException
     */
    private static String getFileMD5(File file) throws NoSuchAlgorithmException, IOException {
        if (!file.isFile()) {
            return null;
        }
        MessageDigest digest;
        FileInputStream in;
        byte buffer[] = new byte[1024];
        int len;
        digest = MessageDigest.getInstance("MD5");
        in = new FileInputStream(file);
        while ((len = in.read(buffer, 0, 1024)) != -1) {
            digest.update(buffer, 0, len);
        }
        in.close();
        BigInteger bigInt = new BigInteger(1, digest.digest());
        return bigInt.toString(16);
    }

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了.

    /**
     * get local app rsa public key
     * @param ctx
     * @return
     * @throws IOException
     * @throws PackageManager.NameNotFoundException
     * @throws CertificateException
     */
    private static String getLocalSignature(Context ctx) throws IOException,
            PackageManager.NameNotFoundException, CertificateException {
        String signCode = null;
        //get signature info depends on package name
        PackageInfo packageInfo = ctx.getPackageManager().getPackageInfo(
                ctx.getPackageName(), PackageManager.GET_SIGNATURES);
        Signature[] signs = packageInfo.signatures;
        Signature sign = signs[0];
        CertificateFactory certFactory = CertificateFactory
                .getInstance("X.509");
        X509Certificate cert = (X509Certificate) certFactory
                .generateCertificate(new ByteArrayInputStream(sign.toByteArray()));
        String pubKey = cert.getPublicKey().toString();
        String ss = subPublicSignature(pubKey);
        ss = ss.replace(",", "");
        ss = ss.toLowerCase();
        int aa = ss.indexOf("modulus");
        int bb = ss.indexOf("publicexponent");
        signCode = ss.substring(aa + 8, bb);

        return signCode;
    }

3)获取外部文件签名

获取外部文件签名的过程其实可以参考Android内部效验apk文件的过程,Android源码中的PackageParser类会在安装apk之前对apk文件做合法性效验,但是遗憾的是这个类被标注为hide了,所以我们不能直接使用.那么就只剩下两个方法了,一个是通过反射使用PackageParser的方法,一个看源码中效验这部分的实现然后抠出来自己实现.

/**
 * Package archive parsing
 *
 * {@hide}
 */
public class PackageParser {
    //source/frameworks/base/core/java/android/content/pm
}

在当前的使用场景下不推荐使用反射,一是使用反射降低效率还有风险,二是效验这部分并没有依赖Android其他部分,只是依赖了JDK中的JarFile.所以扣源码自己实现来得比较实在,这里就不分析使用反射验证的过程了,直接上源码.

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

    public boolean collectCertificates(Package pkg, int flags) {
        //.................
        JarFile jarFile = new JarFile(mArchiveSourcePath);
        //.................
        JarEntry jarEntry = jarFile.getJarEntry(ANDROID_MANIFEST_FILENAME);
        //.................
        certs = loadCertificates(jarFile, jarEntry, readBuffer);
        pkg.mSignatures = null;
        //.................
    }

这个loadCertificates方法需要特别说一下,因为一开始我是看完源码之后自己写实现的,看这个方法的时候每注意注释,就把读文件流什么也没干的步骤略过了,直接通过JarEntry.getCertificates获取证书.结果换了好几个签过名的文件都获取不到证书,重新看了下源码才发现注释中的必须使用JarEntry读文件流才能接收到证书信息......不作死就不会死.拿到证书之后就跟之前2)中的步骤一样了,直接get公钥,然后截取字符串将RSA
public key截出来,最后跟2)中的结果比对就可以做合法性效验了.

    private static Certificate[] loadCertificates(JarFile jarFile, JarEntry je,
                                                  byte[] readBuffer) throws IOException {
        // We must read the stream for the JarEntry to retrieve
        // its certificates.
        InputStream is = new BufferedInputStream(jarFile.getInputStream(je));
        while (is.read(readBuffer, 0, readBuffer.length) != -1) {
            // not using
        }
        is.close();
        return je != null ? je.getCertificates() : null;
    }

转载请注明出处:http://blog.csdn.net/l2show/article/details/48182367

时间: 2024-08-12 20:59:28

Android 运行中效验文件完整合法性的相关文章

Android项目中gen文件下R文件无法生成的解决的方法

帮一个网友解决R文件无法生成的问题,搜集了些材料特整理例如以下,刚開始学习的人參考他人代码时极易出现此种问题,一般都是xml文件出错,无法被正确解析. gen文件夹无法更新,或者gen文件夹下的R.JAVA文件无法生成 1.gen文件夹的用处 android gen文件夹下的R.java并非由用户创建,而是androidproject本身将android的资源进行自己主动"编号"(ID)值. 2.gen文件夹下R文件无法更新/生成的原因 1)res文件夹下的layout下的xml文件名

Android项目中gen文件下R文件无法生成的解决办法

帮一个网友解决R文件无法生成的问题,搜集了些材料特整理如下,初学者参考他人代码时极易出现此种问题,一般都是xml文件出错,无法被正确解析. gen目录无法更新,或者gen目录下的R.JAVA文件无法生成 1.gen目录的用处 android gen目录下的R.java并不是由用户创建,而是android工程本身将android的资源进行自动"编号"(ID)值. 2.gen目录下R文件无法更新/生成的原因 1)res目录下的layout下的xml文件名有错.按照android的命名规范是

用adb pull命令从android系统中读取文件失败的原因及解决办法

问题:使用adb pull命令从android系统中读取文件失败.显示:Permission denied 原因:是由于文件权限原因引起. 使用ls -l命令查看android系统中的文件权限为: -rw-rw---- app_51   app_51 也就是说,该文件只有app_51用户以及app_51群组拥有读写权限,而adb shell的用户为shell,既不是app_51用户,也不在app_51群组中,所以没有权限读取这个文件.所以就出现了 Permission denied. 解决方法:

android XMl 解析神奇xstream 一: 解析android项目中 asset 文件夹 下的 aa.xml 文件

1.下载工具 xstream 下载最新版本地址: https://nexus.codehaus.org/content/repositories/releases/com/thoughtworks/xstream/ 下载完成后 把jar包导入到自己的android项目中 2.asset 文件夹 下的 aa.xml 文件 <?xml version="1.0" encoding="UTF-8"?><product>    <name>

android studio中R文件变红并报错

昨天晚上碰到一个十分费解的问题,想分享一下,希望能帮到你. 以前用studio是R文件是不报错的.当你从其他程序拷过一些代码是会发现R文件会变红并且应用程序不能运行.除了R文件其他地方没有报错,只有app运行时会报错 . 这是因为当你从其他程序拷过一些代码.你的r文件中没有自动加载,这时你需要把那些报错的代码删除并重新在android studio中输入,这样你的应用程序就可以运行了.

Android数据库中数据文件的导出与查看

当Android开发过程中涉及到数据库的操作,我们通常需要将App的数据库文件(即*.db文件)导出查看,以验证对数据库的增删改查是否正确. 由于真机在没有root的情况下,没有权限访问受保护的数据存储区域,所以为了简单起见,可以选择在模拟器上进行数据文件的导出工作.本文用来验证 一个ContentProvider示例是否成功的进行了数据库插入操作,开发工具为Eclipse ADT(Android API 18),详细的步骤如下:  (1)首先New一个模拟器,具体步骤如下图所示(红框标出操作选

Android Studio中清单文件改versionCode和versionName没效果的原因

在Android Studio中,项目的versionCode 和versionName 的控制不是在AndroidManifest.xml清单文件中更改的,而是在项目的build.gradle中更改的,. 其实在AndroidManifest.xml里已经有提示了的: 然后在build.gradle中会发现相同的声明: 修改后同步一下就行了.

java/android开发中删除文件

在java或者android开发中经常遇到要删除一个文件夹及其子文件的需求,本文主要总结了自己开发中遇到并且使用过的两种删除文件和文件夹的方法. 1.如果仅仅是删除一个文件可以使用以下代码,传入文件路径即可 public static void deleteFile(String path) { File file = new File(path); file.delete(); } 2.如果需要删除一个非空文件夹,则需要遍历整个文件夹下子文件,进行递归删除 public static void

android开发中R文件丢失

R文件在android开发中,占据着中会在重要的地位,里面的内容有系统自动生成,不可随意修改,然而在开发过程中,总是不可知的丢失,这里总结一下修补方法 #.在Eclipse里可以 (1).在不能确认xml文件或图片文件没有错误的情况下,最好不要clear,因为这时只要一clear,那么R文件就会没了,而在众多的xml文件里面找出错误确实不是一件容易的事. (2).如果R文件已经丢失,可以 右键项目-->Android Tools--> fix project properties, (3).检