源程序加密解决方案

1. 概述:
Java源程序的加密,有如下两种:
1使用混淆器对源码进行混淆,降低反编译工具的作用
2基于classloader的自定义加密、解密运行

1.1. 混淆器加密
1.2. 自定义classloader加密
1.2.1. 原理
原理:java虚拟机的动态加载机制,为classloader加密方案提供了理论基础。在jvm装载运行程序,初始的时候,只装在了必要的类,如java.lang.String等,而应用程序的类并没有一次性装入内存。Jvm解释执行应用程序的过程中,如果发现有未装载的类,则会调用装载正在执行的那个类的classloader来装载,这个过程是一层一层向上,直到顶层的classloader。Jvm启动的时候会装入ExtClassloader,而ExtClassloader又会装载AppClassloader,例如:

Java代码  
  1. Class Hello{
  2. Public static void main(String[] args){
  3. System.out.println(“hello”);
  4. HelloMethod.sayHello();
  5. }
  6. }
  7. Class HelloMethod{
  8. Public static void sayHello(){
  9. System.out.println(“hello in static HelloMethod”);
  10. }
  11. }

Class Hello{Public static void main(String[] args){System.out.println(“hello”);HelloMethod.sayHello();
}
}
Class HelloMethod{Public static void sayHello(){System.out.println(“hello in static HelloMethod”);}
}

有上面两个类的定义,在执行Hello类的main方法的时候,首先会委托装载Hello类的classloader来装载HelloMethod类,即jvm会委托AppClassloader来装载,但是在AppClassloader的实现的时候,会首先委托装载AppClassloader的classloader来装载,如果上层的classloader无法装载,才会由AppClassloader来装载HelloMethod类。这种模式叫做双亲委托模式。在jvm的所有classloader中都是如此,首先由父classloader加载,失败由自身加载。

Java虚拟机的这种特性,使得我们可以自定义一个classloader,然后由这个classloader来装载应用程序的启动类,然后在启动应用程序,那么当应用程序中有未装载的类的时候,java徐机器逐层向上请求classloader装载新类,那么首先被请求的就是装在应用程序的classloader,即我们自定义的classloader,我们完全可以首先调用自己的加载方法来加载类,如果加载不成功,可以请求父classloader来加载,因为来请求加载的类是完全有可能是系统的类。

在我们使用自定义的classloader的时候,装载自己的程序,那么就可以对装入的字节码进行一定的操作,比如解密。在调用自定义的装载器classloader的时候,首先是要装入被加密之后的文件,通常情况下仍旧已.class为扩展名,在调用defineClass之前对装入的数据解密。
1.2.2. Classloader的两个重要方法
protected Class defineClass(String name, byte[] classData, int offset, int length);
最原子的操作,在调用自定义的classloader加载新类的时候,首先根据自定义规则找到加载的类所存放的位置,然后将数据一byte[]类型读入,进行解密运算时候,调用该方法,以生成一个Class。这是一个比较核心的方法,这个方法是被抽象的Classloader定义为protected访问标记的,只有继承了Classloader这个类才能使用。

Class loadClass(String name, boolean resolve);
Java虚拟机,在装载新类,递归向上查找并调用的方法,在自定义classloader中需要重写,就是判断是否能够自己装载,如果能则自己装载,否则交由系统装载。
2. 源程序加密解决方案

2.1. 自定义classloader加密
加密和解密要是对应的,即使用加密之后的数据,经过解密是需要能够得到原来的数据。

2.1.1. 加密应用程序
为了简单,在这里才用一种简单的加密方法,把得到的需要加密的数据,以字节取,每一个字节加1,对应的解密就是每一个减1。
还是以Hello、HelloMethod类为例子,

Java代码  
  1. BufferedInputStream bis = new BufferedInputStream(new FileInputSteam(“d:/workbench/ciphertool/bin/com/aatest/Hello.class”));
  2. byte[] data = new byte[bis.avialable()];
  3. bis.read(data);
  4. bis.close();
  5. for(int I = 0; I < data.length; i++){
  6. data[i] =(byte)( data[i] + 1);
  7. }
  8. BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(“d:/workbench/ciphertool/bin/com/aatest/Hello.class”));
  9. bos.write(data);
  10. bos.close();

