【摘 要】Java 編譯的class文件很容易被反編譯,不利于保護(hù)一些核心的代碼。文章分析了class文件的幾種加密方案的優(yōu)缺點(diǎn),并提出了安全可靠的class文件加密方案。
【關(guān)鍵詞】Java;Classloader;JavaAgent
Java 的class 類文件是Java源代碼文件經(jīng)過編譯后生成的字節(jié)碼文件,類文件有固定的結(jié)構(gòu),比如前四個字節(jié)固定為0xCAFEBABE,接下來四個字節(jié)是副版本號和主版本號等等。源代碼中的變量、常量等一切全在結(jié)構(gòu)中,了解的類文件的固定結(jié)構(gòu),就能輕易地根據(jù)class文件生成java源文件。這對項目某個核心的模塊來說,會造成關(guān)鍵技術(shù)的泄漏。尤其是涉及到版權(quán)的license管理,如何對class類進(jìn)行加密,是公司必須要考慮的問題。針對這個問題,有各種解決方案,有class混淆方案,也有class加密方案。class加密也有不同的加密技巧和策略。
class混淆器就是在不影響代碼執(zhí)行的條件下,對發(fā)布出去的程序進(jìn)行重新組織和處理,使得處理后的代碼與處理前代碼完成相同的功能?;煜椒ㄖ饕型庥^混淆、控制混淆、預(yù)防混淆、數(shù)據(jù)混淆。混淆后的代碼很難被反編譯,但不是不能被反編譯,反編譯后的程序雖然可讀性較差,但整理后程序的程序代碼還是可用的,程序邏輯可以修改。如果重寫這個類和方法,就可以直接跳過某個校驗過程以達(dá)到某種目的,比如跳過license的驗證或者直接返回許可信息。由于java的ClassLoader可以直接加載這個類,所以修改某個類并直接運(yùn)行不成問題。安全的方案就是把class直接加密,但加密后的class并不能被ClassLoader直接識別并運(yùn)行。
由于class文件有固定的結(jié)構(gòu),ClassLoader將class文件加載到JVM虛擬機(jī),程序就可以運(yùn)行了。加密后class文件,原來的ClassLoader無法正常解析,要繼承并重寫自己的ClassLoader類和defineClass()方法,defineClass()能將class二進(jìn)制內(nèi)容轉(zhuǎn)換成class對象,類的解密工作也可以在這里進(jìn)行,也就是在把類字節(jié)轉(zhuǎn)交給JVM運(yùn)行之前,解密class文件為正常的字節(jié)碼文件。當(dāng)然,這種解密的方法直接暴露在自定義的ClassLoader類中,可以反編譯ClassLoader類來得到解密方法并得到正常的class字節(jié)碼,把這些字節(jié)碼寫入到文件,再反編譯就能得到Java源文件,所以這種方法并不安全。
對于WEB項目,一般來說WEB容器有自己的加載器,比如TOMCAT的catalina.jar,可以修改兩個文件,org.apache.catalina.loader.WebappClassLoaderBase.class文件中的findResourceInternal方法和 org.apache.catalina.startup.ContextConfig.class文件中的processAnnotationsJar方法、processAnnotationsJndi方法、processAnnotationsFile方法,在調(diào)用processAnnotationsStream方法之前解密類文件,把解密后的字節(jié)流傳給processAnnotationsStream。這種加密方法比較靈活,可以根據(jù)情況只加密解密部分核心的類文件以提高運(yùn)行效率。當(dāng)然,這種方法要和具體的TOMCAT版本相結(jié)合,如果要使用其它的WEB容器或者不同版本的TOMCAT,都需要修改相應(yīng)版本的catalina.jar文件,不方便管理。從安全性的角度來看,此種方法和自定義ClassLoader差不多,如果catalina.jar文件和WebappClassLoaderBase.class、ContextConfig.class得不到有效的保護(hù),可以通過修改這兩個文件在得到解密后的class文件后再反編譯得到源文件。
前面兩種方法是用Java來實現(xiàn)解密class文件,本身解密的class能被反編譯,這其實是不安全的,當(dāng)然,你可以嵌套幾次加密和解密的過程,但這樣操作太過復(fù)雜。使用JVMTI編程接口,用C/C++來實現(xiàn)解密過程是個不錯的選擇。JVMTI是JVM Tool Interface,它提供了本地編程接口,利用JDK中JVM的某些類似鉤子機(jī)制和事件監(jiān)聽機(jī)制,監(jiān)聽加載class事件。通過啟動java程序時加參數(shù)-agentlib來指定lib庫,例如:java -agentlib:libClassLoader,其中l(wèi)ibClassLoader是用C/C++編寫,可以是Windows下的DLL庫,也可以是Linux下的so文件,在libClassLoader中實現(xiàn)對加密class的解密工作。由于C/C++編譯后的庫文件被反編譯基本上是匯編語言,要理解其中的解密邏輯幾乎無望。此外,lib還能被加殼,進(jìn)一步增加了反編譯的難度。程序只要在JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)方法中設(shè)置ClassFileLoadHook回調(diào)方法,并實現(xiàn)自己的ClassFileLoadHook方法即可。實踐證明,這是一個行之有效的運(yùn)行class解密方案。
此外,加密方法的選擇直接影響了java項目的運(yùn)行效率。Class加密可以選擇對稱加密,也可以選擇非對稱的加密算法,如RSA,class文件可以完整加密,也可以部分加密。安全其見,建議選擇RSA等非對稱算法,避免密鑰的泄露。由于非對稱加密算法的效率較低,工程中可以結(jié)合實際只對類的頭部結(jié)構(gòu)信息加密,可以大大提高運(yùn)行效率。
作者簡介:王文學(xué)(1976-),男,河南人,講師,研究方向:網(wǎng)絡(luò)安全。
參考文獻(xiàn):
[1]《Java虛擬機(jī)規(guī)范》 Tim Lindholm 機(jī)械工業(yè)出版社