Java 類文件編譯為字節碼,JVM 規範完全透明,導致 反編譯極易 (JAD、JD-GUI、Fernflower 等工具可近乎完美還原源碼)。保護知識產權的三大路徑對比:
| 方案 | 原理 | 缺陷 |
|---|---|---|
| 代碼混淆 | 重命名符號、插入無效指令、控制流扁平化 | 可被反混淆工具還原;不防止邏輯分析 |
| 源碼加密 (PGP/GPG) | 發布加密文件,用戶運行前解密 | 解密後得到明文類文件,等同無加密 |
| 類文件加密 + 自定義 ClassLoader 運行時解密 | 類文件持久化加密,JVM 載入時即時解密,明文不落盤 | 最有效:攻擊者需逆向 ClassLoader 邏輯與密鑰 |
MessageDigest, Signature, KeyPairGenerator 等),廠商無關Cipher, KeyGenerator, SecretKeyFactory 等),受出口管制歷史影響曾分離,現整合在 JDK| 參數 | 長度 | 說明 |
|---|---|---|
| Key (密鑰) | 8 bytes (64 bits,含 8 位校驗位,有效 56 bits) | 對稱密鑰,加解密同鑰 |
| Data (數據塊) | 8 bytes (64 bits) | 分組加密單位 |
| Mode (模式) | - | ENCRYPT_MODE / DECRYPT_MODE |
注意:DES 已不再安全 (56 位密鑰可暴力破解),生產環境強制使用 AES-256。本文以 DES 為教學示例,實戰請替換算法名稱。
// 生成密鑰文件 key.dat
SecureRandom sr = new SecureRandom();
KeyGenerator kg = KeyGenerator.getInstance("DES"); // 實戰用 "AES"
kg.init(sr);
SecretKey key = kg.generateKey();
Files.write(Paths.get("key.dat"), key.getEncoded());
// 加密單個 .class 文件
byte[] rawKey = Files.readAllBytes(Paths.get("key.dat"));
DESKeySpec dks = new DESKeySpec(rawKey);
SecretKeyFactory skf = SecretKeyFactory.getInstance("DES");
SecretKey key = skf.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); // 明確指定模式/填充
cipher.init(Cipher.ENCRYPT_MODE, key, sr);
byte[] classData = Files.readAllBytes(Paths.get("Target.class"));
byte[] encrypted = cipher.doFinal(classData);
Files.write(Paths.get("Target.class.enc"), encrypted); // 發布 .enc 文件
public class DecryptClassLoader extends ClassLoader {
private final SecretKey key;
private final String encryptedDir; // 加密類文件目錄
public DecryptClassLoader(SecretKey key, String encryptedDir, ClassLoader parent) {
super(parent);
this.key = key;
this.encryptedDir = encryptedDir;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
// 1. 定位加密文件
String path = name.replace('.', '/') + ".class.enc";
File encFile = new File(encryptedDir, path);
if (!encFile.exists()) {
return super.findClass(name); // 委派父加載器 (系統類等)
}
try {
// 2. 讀取並解密
byte[] encrypted = Files.readAllBytes(encFile.toPath());
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(encrypted);
// 3. 定義類
return defineClass(name, decrypted, 0, decrypted.length);
} catch (Exception e) {
throw new ClassNotFoundException("Decrypt failed: " + name, e);
}
}
}
public class SecureLauncher {
public static void main(String[] args) throws Exception {
// 1. 載入密鑰 (可從硬件加密設備、環境變量、遠程服務獲取)
byte[] rawKey = Files.readAllBytes(Paths.get("key.dat"));
SecretKey key = SecretKeyFactory.getInstance("DES")
.generateSecret(new DESKeySpec(rawKey));
// 2. 安裝自定義 ClassLoader
DecryptClassLoader loader = new DecryptClassLoader(key, "encrypted_classes",
SecureLauncher.class.getClassLoader());
// 3. 啟動真正應用 (由自定義 Loader 載入)
Class> mainClass = loader.loadClass("com.myapp.Main");
Method main = mainClass.getMethod("main", String[].class);
main.invoke(null, (Object) args);
}
}
// 現代標準:認證加密,防篡改
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// 需管理 IV (12 bytes) 與認證標籤 (16 bytes),建議隨密文一併存儲
| 局限 | 補充方案 |
|---|---|
| 系統類 (java.*, javax.*) 不經自定義 Loader | 無需加密,非業務邏輯 |
| JVM 內存中存在明文字節碼 | 內存加密技術 (極複雜)、硬件加密內存 (SGX/SEV) |
| 熱插拔/動態代理類可能繞過 | 覆蓋 loadClass 而非 findClass (需謹慎) |
| 單一密鑰洩露導致全盤失效 | 分級密鑰、定期輪換、模組級加密 |
「類文件加密 + 自定義 ClassLoader 即時解密」是 Java 端側代碼保護的 工程化最佳方案。核心鏈路:構建期加密 → 發布密文 → 運行期解密 → defineClass。配合 AES-256-GCM、硬件級密鑰管理、完整性驗證、反調試混淆,可將逆向成本提升至經濟上不可行水平。但需認清:純軟件方案無法達到絕對安全,高價值資產需結合硬件信任錨 (TEE/HSM) 實施纵深防禦。