源程序加密解決方案 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代碼 ?
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”);?? ?? }?? }??
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代碼 ?
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();??
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代碼 ?
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" )?? ????@Override ?? ????public ?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;?? ????}?? ?? ????? ? ? ? ? ? ? ?? ????@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;?? ????}?? ?? }??
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代碼 ?
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);??
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代碼 ?
CjClassLoader.class ?——》?CjClassLoaderEncryptor0.class ?? CjClassLoaderEncryptor1.class ???——》?CjClassLoaderEncryptor1.class ?? CjClassLoaderEncryptor2.class ???——》?CjClassLoaderEncryptor2.class ?? CjClassLoaderEncryptor3.class ???——》?CjClassLoaderEncryptor3.class ?? 。。。。。。?? 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代碼 ?
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??
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解密運行的源程序,其堆棧是很長的,如果應用程序中,出現異常,答應異?;蛉罩居涗泴兊煤苈闊?#xff0c;會記錄很多無用的堆棧信息。 備注:文中提到的應用程序,指需要被加密的程序。
轉載于:https://blog.51cto.com/guoguoguo/705614
總結
以上是生活随笔 為你收集整理的java源程序加密解决方案(基于Classloader解密) 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。