BufferedInputStream bis = new BufferedInputStream(new FileInputSteam(“d:/workbench/ciphertool/bin/com/aatest/Hello.class”));
byte[] data = new byte[bis.avialable()];
bis.read(data);
bis.close();
for(int I = 0; I < data.length; i++){data[i] =(byte)( data[i] + 1);
}
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(“d:/workbench/ciphertool/bin/com/aatest/Hello.class”));
bos.write(data);
bos.close();

将加密对象取出,加密,然后存盘。
2.1.2. 解密运行应用程序
在自定义的classloader接收到加载新类请求的时候,首先读入加密之后的文件,然后解密,最后调用defineClass(name, classData, offset, length)生成类,返回出去。

拦截新类加载请求

Java代码  
  1. package com.cjnetwork.ciphertool.core;
  2. import java.io.BufferedInputStream;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.lang.reflect.Method;
  6. import java.util.ArrayList;
  7. import java.util.HashMap;
  8. import java.util.List;
  9. import java.util.Map;
  10. import java.util.jar.JarEntry;
  11. import java.util.jar.JarFile;
  12. import com.cjnetwork.ciphertool.util.CipherUtil;
  13. public class CjClassloader extends ClassLoader {
  14. String classpath;
  15. Map<String, Class> loadedClassPool = new HashMap<String, Class>();
  16. public CjClassloader(String classpath) {
  17. this.classpath = classpath;
  18. }
  19. @SuppressWarnings("unchecked")
  20. @Override
  21. public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
  22. Class claz = null;
  23. if (loadedClassPool.containsKey(name)) {
  24. claz = this.loadedClassPool.get(name);
  25. } else {
  26. try {
  27. if (claz == null) {
  28. claz = super.loadClass(name, false);
  29. if (claz != null) {
  30. System.out.println("系统加载成功:" + name);
  31. }
  32. }
  33. } catch (ClassNotFoundException e) {
  34. System.out.println("系统无法加载:" + name);
  35. }
  36. try {
  37. if (claz == null) {
  38. claz = loadByCjClassLoader(name);
  39. if (claz != null) {
  40. System.out.println("自定义加载成功:" + name);
  41. }
  42. }
  43. } catch (Exception e) {
  44. System.out.println("自定义无法加载:" + name);
  45. }
  46. if (claz != null) {
  47. this.loadedClassPool.put(name, claz);
  48. }
  49. }
  50. if (resolve) {
  51. resolveClass(claz);
  52. }
  53. return claz;
  54. }
  55. /**
  56. *
  57. * 解密加载
  58. *
  59. *
  60. * @param name
  61. * @return
  62. */
  63. @SuppressWarnings("unchecked")
  64. private Class loadByCjClassLoader(String name) {
  65. Class claz = null;
  66. try {
  67. byte[] rawData = loadClassData(name);
  68. if (rawData != null) {
  69. byte[] classData = decrypt(getReverseCypher(this.cjcipher.getKeycode()), rawData);
  70. classData = CipherUtil.filter(classData, this.cjcipher);
  71. claz = defineClass(name, classData, 0, classData.length);
  72. }
  73. } catch (Exception e) {
  74. claz = null;
  75. }
  76. return claz;
  77. }
  78. }

