Java项目的Class文件加密探讨
2017-12-26王文学
【摘 要】Java 编译的class文件很容易被反编译,不利于保护一些核心的代码。文章分析了class文件的几种加密方案的优缺点,并提出了安全可靠的class文件加密方案。
【关键词】Java;Classloader;JavaAgent
Java 的class 类文件是Java源代码文件经过编译后生成的字节码文件,类文件有固定的结构,比如前四个字节固定为0xCAFEBABE,接下来四个字节是副版本号和主版本号等等。源代码中的变量、常量等一切全在结构中,了解的类文件的固定结构,就能轻易地根据class文件生成java源文件。这对项目某个核心的模块来说,会造成关键技术的泄漏。尤其是涉及到版权的license管理,如何对class类进行加密,是公司必须要考虑的问题。针对这个问题,有各种解决方案,有class混淆方案,也有class加密方案。class加密也有不同的加密技巧和策略。
class混淆器就是在不影响代码执行的条件下,对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能。混淆方法主要有外观混淆、控制混淆、预防混淆、数据混淆。混淆后的代码很难被反编译,但不是不能被反编译,反编译后的程序虽然可读性较差,但整理后程序的程序代码还是可用的,程序逻辑可以修改。如果重写这个类和方法,就可以直接跳过某个校验过程以达到某种目的,比如跳过license的验证或者直接返回许可信息。由于java的ClassLoader可以直接加载这个类,所以修改某个类并直接运行不成问题。安全的方案就是把class直接加密,但加密后的class并不能被ClassLoader直接识别并运行。
由于class文件有固定的结构,ClassLoader将class文件加载到JVM虚拟机,程序就可以运行了。加密后class文件,原来的ClassLoader无法正常解析,要继承并重写自己的ClassLoader类和defineClass()方法,defineClass()能将class二进制内容转换成class对象,类的解密工作也可以在这里进行,也就是在把类字节转交给JVM运行之前,解密class文件为正常的字节码文件。当然,这种解密的方法直接暴露在自定义的ClassLoader类中,可以反编译ClassLoader类来得到解密方法并得到正常的class字节码,把这些字节码写入到文件,再反编译就能得到Java源文件,所以这种方法并不安全。
对于WEB项目,一般来说WEB容器有自己的加载器,比如TOMCAT的catalina.jar,可以修改两个文件,org.apache.catalina.loader.WebappClassLoaderBase.class文件中的findResourceInternal方法和 org.apache.catalina.startup.ContextConfig.class文件中的processAnnotationsJar方法、processAnnotationsJndi方法、processAnnotationsFile方法,在调用processAnnotationsStream方法之前解密类文件,把解密后的字节流传给processAnnotationsStream。这种加密方法比较灵活,可以根据情况只加密解密部分核心的类文件以提高运行效率。当然,这种方法要和具体的TOMCAT版本相结合,如果要使用其它的WEB容器或者不同版本的TOMCAT,都需要修改相应版本的catalina.jar文件,不方便管理。从安全性的角度来看,此种方法和自定义ClassLoader差不多,如果catalina.jar文件和WebappClassLoaderBase.class、ContextConfig.class得不到有效的保护,可以通过修改这两个文件在得到解密后的class文件后再反编译得到源文件。
前面两种方法是用Java来实现解密class文件,本身解密的class能被反编译,这其实是不安全的,当然,你可以嵌套几次加密和解密的过程,但这样操作太过复杂。使用JVMTI编程接口,用C/C++来实现解密过程是个不错的选择。JVMTI是JVM Tool Interface,它提供了本地编程接口,利用JDK中JVM的某些类似钩子机制和事件监听机制,监听加载class事件。通过启动java程序时加参数-agentlib来指定lib库,例如:java -agentlib:libClassLoader,其中libClassLoader是用C/C++编写,可以是Windows下的DLL库,也可以是Linux下的so文件,在libClassLoader中实现对加密class的解密工作。由于C/C++编译后的库文件被反编译基本上是汇编语言,要理解其中的解密逻辑几乎无望。此外,lib还能被加壳,进一步增加了反编译的难度。程序只要在JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)方法中设置ClassFileLoadHook回调方法,并实现自己的ClassFileLoadHook方法即可。实践证明,这是一个行之有效的运行class解密方案。
此外,加密方法的选择直接影响了java项目的运行效率。Class加密可以选择对称加密,也可以选择非对称的加密算法,如RSA,class文件可以完整加密,也可以部分加密。安全其见,建议选择RSA等非对称算法,避免密钥的泄露。由于非对称加密算法的效率较低,工程中可以结合实际只对类的头部结构信息加密,可以大大提高运行效率。
作者简介:王文学(1976-),男,河南人,讲师,研究方向:网络安全。
参考文献:
[1]《Java虚拟機规范》 Tim Lindholm 机械工业出版社