Android进阶:自定义类加载器加载加密类文件

1,253 阅读4分钟

之前面试的时候有许多面试官问类加载器相关的问题,所以这是一个很重要的知识点。而且对于高级Android研发来讲,懂得更多类加载相关的东西,对开发也会有很多的帮助,比如热更新,类加密等。 其实笔者对类加密比较感兴趣,就稍稍调研了一下。类加密的其实是为了防止APP被反编译,防止反编译的方法有很多种,比如混淆,加固等。自己对类文件进行加密,并自定义类加载器也是一种办法:

首先我们的代码打包编译之后会变成难以读懂的二进制字节码,并且变成.class文件。但是简单的APP编译出来之后可以被反编译,甚至你写的代码完完全全被暴露。你的代码被抄袭,被复制都是小事,重要的其实是你们APP的商业信息有可能被泄露! 下面是一个简单的例子被反编译的场景:

所以为了不让用户轻易的反编译出源代码文件,就要对.class文件进行加密,再通过特殊的加载类的方式解密,并将这个类加载到内存中。

首先说加密,加密无非就是把.class字节码文件进行一些变换,这里面就涉及密码学的知识了!加密的方式有很多种,要想提高保密性,可以考虑DES,AES,RSA。一旦加密算法的源码被公开,其实破解也就是很简单的事情了,所以建议大家还是用高安全性的密码系统,到时候及时的更换密钥。就能一定程度上增加破解难度。

有了加密算法,接下来就是加密一个字节码文件了:

      private static File file = null;
    private static String path = null;
    // 读取已经编译好的正常的class字节码文件
    public static void readClass(String filePath) throws Exception {
        file = new File(filePath);
        path = filePath;
    }

    // 加密生成已加密的class字节码文件
    public static void encrypt() throws Exception {
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(path.substring(0,
                path.lastIndexOf(".class"))
                + "附件.class");
        byte[] b = new byte[1024];
        int ch = 0;
        while ((ch = fis.read(b)) != -1) {
            // 变换b
            b=crypt(b, "encrypt");
            fos.write(b, 0, ch);
        }
    }

这时候,调用readClass方法和encrypt方法,就能在原本XX.class文件的目录下生成XX附件.class。这时候,我们就使用这个副本的字节码文件,删除原来的,下次运行的时候再解密,这样就行了。

对类加密之后,需要用自己的方式把类再加载出来。正常的时候咱们写的类也需要被类加载器加载到内存中。所以,就涉及到类加载器的知识了:

**系统默认三个类加载器,分别是:**BootStrap,ExtClassLoader,AppClassLoader。那么这几个类加载器有什么区别呢?

首先类加载器有父子关系。BootStrap是爷爷(用C++编写,主要负责加载jre/lib/rt.jar),ExtClassLoader是爸爸(主要用于加载JRE/lib/ext/*.jar),AppClassLoader是儿子(用于负责加载ClassPath指定目录下的所有jar)。

所以我们一般写的class文件都是AppClassLoader加载的。那假如我们写了一个类,我们把这个类复制一份,放到ExtClassLoader目录下,那么类加载器会怎么加载呢?这就要提到类加载器的委托机制了。

类加载器的委托机制:当一个线程调用一个类的时候,首先用当前线程的类加载器去加载这个类,这个类加载器一开始不加载,会通知他的上一级类加载器去加载,等到了BootStrap加载器的时候,如果没有就再让调用的下级加载器去加载。如果都没有就报ClassNotFoundException异常。也可以直接指定类加载器去加载。

所以我们可以自己定义一个类加载器,让这个让这个类加载器去加载我们加密过的类。自定义类加载器需要继承ClassLoader类,并且重写findClass方法。

class MyClassLoader extends ClassLoader {
    private String path = null;

    // 设置自定义类加载器的目录
    public MyClassLoader(String path) {
        this.path = path;
    }

    /*
     * findClass和loadClass的区别?
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {           
            File f = new File(path, name.substring(name.lastIndexOf('.') + 1)
                    + ".class");
            FileInputStream fis = new FileInputStream(f);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int ch = 0;
            while ((ch = fis.read()) != -1) {
                bos.write(ch);
            }
            byte[] buf = bos.toByteArray();
            //解密.class
            buf = Z1Encrypt.crypt(buf, "decrypt");
            fis.close();
            bos.close();
            //根据字节码返回这个类
            return defineClass(name, buf, 0, buf.length);
        } catch (Exception e) {
            throw new ClassNotFoundException(name + " is not found!");
        }
    }
}

接着我们用我们的自定义类加载器去解密加载我们加密好的字节码文件

public static void main(String[] args) throws Exception {
        /*Z1Encrypt.readClass("F:\\WorkSpace\\classLoader\\DemoTemp.class");
        Z1Encrypt.encrypt();
        */
        MyClassLoader mcl = new MyClassLoader("F:\\WorkSpace\\classLoader\\");
        Class clazz  = mcl.findClass("DemoTemp");
        Method me = clazz.getMethod("say",null);                       
        Object obj = clazz.newInstance();
        me.invoke(obj, null);
    }

后来成功的解密了字节码文件,并将其加载到内存中,并成功调用了相关的方法了。

以上是一个非常简单的代码加密,并自定义类加载器的方法,仅供学习其中思路和知识。