package com.cjnetwork.ciphertool.core;import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;import com.cjnetwork.ciphertool.util.CipherUtil;public class CjClassloader extends ClassLoader {String classpath;Map<String, Class> loadedClassPool = new HashMap<String, Class>();public CjClassloader(String classpath) {this.classpath = classpath;}@SuppressWarnings("unchecked")@Overridepublic synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {Class claz = null;if (loadedClassPool.containsKey(name)) {claz = this.loadedClassPool.get(name);} else {try {if (claz == null) {claz = super.loadClass(name, false);if (claz != null) {System.out.println("系统加载成功:" + name);}}} catch (ClassNotFoundException e) {System.out.println("系统无法加载:" + name);}try {if (claz == null) {claz = loadByCjClassLoader(name);if (claz != null) {System.out.println("自定义加载成功:" + name);}}} catch (Exception e) {System.out.println("自定义无法加载:" + name);}if (claz != null) {this.loadedClassPool.put(name, claz);}}if (resolve) {resolveClass(claz);}return claz;}/*** * 解密加载* * * @param name* @return*/@SuppressWarnings("unchecked")private Class loadByCjClassLoader(String name) {Class claz = null;try {byte[] rawData = loadClassData(name);if (rawData != null) {byte[] classData = decrypt(getReverseCypher(this.cjcipher.getKeycode()), rawData);classData = CipherUtil.filter(classData, this.cjcipher);claz = defineClass(name, classData, 0, classData.length);}} catch (Exception e) {claz = null;}return claz;}}

最主要的是集成Classloader,并重写Class loadClass(String name, Boolean resolve)方法,在这个方法中,可以根据需要自己加载需要的文件,并解析生成Class。

解密并返回Class

Java代码  
  1. BufferedInputStream bis = new BufferedInputStream(new FileInputSteam(“d:/workbench/ciphertool/bin/com/aatest/Hello.class”));
  2. byte[] data = new byte[bis.avialable()];
  3. bis.read(data);
  4. bis.close();
  5. for(int I = 0; I < data.length; i++){
  6. data[i] =(byte)( data[i] - 1);
  7. }
  8. Class claz = defineClass(“Hello”, data, 0, data.length);

BufferedInputStream bis = new BufferedInputStream(new FileInputSteam(“d:/workbench/ciphertool/bin/com/aatest/Hello.class”));
byte[] data = new byte[bis.avialable()];
bis.read(data);
bis.close();
for(int I = 0; I < data.length; i++){data[i] =(byte)( data[i] - 1);
}
Class claz = defineClass(“Hello”, data, 0, data.length);

2.2. 加密自定义classloader
采用以上的方法,就可以将应用程序加密,使得被加密的程序不能被反编译,因为加密之后的class文件已经不是jvm定义的标准class文件,只能通过解密运行程序解密,才能运行。

如果只做到这一步,对于java源程序加密还没有完成。虽然应用程序无法直接反编译,但是自定义的classloader是没有被加密的,它自身是可以被反编译的。理论上,如果得到真正的class文件(即jvm标准的class文件),是可以反编译的java文件,在这里,假设得到class文件就得到了java文件。
如果***者将自定义的classloader反编译,得到源码,则***者可以再自定义解密运行的同事,将得到的应用程序的字节码存储到本地,那么,***者就相当于是跳过了源程序加密解密。例如***者在代码Class claz = defineClass(name, classData, offset, length);这句代码前,将classData存储到本地,即***者可以再解密运行应用程序的同时,将应用程序的字节码保存,就达到了破解应用程序源代码的效果。

为了描述方便,实例化一个 自定义的classloader,叫做CjClassLoader
这一个漏洞在于,CjClassLoader没有加密,***者可以在其中嵌入导出应用程序代码,那么,要解决这个问题,加密CjClassLoader就成了保护应用程序源代码的关键。
试想,如果加密、解密运行程序中,没有CjClassLoader.class文件,或是CjClassLoader.class文件本身也是经过加密的,CjClassLoader类的获得也是通过自己书写的方法动态获取,那么***者无法获取到CjClassLoader.class文件,相当于无法获取到CjClassLoader.java文件,那么也就无法再其中加入到处应用程序类文件的代码,那么被加密的应用程序可以认为是安全的。
假设将CjClassLoader.class加密后生成CjClassLoaderEncryptor0.class,那么CjClassLoader是安全了,但理论上***者还是可以通过反编译CjClassLoaderEncryptor0来获取CjClassLoader的源码,那么保护CjClassLoaderEncryptor0又成了保护应用程序的关键,注意在CjClassLoaderEncryptor0中存在解密CjClassLoader的密钥,即将密钥硬编码到CjClassLoaderEncryptor0中,这样做是为了防止***者直接获取密钥,直接破解最里面一层的加密,至于什么是最里面一层,请继续看后文。
那么如何CjClassLoaderEncryptor0.class的安全性呢,我们同样采取加密的方式,即将CjClassLoaderEncryptor0.class加密,生成CjClassLoaderEncryptor1.class,在解密运行的时候首先动态的生成CjClassLoaderEncryptor1.class,在由CjClassLoaderEncryptor1所定义的类动态的装入CjClassLoaderEncryptor0.class,并且解密生成CjClassLoader,最后使用CjClassLoader装入应用程序,运行。整体上的思路如下:

