Java 誕生於 1995 年,以平台無關性、安全性、面向物件、分散式、健壯性贏得青睞。但存在致命弱點:運行速度慢、用戶使用不便、原始碼保護機制不足。Java 基於解釋位元組碼運行,JVM 遠比微處理器簡單、文檔齊全,導致目標程式極易被反編譯,所得代碼與原始碼高度相似甚至完全一致,可讀性極佳,對原始碼保護極為不利。
經研究總結,至少有三種保護實現方式:1. 混淆器;2. 網路載入關鍵類;3. 加密關鍵類。
目前開發人員常用混淆器保護代碼。混淆器將類、變數、方法、套件名改為無意義字串;使用非法字元替換符號;插入導致反編譯工具崩潰的代碼;加入無關指令或永不執行指令,使反編譯失敗或所得代碼可讀性極差。
演示:原始碼 sKey_kb.java 經混淆後再用 Jad 反編譯:
// 原始碼片段
public class sKey_kb {
public static void main(String args[]) throws Exception {
FileInputStream f = new FileInputStream("key1.dat");
ObjectInputStream b = new ObjectInputStream(f);
Key k = (Key) b.readObject();
byte[] kb = k.getEncoded();
FileOutputStream f2 = new FileOutputStream("keykb1.dat");
f2.write(kb);
for (int i = 0; i < kb.length; i++)
System.out.print(kb[i] + ",");
}
}
// 混淆後反編譯結果
import java.io.*;
import java.security.Key;
public class sKey_kb {
public skey() {}
public static void main(String args[]) {
FileInputStream fileinputstream = new FileInputStream(ma);
ObjectInputStream objectinputstream = new ObjectInputStream(fileinputstream);
Key key = (Key) b.readObject();
byte abyte0[] = key.getEncoded();
FileOutputStream fileoutputstream = new FileOutputStream(na);
fileoutputstream.write(abyte0);
for (int i = 0; i < abyte0.length; i++)
System.out.print(abyte0[i] + oa);
}
private static String a(String s) {
int i = s.length();
char ac[] = new char[i];
for (int j = 0; j < i; j++)
ac[j] = (char) (s.charAt(j) ^ 0xffff5aca);
return new String(ac);
}
private static String ma = "u5AA1u5AAFu5AF3u5AFBu5AE4u5AAEu5AABu5ABE";
private static String na = "u5AA1u5AAFu5AB3u5AA1u5AA8u5AFBu5AE4u5AAEu5AABu5ABE";
private static String oa = "u5AE6";
public static {
ma = a(ma);
na = a(ma);
oa = a(oa);
}
}
混淆後代碼雖仍可反編譯,但變得難以閱讀,且無法直接編譯運行。但若軟體內含註冊資訊或簡單演算法,反編譯仍可能獲取。混淆器與反編譯器的對抗無休止,需從其他角度保護。
Java 提供 ClassLoader 類,透過重寫可實現從網路 URL 載入位元組碼。將重要、隱秘的 class 放在網路伺服器,透過密碼驗證權限後下載,達到保護目的。URLClassLoader 正好支援此功能:以 URL 陣列告知載入位置,再用 loadClass() 載入位元組碼、獲取方法並執行。
具體步驟:
URL url[] = {
new URL("file:///c:/classloader/web"),
new URL("https://www.ddvip.com/")
};URLClassLoader 物件……