了解ClassLoader
1, 什么是 ClassLoader?
Java 程序并不是一個可執(zhí)行文件,是需要的時候,才把裝載到 JVM中。ClassLoader 做的工作就是 JVM 中將類裝入內(nèi)存。 而且,Java ClassLoader 就是用 Java 語言編寫的。這意味著您可以創(chuàng)建自己的 ClassLoader
ClassLoader 的基本目標是對類的請求提供服務(wù)。當 JVM 需要使用類時,它根據(jù)名稱向 ClassLoader 請求這個類,然后 ClassLoader 試圖返回一個表示這個類的 Class 對象。 通過覆蓋對應(yīng)于這個過程不同階段的方法,可以創(chuàng)建定制的 ClassLoader。
2, 一些重要的方法
A) 方法 loadClass
ClassLoader.loadClass() 是 ClassLoader 的入口點。該方法的定義如下:
Class loadClass( String name, boolean resolve );
name JVM 需要的類的名稱,如 Foo 或 java.lang.Object。
resolve 參數(shù)告訴方法是否需要解析類。在準備執(zhí)行類之前,應(yīng)考慮類解析。并不總是需要解析。如果 JVM 只需要知道該類是否存在或找出該類的超類,那么就不需要解析。
方法 defineClass
defineClass 方法是 ClassLoader 的主要訣竅。該方法接受由原始字節(jié)組成的數(shù)組并把它轉(zhuǎn)換成 Class 對象。原始數(shù)組包含如從文件系統(tǒng)或網(wǎng)絡(luò)裝入的數(shù)據(jù)。defineClass 管理 JVM 的許多復(fù)雜、神秘和倚賴于實現(xiàn)的方面 -- 它把字節(jié)碼分析成運行時數(shù)據(jù)結(jié)構(gòu)、校驗有效性等等。不必擔(dān)心,您無需親自編寫它。事實上,即使您想要這么做也不能覆蓋它,因為它已被標記成final的。
C) 方法 findSystemClass
findSystemClass 方法從本地文件系統(tǒng)裝入文件。它在本地文件系統(tǒng)中尋找類文件,如果存在,就使用 defineClass 將原始字節(jié)轉(zhuǎn)換成 Class 對象,以將該文件轉(zhuǎn)換成類。當運行 Java 應(yīng)用程序時,這是 JVM 正常裝入類的缺省機制。(Java 2 中 ClassLoader 的變動提供了關(guān)于 Java 版本 1.2 這個過程變動的詳細信息。) 對于定制的 ClassLoader,只有在嘗試其它方法裝入類之后,再使用 findSystemClass。原因很簡單:ClassLoader 是負責(zé)執(zhí)行裝入類的特殊步驟,不是負責(zé)所有類。例如,即使 ClassLoader 從遠程的 Web 站點裝入了某些類,仍然需要在本地機器上裝入大量的基本 Java 庫。而這些類不是我們所關(guān)心的,所以要 JVM 以缺省方式裝入它們:從本地文件系統(tǒng)。這就是 findSystemClass 的用途。
D) 方法 resolveClass
正如前面所提到的,可以不完全地(不帶解析)裝入類,也可以完全地(帶解析)裝入類。當編寫我們自己的 loadClass 時,可以調(diào)用 resolveClass,這取決于 loadClass 的 resolve 參數(shù)的值。
E) 方法 findLoadedClass
findLoadedClass 充當一個緩存:當請求 loadClass 裝入類時,它調(diào)用該方法來查看 ClassLoader 是否已裝入這個類,這樣可以避免重新裝入已存在類所造成的麻煩。應(yīng)首先調(diào)用該方法。
3, 怎么組裝這些方法
1) 調(diào)用 findLoadedClass 來查看是否存在已裝入的類。
2) 如果沒有,那么采用那種特殊的神奇方式來獲取原始字節(jié)。
3) 如果已有原始字節(jié),調(diào)用 defineClass 將它們轉(zhuǎn)換成 Class 對象。
4) 如果沒有原始字節(jié),然后調(diào)用 findSystemClass 查看是否從本地文件系統(tǒng)獲取類。
5) 如果 resolve 參數(shù)是 true,那么調(diào)用 resolveClass 解析 Class 對象。
6) 如果還沒有類,返回 ClassNotFoundException。
4,Java 2 中 ClassLoader 的變動
1)loadClass 的缺省實現(xiàn)
定制編寫的 loadClass 方法一般嘗試幾種方式來裝入所請求的類,如果您編寫許多類,會發(fā)現(xiàn)一次次地在相同的、很復(fù)雜的方法上編寫變量。 在 Java 1.2 中 loadClass 的實現(xiàn)嵌入了大多數(shù)查找類的一般方法,并使您通過覆蓋 findClass 方法來定制它,在適當?shù)臅r候 findClass 會調(diào)用 loadClass。 這種方式的好處是您可能不一定要覆蓋 loadClass;只要覆蓋 findClass 就行了,這減少了工作量。
2)新方法:findClass
loadClass 的缺省實現(xiàn)調(diào)用這個新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代碼,而無需要復(fù)制其它代碼(例如,當專門的方法失敗時,調(diào)用系統(tǒng) ClassLoader)。
3) 新方法:getSystemClassLoader
如果覆蓋 findClass 或 loadClass,getSystemClassLoader 使您能以實際 ClassLoader 對象來訪問系統(tǒng) ClassLoader(而不是固定的從 findSystemClass 調(diào)用它)。
4) 新方法:getParent
為了將類請求委托給父代 ClassLoader,這個新方法允許 ClassLoader 獲取它的父代 ClassLoader。當使用特殊方法,定制的 ClassLoader 不能找到類時,可以使用這種方法。
父代 ClassLoader 被定義成創(chuàng)建該 ClassLoader 所包含代碼的對象的 ClassLoader。
運用加密技術(shù)保護Java源代碼
一、為什么要加密?
對 于傳統(tǒng)的C或C++之類的語言來說,要在Web上保護源代碼是很容易的,只要不發(fā)布它就可以。遺憾的是,Java程序的源代碼很容易被別人偷看。只要有一 個反編譯器,任何人都可以分析別人的代碼。Java的靈活性使得源代碼很容易被竊取,但與此同時,它也使通過加密保護代碼變得相對容易,我們唯一需要了解 的就是Java的ClassLoader對象。當然,在加密過程中,有關(guān)Java Cryptography Extension(JCE)的知識也是必不可少的。
有幾種技術(shù)可以“模糊”Java類文件,使得反編譯器處理類文件的效果大打折扣。然而,修改反編譯器使之能夠處理這些經(jīng)過模糊處理的類文件并不是什么難事,所以不能簡單地依賴模糊技術(shù)來保證源代碼的安全。
我們可以用流行的加密工具加密應(yīng)用,比如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard)。這時,最終用戶在運行應(yīng)用之前必須先進行解密。但解密之后,最終用戶就有了一份不加密的類文件,這和事先不進行加密沒有什么差別。
Java 運行時裝入字節(jié)碼的機制隱含地意味著可以對字節(jié)碼進行修改。JVM每次裝入類文件時都需要一個稱為ClassLoader的對象,這個對象負責(zé)把新的類裝 入正在運行的JVM。JVM給ClassLoader一個包含了待裝入類(比如java.lang.Object)名字的字符串,然后由 ClassLoader負責(zé)找到類文件,裝入原始數(shù)據(jù),并把它轉(zhuǎn)換成一個Class對象。
我們可以通過定制ClassLoader,在類文件執(zhí)行之前修改它。這種技術(shù)的應(yīng)用非常廣泛——在這里,它的用途是在類文件裝入之時進行解密,因此可以看成是一種即時解密器。由于解密后的字節(jié)碼文件永遠不會保存到文件系統(tǒng),所以竊密者很難得到解密后的代碼。
由于把原始字節(jié)碼轉(zhuǎn)換成Class對象的過程完全由系統(tǒng)負責(zé),所以創(chuàng)建定制ClassLoader對象其實并不困難,只需先獲得原始數(shù)據(jù),接著就可以進行包含解密在內(nèi)的任何轉(zhuǎn)換。
Java 2在一定程度上簡化了定制ClassLoader的構(gòu)建。在Java 2中,loadClass的缺省實現(xiàn)仍舊負責(zé)處理所有必需的步驟,但為了顧及各種定制的類裝入過程,它還調(diào)用一個新的findClass方法。
這為我們編寫定制的ClassLoader提供了一條捷徑,減少了麻煩:只需覆蓋findClass,而不是覆蓋loadClass。這種方法避免了重復(fù)所有裝入器必需執(zhí)行的公共步驟,因為這一切由loadClass負責(zé)。
不 過,本文的定制ClassLoader并不使用這種方法。原因很簡單。如果由默認的ClassLoader先尋找經(jīng)過加密的類文件,它可以找到;但由于類 文件已經(jīng)加密,所以它不會認可這個類文件,裝入過程將失敗。因此,我們必須自己實現(xiàn)loadClass,稍微增加了一些工作量。
二、定制類裝入器
每一個運行著的JVM已經(jīng)擁有一個ClassLoader。這個默認的ClassLoader根據(jù)CLASSPATH環(huán)境變量的值,在本地文件系統(tǒng)中尋找合適的字節(jié)碼文件。
應(yīng) 用定制ClassLoader要求對這個過程有較為深入的認識。我們首先必須創(chuàng)建一個定制ClassLoader類的實例,然后顯式地要求它裝入另外一個 類。這就強制JVM把該類以及所有它所需要的類關(guān)聯(lián)到定制的ClassLoader。Listing 1顯示了如何用定制ClassLoader裝入類文件。
【Listing 1:利用定制的ClassLoader裝入類文件】
// 首先創(chuàng)建一個ClassLoader對象
ClassLoader myClassLoader = new myClassLoader();
// 利用定制ClassLoader對象裝入類文件
// 并把它轉(zhuǎn)換成Class對象
Class myClass = myClassLoader.loadClass( "mypackage.MyClass" );
// 最后,創(chuàng)建該類的一個實例
Object newInstance = myClass.newInstance();
// 注意,MyClass所需要的所有其他類,都將通過
// 定制的ClassLoader自動裝入
如前所述,定制ClassLoader只需先獲取類文件的數(shù)據(jù),然后把字節(jié)碼傳遞給運行時系統(tǒng),由后者完成余下的任務(wù)。
ClassLoader 有幾個重要的方法。創(chuàng)建定制的ClassLoader時,我們只需覆蓋其中的一個,即loadClass,提供獲取原始類文件數(shù)據(jù)的代碼。這個方法有兩個 參數(shù):類的名字,以及一個表示JVM是否要求解析類名字的標記(即是否同時裝入有依賴關(guān)系的類)。如果這個標記是true,我們只需在返回JVM之前調(diào)用 resolveClass。
【Listing 2:ClassLoader.loadClass()的一個簡單實現(xiàn)】
public Class loadClass( String name, boolean resolve )
throws ClassNotFoundException {
try {
// 我們要創(chuàng)建的Class對象
Class clasz = null;
// 必需的步驟1:如果類已經(jīng)在系統(tǒng)緩沖之中,
// 我們不必再次裝入它
clasz = findLoadedClass( name );
if (clasz != null)
return clasz;
// 下面是定制部分
byte classData[] = /* 通過某種方法獲取字節(jié)碼數(shù)據(jù) */;
if (classData != null) {
// 成功讀取字節(jié)碼數(shù)據(jù),現(xiàn)在把它轉(zhuǎn)換成一個Class對象
clasz = defineClass( name, classData, 0, classData.length );
}
// 必需的步驟2:如果上面沒有成功,
// 我們嘗試用默認的ClassLoader裝入它
if (clasz == null)
clasz = findSystemClass( name );
// 必需的步驟3:如有必要,則裝入相關(guān)的類
if (resolve && clasz != null)
resolveClass( clasz );
// 把類返回給調(diào)用者
return clasz;
} catch( IOException ie ) {
throw new ClassNotFoundException( ie.toString() );
} catch( GeneralSecurityException gse ) {
throw new ClassNotFoundException( gse.toString() );
}
}
Listing 2顯示了一個簡單的loadClass實現(xiàn)。代碼中的大部分對所有ClassLoader對象來說都一樣,但有一小部分(已通過注釋標記)是特有的。在處理過程中,ClassLoader對象要用到其他幾個輔助方法:
findLoadedClass:用來進行檢查,以便確認被請求的類當前還不存在。loadClass方法應(yīng)該首先調(diào)用它。
defineClass:獲得原始類文件字節(jié)碼數(shù)據(jù)之后,調(diào)用defineClass把它轉(zhuǎn)換成一個Class對象。任何loadClass實現(xiàn)都必須調(diào)用這個方法。
findSystemClass:提供默認ClassLoader的支持。如果用來尋找類的定制方法不能找到指定的類(或者有意地不用定制方法),則可以調(diào)用該方法嘗試默認的裝入方式。這是很有用的,特別是從普通的JAR文件裝入標準Java類時。
resolveClass:當JVM想要裝入的不僅包括指定的類,而且還包括該類引用的所有其他類時,它會把loadClass的resolve參數(shù)設(shè)置成true。這時,我們必須在返回剛剛裝入的Class對象給調(diào)用者之前調(diào)用resolveClass。
三、加密、解密
Java加密擴展即Java Cryptography Extension,簡稱JCE。它是Sun的加密服務(wù)軟件,包含了加密和密匙生成功能。JCE是JCA(Java Cryptography Architecture)的一種擴展。
JCE 沒有規(guī)定具體的加密算法,但提供了一個框架,加密算法的具體實現(xiàn)可以作為服務(wù)提供者加入。除了JCE框架之外,JCE軟件包還包含了SunJCE服務(wù)提供 者,其中包括許多有用的加密算法,比如DES(Data Encryption Standard)和Blowfish。
為簡單計,在本文中我們將用DES算法加密和解密字節(jié)碼。下面是用JCE加密和解密數(shù)據(jù)必須遵循的基本步驟:
步驟1:生成一個安全密匙。在加密或解密任何數(shù)據(jù)之前需要有一個密匙。密匙是隨同被加密的應(yīng)用一起發(fā)布的一小段數(shù)據(jù),Listing 3顯示了如何生成一個密匙。 【Listing 3:生成一個密匙】
// DES算法要求有一個可信任的隨機數(shù)源
SecureRandom sr = new SecureRandom();
// 為我們選擇的DES算法生成一個KeyGenerator對象
KeyGenerator kg = KeyGenerator.getInstance( "DES" );
kg.init( sr );
// 生成密匙
SecretKey key = kg.generateKey();
// 獲取密匙數(shù)據(jù)
byte rawKeyData[] = key.getEncoded();
/* 接下來就可以用密匙進行加密或解密,或者把它保存
為文件供以后使用 */
doSomething( rawKeyData );
步驟2:加密數(shù)據(jù)。得到密匙之后,接下來就可以用它加密數(shù)據(jù)。除了解密的ClassLoader之外,一般還要有一個加密待發(fā)布應(yīng)用的獨立程序(見Listing 4)。 【Listing 4:用密匙加密原始數(shù)據(jù)】
// DES算法要求有一個可信任的隨機數(shù)源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某種方法獲得密匙數(shù)據(jù) */;
// 從原始密匙數(shù)據(jù)創(chuàng)建DESKeySpec對象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 創(chuàng)建一個密匙工廠,然后用它把DESKeySpec轉(zhuǎn)換成
// 一個SecretKey對象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher對象實際完成加密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher對象
cipher.init( Cipher.ENCRYPT_MODE, key, sr );
// 現(xiàn)在,獲取數(shù)據(jù)并加密
byte data[] = /* 用某種方法獲取數(shù)據(jù) */
// 正式執(zhí)行加密操作
byte encryptedData[] = cipher.doFinal( data );
// 進一步處理加密后的數(shù)據(jù)
doSomething( encryptedData );
步驟3:解密數(shù)據(jù)。運行經(jīng)過加密的應(yīng)用時,ClassLoader分析并解密類文件。操作步驟如Listing 5所示。 【Listing 5:用密匙解密數(shù)據(jù)】
// DES算法要求有一個可信任的隨機數(shù)源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某種方法獲取原始密匙數(shù)據(jù) */;
// 從原始密匙數(shù)據(jù)創(chuàng)建一個DESKeySpec對象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 創(chuàng)建一個密匙工廠,然后用它把DESKeySpec對象轉(zhuǎn)換成
// 一個SecretKey對象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher對象實際完成解密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher對象
cipher.init( Cipher.DECRYPT_MODE, key, sr );
// 現(xiàn)在,獲取數(shù)據(jù)并解密
byte encryptedData[] = /* 獲得經(jīng)過加密的數(shù)據(jù) */
// 正式執(zhí)行解密操作
byte decryptedData[] = cipher.doFinal( encryptedData );
// 進一步處理解密后的數(shù)據(jù)
doSomething( decryptedData );
四、應(yīng)用實例
前面介紹了如何加密和解密數(shù)據(jù)。要部署一個經(jīng)過加密的應(yīng)用,步驟如下:
步驟1:創(chuàng)建應(yīng)用。我們的例子包含一個App主類,兩個輔助類(分別稱為Foo和Bar)。這個應(yīng)用沒有什么實際功用,但只要我們能夠加密這個應(yīng)用,加密其他應(yīng)用也就不在話下。
步驟2:生成一個安全密匙。在命令行,利用GenerateKey工具(參見GenerateKey.java)把密匙寫入一個文件: % java GenerateKey key.data
步驟3:加密應(yīng)用。在命令行,利用EncryptClasses工具(參見EncryptClasses.java)加密應(yīng)用的類: % java EncryptClasses key.data App.class Foo.class Bar.class
該命令把每一個.class文件替換成它們各自的加密版本。
步驟4:運行經(jīng)過加密的應(yīng)用。用戶通過一個DecryptStart程序運行經(jīng)過加密的應(yīng)用。DecryptStart程序如Listing 6所示。 【Listing 6:DecryptStart.java,啟動被加密應(yīng)用的程序】
import java.io.*;
import java.security.*;
import java.lang.reflect.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class DecryptStart extends ClassLoader
{
// 這些對象在構(gòu)造函數(shù)中設(shè)置,
// 以后loadClass()方法將利用它們解密類
private SecretKey key;
private Cipher cipher;
// 構(gòu)造函數(shù):設(shè)置解密所需要的對象
public DecryptStart( SecretKey key ) throws GeneralSecurityException,
IOException {
this.key = key;
String algorithm = "DES";
SecureRandom sr = new SecureRandom();
System.err.println( "[DecryptStart: creating cipher]" );
cipher = Cipher.getInstance( algorithm );
cipher.init( Cipher.DECRYPT_MODE, key, sr );
}
// main過程:我們要在這里讀入密匙,創(chuàng)建DecryptStart的
// 實例,它就是我們的定制ClassLoader。
// 設(shè)置好ClassLoader以后,我們用它裝入應(yīng)用實例,
// 最后,我們通過Java Reflection API調(diào)用應(yīng)用實例的main方法
static public void main( String args[] ) throws Exception {
String keyFilename = args[0];
String appName = args[1];
// 這些是傳遞給應(yīng)用本身的參數(shù)
String realArgs[] = new String[args.length-2];
System.arraycopy( args, 2, realArgs, 0, args.length-2 );
// 讀取密匙
System.err.println( "[DecryptStart: reading key]" );
byte rawKey[] = Util.readFile( keyFilename );
DESKeySpec dks = new DESKeySpec( rawKey );
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// 創(chuàng)建解密的ClassLoader
DecryptStart dr = new DecryptStart( key );
// 創(chuàng)建應(yīng)用主類的一個實例
// 通過ClassLoader裝入它
System.err.println( "[DecryptStart: loading "+appName+"]" );
Class clasz = dr.loadClass( appName );
// 最后,通過Reflection API調(diào)用應(yīng)用實例
// 的main()方法
// 獲取一個對main()的引用
String proto[] = new String[1];
Class mainArgs[] = { (new String[1]).getClass() };
Method main = clasz.getMethod( "main", mainArgs );
// 創(chuàng)建一個包含main()方法參數(shù)的數(shù)組
Object argsArray[] = { realArgs };
System.err.println( "[DecryptStart: running "+appName+".main()]" );
// 調(diào)用main()
main.invoke( null, argsArray );
}
public Class loadClass( String name, boolean resolve )
throws ClassNotFoundException {
try {
// 我們要創(chuàng)建的Class對象
Class clasz = null;
// 必需的步驟1:如果類已經(jīng)在系統(tǒng)緩沖之中
// 我們不必再次裝入它
clasz = findLoadedClass( name );
if (clasz != null)
return clasz;
// 下面是定制部分
try {
// 讀取經(jīng)過加密的類文件
byte classData[] = Util.readFile( name+".class" );
if (classData != null) {
// 解密...
byte decryptedClassData[] = cipher.doFinal( classData );
// ... 再把它轉(zhuǎn)換成一個類
clasz = defineClass( name, decryptedClassData,
0, decryptedClassData.length );
System.err.println( "[DecryptStart: decrypting class "+name+"]" );
}
} catch( FileNotFoundException fnfe ) {
}
// 必需的步驟2:如果上面沒有成功
// 我們嘗試用默認的ClassLoader裝入它
if (clasz == null)
clasz = findSystemClass( name );
// 必需的步驟3:如有必要,則裝入相關(guān)的類
if (resolve && clasz != null)
resolveClass( clasz );
// 把類返回給調(diào)用者
return clasz;
} catch( IOException ie ) {
throw new ClassNotFoundException( ie.toString()
);
} catch( GeneralSecurityException gse ) {
throw new ClassNotFoundException( gse.toString()
);
}
}
}
對于未經(jīng)加密的應(yīng)用,正常執(zhí)行方式如下: % java App arg0 arg1 arg2
對于經(jīng)過加密的應(yīng)用,則相應(yīng)的運行方式為: % java DecryptStart key.data App arg0 arg1 arg2
DecryptStart 有兩個目的。一個DecryptStart的實例就是一個實施即時解密操作的定制ClassLoader;同時,DecryptStart還包含一個 main過程,它創(chuàng)建解密器實例并用它裝入和運行應(yīng)用。示例應(yīng)用App的代碼包含在App.java、Foo.java和Bar.java內(nèi)。 Util.java是一個文件I/O工具,本文示例多處用到了它。完整的代碼請從本文最后下載。
五、注意事項
我們看到,要在不修改源代碼的情況下加密一個Java應(yīng)用是很容易的。不過,世上沒有完全安全的系統(tǒng)。本文的加密方式提供了一定程度的源代碼保護,但對某些攻擊來說它是脆弱的。
雖 然應(yīng)用本身經(jīng)過了加密,但啟動程序DecryptStart沒有加密。攻擊者可以反編譯啟動程序并修改它,把解密后的類文件保存到磁盤。降低這種風(fēng)險的辦 法之一是對啟動程序進行高質(zhì)量的模糊處理。或者,啟動程序也可以采用直接編譯成機器語言的代碼,使得啟動程序具有傳統(tǒng)執(zhí)行文件格式的安全性。
另外還要記住的是,大多數(shù)JVM本身并不安全。狡猾的黑客可能會修改JVM,從ClassLoader之外獲取解密后的代碼并保存到磁盤,從而繞過本文的加密技術(shù)。Java沒有為此提供真正有效的補救措施。
不過應(yīng)該指出的是,所有這些可能的攻擊都有一個前提,這就是攻擊者可以得到密匙。如果沒有密匙,應(yīng)用的安全性就完全取決于加密算法的安全性。雖然這種保護代碼的方法稱不上十全十美,但它仍不失為一種保護知識產(chǎn)權(quán)和敏感用戶數(shù)據(jù)的有效方案。
參考資源
在運行時刻更新功能模塊。介紹了一個利用類庫加載器ClassLoader 實現(xiàn)在運行時刻更新部分功能模塊的Java程序,并將其與C/C++中實現(xiàn)同樣功能的動態(tài)鏈接庫方案進行了比較。
Java 技巧 105:利用 JWhich 掌握類路徑。展示一個簡單的工具,它可以清楚地確定類裝載器從類路徑中載入了什么 Java 類。
要了解更多的 Java 安全信息,請閱讀 java.sun.com的 Java Security API 頁。
如何封鎖您的(或打開別人的) Java 代碼。Java 代碼反編譯和模糊處理的指南。
使您的軟件運行起來:擺弄數(shù)字。真正安全的軟件需要精確的隨機數(shù)生成器。
下載本文代碼:EncryptedJavaClass_code.zip
關(guān)于作者
俞良松,軟件工程師,獨立顧問和自由撰稿人。最初從事PB和Oracle開發(fā),現(xiàn)主要興趣在于Internet開發(fā)。您可以通過 javaman@163.net 和我聯(lián)系。
1, 什么是 ClassLoader?
Java 程序并不是一個可執(zhí)行文件,是需要的時候,才把裝載到 JVM中。ClassLoader 做的工作就是 JVM 中將類裝入內(nèi)存。 而且,Java ClassLoader 就是用 Java 語言編寫的。這意味著您可以創(chuàng)建自己的 ClassLoader
ClassLoader 的基本目標是對類的請求提供服務(wù)。當 JVM 需要使用類時,它根據(jù)名稱向 ClassLoader 請求這個類,然后 ClassLoader 試圖返回一個表示這個類的 Class 對象。 通過覆蓋對應(yīng)于這個過程不同階段的方法,可以創(chuàng)建定制的 ClassLoader。
2, 一些重要的方法
A) 方法 loadClass
ClassLoader.loadClass() 是 ClassLoader 的入口點。該方法的定義如下:
Class loadClass( String name, boolean resolve );
name JVM 需要的類的名稱,如 Foo 或 java.lang.Object。
resolve 參數(shù)告訴方法是否需要解析類。在準備執(zhí)行類之前,應(yīng)考慮類解析。并不總是需要解析。如果 JVM 只需要知道該類是否存在或找出該類的超類,那么就不需要解析。