Java代码  
  1. CjClassLoader.class ——》 CjClassLoaderEncryptor0.class
  2. CjClassLoaderEncryptor1.class   ——》 CjClassLoaderEncryptor1.class
  3. CjClassLoaderEncryptor2.class   ——》 CjClassLoaderEncryptor2.class
  4. CjClassLoaderEncryptor3.class   ——》 CjClassLoaderEncryptor3.class
  5. 。。。。。。
  6. CjClassLoaderEncryptorN.class   ——》 CjClassLoaderEncryptorN.class

CjClassLoader.class ——》 CjClassLoaderEncryptor0.class
CjClassLoaderEncryptor1.class   ——》 CjClassLoaderEncryptor1.class
CjClassLoaderEncryptor2.class   ——》 CjClassLoaderEncryptor2.class
CjClassLoaderEncryptor3.class   ——》 CjClassLoaderEncryptor3.class
。。。。。。
CjClassLoaderEncryptorN.class   ——》 CjClassLoaderEncryptorN.class

这样的一级一级加密,我们称CjClassLoaderEncryptorN.class为最外层,成CjClassLoaderEncryptor0.class为最里层。除去最外层没有加密,里面的每一层都是加密之后的数据,都是不能直接为jvm所识别的字节码,都是需要通过后一级的解密程序解密之后才能为jvm所识别。

系统装在CjClassLoaderEncryptorN.class,生成CjClassLoaderEncryptorN类,使用反射机制,调用CjClassLoaderEncryptorN类中的方法,这个方法可以动态的装入CjClassLoaderEncryptor(N-1).class,并利用CjClassLoaderEncryptorN中的密钥,解密CjClassLoaderEncryptor(N – 1),然后生成CjClassLoaderEncryptor( N – 1)类,最后调用CjClassLoaderEncryptor(N – 1)中的方法。而CjClassLoaderEncryptor( N – 1)类中的方法,可以动态装入CjClassLoaderEncryptor(N- 2).class文件,并利用CjClassLoaderEncryptor(N – 1)中的密钥,解密CjClassLoaderEncryptor(N – 2),然后生成CjClassLoaderEncryptor(N – 2)类,最后调用方法,被调用的方法可以动态的装入CjClassLoaderEncryptor(N – 3).class。。。。。。。。

Java代码  
  1. CjClassLoaderEncryptorN  (密钥N,动态装入,解密,方法调用)  CjClassLoaderEncryptor(N–1)
  2. CjClassLoaderEncryptor(N-1) (密钥N-1,动态装入,解密,方法调用) CjClassLoaderEncryptor(N–2)
  3. CjClassLoaderEncryptor(N-2) (密钥N-2,动态装入,解密,方法调用) CjClassLoaderEncryptor(N-3)
  4. ......
  5. CjClassLoaderEncryptor1  (密钥1,动态装入,解密,方法调用)  CjClassLoaderEncryptor0
  6. CjClassLoaderEncryptor0  (密钥0,动态装入,解密,方法调用)  CjClassLoader

CjClassLoaderEncryptorN  (密钥N,动态装入,解密,方法调用)  CjClassLoaderEncryptor(N–1)
CjClassLoaderEncryptor(N-1) (密钥N-1,动态装入,解密,方法调用) CjClassLoaderEncryptor(N–2)
CjClassLoaderEncryptor(N-2) (密钥N-2,动态装入,解密,方法调用) CjClassLoaderEncryptor(N-3)
......
CjClassLoaderEncryptor1  (密钥1,动态装入,解密,方法调用)  CjClassLoaderEncryptor0
CjClassLoaderEncryptor0  (密钥0,动态装入,解密,方法调用)  CjClassLoader

最后使用CjClassLoader解密装载应用程序。

通过这样一个过程的加密CjClassLoader,可以达到保护加密程序本身的目的,这种保护在理论上是可破,但在实际操作中将会变得困难,因为密钥是通过硬编码的方式存储在下一层的封装器中,即CjClassLoaderEncryptor(N-1).class的密钥是放在CjClassLoaderEncryptorN.class中,如果存在CjClassLoaderEncryptor1000.class,那么加密过程将会变得非常复杂。

当然动态生成CjClassLoaderEncryptorN.class的工作,虽然内置了应编码(解密CjClassLoaderEncryptor(N-1)的密钥),但是这样一个过程,是不需要手动实现,利用程序自动生成即可。目前,这个版本的实现中是采用了动态生成CjClassLoaderEncryptorN.java文件,然后调用javac 命令,编译生成class文件。

请记住,这个过程是理论上不安全的,但如果需要加密的应用程序非常的重要,那么可以将加密、解密运行自身的CjClassLoader加密次数增加,以达到更加安全的目的。
2.3. 隐藏自定义classloader
通过上述加密CjClassLoader的方案,可以使得CjClassLoader变得相对安全,但似乎还是有一个问题,即解密运行程序本身的main方法中,会动态的装入CjClassLoaderEncryptorN,然后通过层层调用,最终获取到CjClassLoader类,然后使用CjClassLoader解密装载应用程序,这段代码是没有加密的,***者可以不考虑CjClassLoaderEncryptorN开始的层层调用,只需要在最终获取的CjClassLoader解密应用程序之前,将CjClassLoader本地化,即可以获得未经加密的CjClassLoader,这样,就不安全了。
解决这个问题,可以将这段代码中动态获取CjClassLoader类,修改为动态获取CjClassLoader中的Class loadClass(String name, Boolean resovle)方法,然后直接使用获取到的方法,开始加载应用程序。

如此,***者就没有办法直接获取到解密之后的CjClassLoader,保护了加密、解密程序。

2.4. 隐藏加密、解密方法
在上述的实现中,CjClassLoader中加密、解密应用程序的方法是被放置于CipherUtil.class文件中,而这个文件是没有被加密的,***者是可以直接获取到应用程序加密和解密的方法的,这给应用程序带来了不安全性,是的***者不利用解密程序的繁琐解密过程,而自定调用CipherUtil.class中的方法,解密应用程序。

解决这个问题,可以将CipherUtil.class中的加密和解密方法封装到CjClassloader中,因为CjClassloader是没有办法直接得到,所以认为加密解密所用到的方法是安全的。最终在程序中调用的时候不是直接得到CjClassloader类,都是通过CjClassloaderEncrytorN的层层方法调用,而直接获取到需要使用的方法。例如,我们可以在CjClassloaderEncrytorN类中封装了一个Method getEncrytMethod(),如此的方法,这个方法会去调用CjClassloaderEncrytor(N-1)中的同名方法,如此一直调用,直到CjClassloaderEncrytor0.class中,在这个类中直接反射获得CjClassloader中的加密方法,当然这个是比较特殊的,因为在CjClassloaderEncrytor0中时候反射获取CjClassloader中方法的时候,这个反射是需要带参数的,但这个带参数获取也是简单的。

3. bug

异常堆栈过长
经过这种一层一层的CjClassLoader解密运行的源程序,其堆栈是很长的,如果应用程序中,出现异常,答应异常或日志记录将会变得很麻烦,会记录很多无用的堆栈信息。

备注:文中提到的应用程序,指需要被加密的程序。

转载于:https://blog.51cto.com/guoguoguo/705614