defineClass 方法是 ClassLoader 的主要訣竅。該方法接受由原始字節(jié)組成的數(shù)組并把它轉(zhuǎn)換成 Class 對象。原始數(shù)組包含如從文件系統(tǒng)或網(wǎng)絡(luò)裝入的數(shù)據(jù)。defineClass 管理 JVM 的許多復(fù)雜、神秘和倚賴于實現(xiàn)的方面 -- 它把字節(jié)碼分析成運行時數(shù)據(jù)結(jié)構(gòu)、校驗有效性等等。不必擔(dān)心,您無需親自編寫它。事實上,即使您想要這么做也不能覆蓋它,因為它已被標記成final的。
C) 方法 findSystemClass
findSystemClass 方法從本地文件系統(tǒng)裝入文件。它在本地文件系統(tǒng)中尋找類文件,如果存在,就使用 defineClass 將原始字節(jié)轉(zhuǎn)換成 Class 對象,以將該文件轉(zhuǎn)換成類。當運行 Java 應(yīng)用程序時,這是 JVM 正常裝入類的缺省機制。(Java 2 中 ClassLoader 的變動提供了關(guān)于 Java 版本 1.2 這個過程變動的詳細信息。) 對于定制的 ClassLoader,只有在嘗試其它方法裝入類之后,再使用 findSystemClass。原因很簡單:ClassLoader 是負責(zé)執(zhí)行裝入類的特殊步驟,不是負責(zé)所有類。例如,即使 ClassLoader 從遠程的 Web 站點裝入了某些類,仍然需要在本地機器上裝入大量的基本 Java 庫。而這些類不是我們所關(guān)心的,所以要 JVM 以缺省方式裝入它們:從本地文件系統(tǒng)。這就是 findSystemClass 的用途。
D) 方法 resolveClass
正如前面所提到的,可以不完全地(不帶解析)裝入類,也可以完全地(帶解析)裝入類。當編寫我們自己的 loadClass 時,可以調(diào)用 resolveClass,這取決于 loadClass 的 resolve 參數(shù)的值。
E) 方法 findLoadedClass
findLoadedClass 充當一個緩存:當請求 loadClass 裝入類時,它調(diào)用該方法來查看 ClassLoader 是否已裝入這個類,這樣可以避免重新裝入已存在類所造成的麻煩。應(yīng)首先調(diào)用該方法。
3, 怎么組裝這些方法
1) 調(diào)用 findLoadedClass 來查看是否存在已裝入的類。
2) 如果沒有,那么采用那種特殊的神奇方式來獲取原始字節(jié)。
3) 如果已有原始字節(jié),調(diào)用 defineClass 將它們轉(zhuǎn)換成 Class 對象。
4) 如果沒有原始字節(jié),然后調(diào)用 findSystemClass 查看是否從本地文件系統(tǒng)獲取類。
5) 如果 resolve 參數(shù)是 true,那么調(diào)用 resolveClass 解析 Class 對象。
6) 如果還沒有類,返回 ClassNotFoundException。
4,Java 2 中 ClassLoader 的變動
1)loadClass 的缺省實現(xiàn)
定制編寫的 loadClass 方法一般嘗試幾種方式來裝入所請求的類,如果您編寫許多類,會發(fā)現(xiàn)一次次地在相同的、很復(fù)雜的方法上編寫變量。 在 Java 1.2 中 loadClass 的實現(xiàn)嵌入了大多數(shù)查找類的一般方法,并使您通過覆蓋 findClass 方法來定制它,在適當?shù)臅r候 findClass 會調(diào)用 loadClass。 這種方式的好處是您可能不一定要覆蓋 loadClass;只要覆蓋 findClass 就行了,這減少了工作量。
2)新方法:findClass
loadClass 的缺省實現(xiàn)調(diào)用這個新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代碼,而無需要復(fù)制其它代碼(例如,當專門的方法失敗時,調(diào)用系統(tǒng) ClassLoader)。
3) 新方法:getSystemClassLoader
如果覆蓋 findClass 或 loadClass,getSystemClassLoader 使您能以實際 ClassLoader 對象來訪問系統(tǒng) ClassLoader(而不是固定的從 findSystemClass 調(diào)用它)。
4) 新方法:getParent
為了將類請求委托給父代 ClassLoader,這個新方法允許 ClassLoader 獲取它的父代 ClassLoader。當使用特殊方法,定制的 ClassLoader 不能找到類時,可以使用這種方法。
父代 ClassLoader 被定義成創(chuàng)建該 ClassLoader 所包含代碼的對象的 ClassLoader。
運用加密技術(shù)保護Java源代碼
QUOTE
Java程序的源代碼很容易被別人偷看。只要有一個反編譯器,任何人都可以分析別人的代碼。本文討論如何在不修改原有程序的情況下,通過加密技術(shù)保護源代碼。一、為什么要加密?
對 于傳統(tǒng)的C或C++之類的語言來說,要在Web上保護源代碼是很容易的,只要不發(fā)布它就可以。遺憾的是,Java程序的源代碼很容易被別人偷看。只要有一 個反編譯器,任何人都可以分析別人的代碼。Java的靈活性使得源代碼很容易被竊取,但與此同時,它也使通過加密保護代碼變得相對容易,我們唯一需要了解 的就是Java的ClassLoader對象。當然,在加密過程中,有關(guān)Java Cryptography Extension(JCE)的知識也是必不可少的。
有幾種技術(shù)可以“模糊”Java類文件,使得反編譯器處理類文件的效果大打折扣。然而,修改反編譯器使之能夠處理這些經(jīng)過模糊處理的類文件并不是什么難事,所以不能簡單地依賴模糊技術(shù)來保證源代碼的安全。
我們可以用流行的加密工具加密應(yīng)用,比如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard)。這時,最終用戶在運行應(yīng)用之前必須先進行解密。但解密之后,最終用戶就有了一份不加密的類文件,這和事先不進行加密沒有什么差別。
Java 運行時裝入字節(jié)碼的機制隱含地意味著可以對字節(jié)碼進行修改。JVM每次裝入類文件時都需要一個稱為ClassLoader的對象,這個對象負責(zé)把新的類裝 入正在運行的JVM。JVM給ClassLoader一個包含了待裝入類(比如java.lang.Object)名字的字符串,然后由 ClassLoader負責(zé)找到類文件,裝入原始數(shù)據(jù),并把它轉(zhuǎn)換成一個Class對象。
我們可以通過定制ClassLoader,在類文件執(zhí)行之前修改它。這種技術(shù)的應(yīng)用非常廣泛——在這里,它的用途是在類文件裝入之時進行解密,因此可以看成是一種即時解密器。由于解密后的字節(jié)碼文件永遠不會保存到文件系統(tǒng),所以竊密者很難得到解密后的代碼。
由于把原始字節(jié)碼轉(zhuǎn)換成Class對象的過程完全由系統(tǒng)負責(zé),所以創(chuàng)建定制ClassLoader對象其實并不困難,只需先獲得原始數(shù)據(jù),接著就可以進行包含解密在內(nèi)的任何轉(zhuǎn)換。
Java 2在一定程度上簡化了定制ClassLoader的構(gòu)建。在Java 2中,loadClass的缺省實現(xiàn)仍舊負責(zé)處理所有必需的步驟,但為了顧及各種定制的類裝入過程,它還調(diào)用一個新的findClass方法。
這為我們編寫定制的ClassLoader提供了一條捷徑,減少了麻煩:只需覆蓋findClass,而不是覆蓋loadClass。這種方法避免了重復(fù)所有裝入器必需執(zhí)行的公共步驟,因為這一切由loadClass負責(zé)。
不 過,本文的定制ClassLoader并不使用這種方法。原因很簡單。如果由默認的ClassLoader先尋找經(jīng)過加密的類文件,它可以找到;但由于類 文件已經(jīng)加密,所以它不會認可這個類文件,裝入過程將失敗。因此,我們必須自己實現(xiàn)loadClass,稍微增加了一些工作量。
二、定制類裝入器
每一個運行著的JVM已經(jīng)擁有一個ClassLoader。這個默認的ClassLoader根據(jù)CLASSPATH環(huán)境變量的值,在本地文件系統(tǒng)中尋找合適的字節(jié)碼文件。
應(yīng) 用定制ClassLoader要求對這個過程有較為深入的認識。我們首先必須創(chuàng)建一個定制ClassLoader類的實例,然后顯式地要求它裝入另外一個 類。這就強制JVM把該類以及所有它所需要的類關(guān)聯(lián)到定制的ClassLoader。Listing 1顯示了如何用定制ClassLoader裝入類文件。
【Listing 1:利用定制的ClassLoader裝入類文件】
// 首先創(chuàng)建一個ClassLoader對象
ClassLoader myClassLoader = new myClassLoader();
// 利用定制ClassLoader對象裝入類文件
// 并把它轉(zhuǎn)換成Class對象
Class myClass = myClassLoader.loadClass( "mypackage.MyClass" );
// 最后,創(chuàng)建該類的一個實例
Object newInstance = myClass.newInstance();
// 注意,MyClass所需要的所有其他類,都將通過
// 定制的ClassLoader自動裝入
如前所述,定制ClassLoader只需先獲取類文件的數(shù)據(jù),然后把字節(jié)碼傳遞給運行時系統(tǒng),由后者完成余下的任務(wù)。
ClassLoader 有幾個重要的方法。創(chuàng)建定制的ClassLoader時,我們只需覆蓋其中的一個,即loadClass,提供獲取原始類文件數(shù)據(jù)的代碼。這個方法有兩個 參數(shù):類的名字,以及一個表示JVM是否要求解析類名字的標記(即是否同時裝入有依賴關(guān)系的類)。如果這個標記是true,我們只需在返回JVM之前調(diào)用 resolveClass。
【Listing 2:ClassLoader.loadClass()的一個簡單實現(xiàn)】
public Class loadClass( String name, boolean resolve )
throws ClassNotFoundException {
try {
// 我們要創(chuàng)建的Class對象
Class clasz = null;
// 必需的步驟1:如果類已經(jīng)在系統(tǒng)緩沖之中,
// 我們不必再次裝入它
clasz = findLoadedClass( name );
if (clasz != null)
return clasz;
// 下面是定制部分
byte classData[] = /* 通過某種方法獲取字節(jié)碼數(shù)據(jù) */;
if (classData != null) {
// 成功讀取字節(jié)碼數(shù)據(jù),現(xiàn)在把它轉(zhuǎn)換成一個Class對象
clasz = defineClass( name, classData, 0, classData.length );
}
// 必需的步驟2:如果上面沒有成功,
// 我們嘗試用默認的ClassLoader裝入它
if (clasz == null)
clasz = findSystemClass( name );
// 必需的步驟3:如有必要,則裝入相關(guān)的類
if (resolve && clasz != null)
resolveClass( clasz );
// 把類返回給調(diào)用者
return clasz;
} catch( IOException ie ) {
throw new ClassNotFoundException( ie.toString() );
} catch( GeneralSecurityException gse ) {
throw new ClassNotFoundException( gse.toString() );
}
}
Listing 2顯示了一個簡單的loadClass實現(xiàn)。代碼中的大部分對所有ClassLoader對象來說都一樣,但有一小部分(已通過注釋標記)是特有的。在處理過程中,ClassLoader對象要用到其他幾個輔助方法:
findLoadedClass:用來進行檢查,以便確認被請求的類當前還不存在。loadClass方法應(yīng)該首先調(diào)用它。
defineClass:獲得原始類文件字節(jié)碼數(shù)據(jù)之后,調(diào)用defineClass把它轉(zhuǎn)換成一個Class對象。任何loadClass實現(xiàn)都必須調(diào)用這個方法。
findSystemClass:提供默認ClassLoader的支持。如果用來尋找類的定制方法不能找到指定的類(或者有意地不用定制方法),則可以調(diào)用該方法嘗試默認的裝入方式。這是很有用的,特別是從普通的JAR文件裝入標準Java類時。
resolveClass:當JVM想要裝入的不僅包括指定的類,而且還包括該類引用的所有其他類時,它會把loadClass的resolve參數(shù)設(shè)置成true。這時,我們必須在返回剛剛裝入的Class對象給調(diào)用者之前調(diào)用resolveClass。
三、加密、解密
Java加密擴展即Java Cryptography Extension,簡稱JCE。它是Sun的加密服務(wù)軟件,包含了加密和密匙生成功能。JCE是JCA(Java Cryptography Architecture)的一種擴展。
JCE 沒有規(guī)定具體的加密算法,但提供了一個框架,加密算法的具體實現(xiàn)可以作為服務(wù)提供者加入。除了JCE框架之外,JCE軟件包還包含了SunJCE服務(wù)提供 者,其中包括許多有用的加密算法,比如DES(Data Encryption Standard)和Blowfish。
為簡單計,在本文中我們將用DES算法加密和解密字節(jié)碼。下面是用JCE加密和解密數(shù)據(jù)必須遵循的基本步驟:
步驟1:生成一個安全密匙。在加密或解密任何數(shù)據(jù)之前需要有一個密匙。密匙是隨同被加密的應(yīng)用一起發(fā)布的一小段數(shù)據(jù),Listing 3顯示了如何生成一個密匙。 【Listing 3:生成一個密匙】
// DES算法要求有一個可信任的隨機數(shù)源
SecureRandom sr = new SecureRandom();
// 為我們選擇的DES算法生成一個KeyGenerator對象
KeyGenerator kg = KeyGenerator.getInstance( "DES" );
kg.init( sr );
// 生成密匙
SecretKey key = kg.generateKey();
// 獲取密匙數(shù)據(jù)
byte rawKeyData[] = key.getEncoded();
/* 接下來就可以用密匙進行加密或解密,或者把它保存
為文件供以后使用 */
doSomething( rawKeyData );
步驟2:加密數(shù)據(jù)。得到密匙之后,接下來就可以用它加密數(shù)據(jù)。除了解密的ClassLoader之外,一般還要有一個加密待發(fā)布應(yīng)用的獨立程序(見Listing 4)。 【Listing 4:用密匙加密原始數(shù)據(jù)】
// DES算法要求有一個可信任的隨機數(shù)源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某種方法獲得密匙數(shù)據(jù) */;
// 從原始密匙數(shù)據(jù)創(chuàng)建DESKeySpec對象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 創(chuàng)建一個密匙工廠,然后用它把DESKeySpec轉(zhuǎn)換成
// 一個SecretKey對象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher對象實際完成加密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher對象
cipher.init( Cipher.ENCRYPT_MODE, key, sr );
// 現(xiàn)在,獲取數(shù)據(jù)并加密
byte data[] = /* 用某種方法獲取數(shù)據(jù) */
// 正式執(zhí)行加密操作
byte encryptedData[] = cipher.doFinal( data );
// 進一步處理加密后的數(shù)據(jù)
doSomething( encryptedData );
步驟3:解密數(shù)據(jù)。運行經(jīng)過加密的應(yīng)用時,ClassLoader分析并解密類文件。操作步驟如Listing 5所示。 【Listing 5:用密匙解密數(shù)據(jù)】
// DES算法要求有一個可信任的隨機數(shù)源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某種方法獲取原始密匙數(shù)據(jù) */;
// 從原始密匙數(shù)據(jù)創(chuàng)建一個DESKeySpec對象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 創(chuàng)建一個密匙工廠,然后用它把DESKeySpec對象轉(zhuǎn)換成
// 一個SecretKey對象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher對象實際完成解密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher對象
cipher.init( Cipher.DECRYPT_MODE, key, sr );
// 現(xiàn)在,獲取數(shù)據(jù)并解密
byte encryptedData[] = /* 獲得經(jīng)過加密的數(shù)據(jù) */
// 正式執(zhí)行解密操作
byte decryptedData[] = cipher.doFinal( encryptedData );
// 進一步處理解密后的數(shù)據(jù)
doSomething( decryptedData );
四、應(yīng)用實例
前面介紹了如何加密和解密數(shù)據(jù)。要部署一個經(jīng)過加密的應(yīng)用,步驟如下:
步驟1:創(chuàng)建應(yīng)用。我們的例子包含一個App主類,兩個輔助類(分別稱為Foo和Bar)。這個應(yīng)用沒有什么實際功用,但只要我們能夠加密這個應(yīng)用,加密其他應(yīng)用也就不在話下。
步驟2:生成一個安全密匙。在命令行,利用GenerateKey工具(參見GenerateKey.java)把密匙寫入一個文件: % java GenerateKey key.data
步驟3:加密應(yīng)用。在命令行,利用EncryptClasses工具(參見EncryptClasses.java)加密應(yīng)用的類: % java EncryptClasses key.data App.class Foo.class Bar.class
該命令把每一個.class文件替換成它們各自的加密版本。
步驟4:運行經(jīng)過加密的應(yīng)用。用戶通過一個DecryptStart程序運行經(jīng)過加密的應(yīng)用。DecryptStart程序如Listing 6所示。 【Listing 6:DecryptStart.java,啟動被加密應(yīng)用的程序】
import java.io.*;
import java.security.*;
import java.lang.reflect.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class DecryptStart extends ClassLoader
{
// 這些對象在構(gòu)造函數(shù)中設(shè)置,
// 以后loadClass()方法將利用它們解密類
private SecretKey key;
private Cipher cipher;
// 構(gòu)造函數(shù):設(shè)置解密所需要的對象
public DecryptStart( SecretKey key ) throws GeneralSecurityException,
IOException {
this.key = key;
String algorithm = "DES";
SecureRandom sr = new SecureRandom();
System.err.println( "[DecryptStart: creating cipher]" );
cipher = Cipher.getInstance( algorithm );
cipher.init( Cipher.DECRYPT_MODE, key, sr );
}
// main過程:我們要在這里讀入密匙,創(chuàng)建DecryptStart的
// 實例,它就是我們的定制ClassLoader。
// 設(shè)置好ClassLoader以后,我們用它裝入應(yīng)用實例,
// 最后,我們通過Java Reflection API調(diào)用應(yīng)用實例的main方法
static public void main( String args[] ) throws Exception {
String keyFilename = args[0];
String appName = args[1];
// 這些是傳遞給應(yīng)用本身的參數(shù)
String realArgs[] = new String[args.length-2];
System.arraycopy( args, 2, realArgs, 0, args.length-2 );
// 讀取密匙
System.err.println( "[DecryptStart: reading key]" );
byte rawKey[] = Util.readFile( keyFilename );
DESKeySpec dks = new DESKeySpec( rawKey );
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// 創(chuàng)建解密的ClassLoader
DecryptStart dr = new DecryptStart( key );
// 創(chuàng)建應(yīng)用主類的一個實例
// 通過ClassLoader裝入它
System.err.println( "[DecryptStart: loading "+appName+"]" );
Class clasz = dr.loadClass( appName );
// 最后,通過Reflection API調(diào)用應(yīng)用實例
// 的main()方法
// 獲取一個對main()的引用
String proto[] = new String[1];
Class mainArgs[] = { (new String[1]).getClass() };
Method main = clasz.getMethod( "main", mainArgs );
// 創(chuàng)建一個包含main()方法參數(shù)的數(shù)組
Object argsArray[] = { realArgs };
System.err.println( "[DecryptStart: running "+appName+".main()]" );
// 調(diào)用main()
main.invoke( null, argsArray );
}
public Class loadClass( String name, boolean resolve )
throws ClassNotFoundException {
try {
// 我們要創(chuàng)建的Class對象
Class clasz = null;
// 必需的步驟1:如果類已經(jīng)在系統(tǒng)緩沖之中
// 我們不必再次裝入它
clasz = findLoadedClass( name );
if (clasz != null)
return clasz;
// 下面是定制部分
try {
// 讀取經(jīng)過加密的類文件
byte classData[] = Util.readFile( name+".class" );
if (classData != null) {
// 解密...
byte decryptedClassData[] = cipher.doFinal( classData );
// ... 再把它轉(zhuǎn)換成一個類
clasz = defineClass( name, decryptedClassData,
0, decryptedClassData.length );
System.err.println( "[DecryptStart: decrypting class "+name+"]" );
}
} catch( FileNotFoundException fnfe ) {
}
// 必需的步驟2:如果上面沒有成功
// 我們嘗試用默認的ClassLoader裝入它
if (clasz == null)
clasz = findSystemClass( name );
// 必需的步驟3:如有必要,則裝入相關(guān)的類
if (resolve && clasz != null)
resolveClass( clasz );
// 把類返回給調(diào)用者
return clasz;
} catch( IOException ie ) {
throw new ClassNotFoundException( ie.toString()
);
} catch( GeneralSecurityException gse ) {
throw new ClassNotFoundException( gse.toString()
);
}
}
}
對于未經(jīng)加密的應(yīng)用,正常執(zhí)行方式如下: % java App arg0 arg1 arg2
對于經(jīng)過加密的應(yīng)用,則相應(yīng)的運行方式為: % java DecryptStart key.data App arg0 arg1 arg2
DecryptStart 有兩個目的。一個DecryptStart的實例就是一個實施即時解密操作的定制ClassLoader;同時,DecryptStart還包含一個 main過程,它創(chuàng)建解密器實例并用它裝入和運行應(yīng)用。示例應(yīng)用App的代碼包含在App.java、Foo.java和Bar.java內(nèi)。 Util.java是一個文件I/O工具,本文示例多處用到了它。完整的代碼請從本文最后下載。
五、注意事項
我們看到,要在不修改源代碼的情況下加密一個Java應(yīng)用是很容易的。不過,世上沒有完全安全的系統(tǒng)。本文的加密方式提供了一定程度的源代碼保護,但對某些攻擊來說它是脆弱的。
雖 然應(yīng)用本身經(jīng)過了加密,但啟動程序DecryptStart沒有加密。攻擊者可以反編譯啟動程序并修改它,把解密后的類文件保存到磁盤。降低這種風(fēng)險的辦 法之一是對啟動程序進行高質(zhì)量的模糊處理。或者,啟動程序也可以采用直接編譯成機器語言的代碼,使得啟動程序具有傳統(tǒng)執(zhí)行文件格式的安全性。
另外還要記住的是,大多數(shù)JVM本身并不安全。狡猾的黑客可能會修改JVM,從ClassLoader之外獲取解密后的代碼并保存到磁盤,從而繞過本文的加密技術(shù)。Java沒有為此提供真正有效的補救措施。
不過應(yīng)該指出的是,所有這些可能的攻擊都有一個前提,這就是攻擊者可以得到密匙。如果沒有密匙,應(yīng)用的安全性就完全取決于加密算法的安全性。雖然這種保護代碼的方法稱不上十全十美,但它仍不失為一種保護知識產(chǎn)權(quán)和敏感用戶數(shù)據(jù)的有效方案。
參考資源
在運行時刻更新功能模塊。介紹了一個利用類庫加載器ClassLoader 實現(xiàn)在運行時刻更新部分功能模塊的Java程序,并將其與C/C++中實現(xiàn)同樣功能的動態(tài)鏈接庫方案進行了比較。
Java 技巧 105:利用 JWhich 掌握類路徑。展示一個簡單的工具,它可以清楚地確定類裝載器從類路徑中載入了什么 Java 類。
要了解更多的 Java 安全信息,請閱讀 java.sun.com的 Java Security API 頁。
如何封鎖您的(或打開別人的) Java 代碼。Java 代碼反編譯和模糊處理的指南。
使您的軟件運行起來:擺弄數(shù)字。真正安全的軟件需要精確的隨機數(shù)生成器。
下載本文代碼:EncryptedJavaClass_code.zip
關(guān)于作者
俞良松,軟件工程師,獨立顧問和自由撰稿人。最初從事PB和Oracle開發(fā),現(xiàn)主要興趣在于Internet開發(fā)。您可以通過 javaman@163.net 和我聯(lián)系。