java源程序加密解决方案(基于Classloader解密)相关推荐

  1. c rsa java私钥_RSA,JAVA私钥加密,C#公钥解密

    做这个东西在坑里爬了3天才爬出来,记录下供园友参考.C#程序员一枚,项目需要和Java做数据交互,对方甩了段密文和一个CER证书给我,然后我要对其密文进行解密. RSA 非对称加密,对方用私钥加密,我 ...

  2. java AES加密 前端CryptoJS AES解密

    问题 java加密生成base64位字符串,前端使用Java提供的密钥,前端无法解密出数据. java后台加密使用AES/ECB/PKCS5Padding 数据准备: 一个固定秘钥,互相约定 B7E1 ...

  3. java pgp加密_基于Java Bouncy Castle的PGP加密解密示例

    # re: 基于Java Bouncy Castle的PGP加密解密示例  回复  更多评论 2016-03-02 10:32 by 毛小龙 对文件进行加密 在测试类里面已经跑通了 抽取出来调用就报这 ...

  4. java ecc 加密_基于java实现的ECC加密算法示例

    本文实例讲述了基于java实现的ECC加密算法.分享给大家供大家参考,具体如下: ECC ECC-Elliptic Curves Cryptography,椭圆曲线密码编码学,是目前已知的公钥体制中, ...

  5. JAVA SHA-1加密及DES加解密

    1.SHA-1加密 SHA-1标准加密: //SHA-1public static String getSha1(String str){if(str == null || str.length() ...

  6. java 基于类路径搜索_一种基于ClassLoader的自定义类查找方法与流程

    本发明涉及IT技术领域,特别是指一种基于ClassLoader的自定义类查找方法. 背景技术: 随着信息系统的复杂性日益增大,使用反射机制设计的系统越来越多.根据不同的业务需要通过反射去获得相应的处理 ...

  7. Java常用加密解密算法全解

    数据编码.数字签名.信息加密 是前后端开发都经常需要使用到的技术,应用场景包括了用户登入.交易.信息通讯.OAuth 等等,不同的应用场景也会需要使用到不同的签名加密算法,或者需要搭配不一样的 签名加 ...

  8. java dsa加密与解密_Java DSA 加密 | 解密

    Java DSA 加密 | 解密 什么是DSA ? DSA (Digital Signature Algorithm) 是 Schnorr 和 ElGamal 签名算法的变种,被美国 NIST 作为 ...

  9. Java各种加密和解密方式

    加密算法 文章1:https://www.cnblogs.com/xibushijie/p/12851771.html 文章2:https://blog.csdn.net/weixin_3706959 ...

最新文章

  1. 多核时代 .NET Framework 4 中的并行编程6---并行LINQ
  2. Python基础数据类型之字符串(一)
  3. (建议收藏)万字长文,帮你一招搞定产品经理面试-详解产品经理面试大全
  4. 软件Release版本异常捕获程序(BugReport)
  5. mysql 指示符安装,Mysql的安装
  6. poj 3207 2-sat
  7. OpenDiscussion_DataDrivenDesign
  8. datax实现mysql数据同步
  9. ACL访问控制列表【笔记|实验】
  10. Linux命令-文件处理命令:touch
  11. 【EDAS问题】轻量级EDAS部署hsf服务出现找不到类的解决方案
  12. 运算放大器权威指南_运算放大器科普文章
  13. 扁平化管理:硅谷高效工作法
  14. python调用短信api接口实现验证码发送
  15. 基于中移物联网MQTT协议的ESP8266远程开机实例
  16. 单片机中的数据存储器ram
  17. mysql运维备份_MySQL运维经验
  18. 1218: 青蛙(三)
  19. 个人Linux学习笔记操作大全
  20. Linux系统常用基本命令总结

热门文章

  1. debian上安装docker ce
  2. JAVA程序通过JNI调用C/C++库
  3. 当我真正开始爱自己——查理·卓别林
  4. oracle数据库表的导入导出cmd命令大全
  5. 移动app测试流程与测试点
  6. 快速附加没有日志文件的 SQL Server 数据库文件!
  7. 快速了解 MySQL 的性能优化
  8. Python3 透明网桥算法
  9. C# 页面调用控制台应用程序
  10. C++程序设计方法3:强制类型转换