Java 編譯產生的 .class 位元碼保留豐富元資料,標準類別載入器公開讀取,配合反編譯器 (JAD, CFR, Fernflower) 輕易還原源碼。自訂 ClassLoader 可在載入管道植入解密邏輯,實現原始碼保護。
1. findClass() / loadClass() → 定位類別資料來源
2. 讀取位元碼資料 (byte[])
3. defineClass(name, bytes, 0, bytes.length) → 交給 JVM 定義類別
// 切入點:步驟 2 與 3 之間進行解密
package com.neusoft.jiami;
import javax.crypto.*;
import java.io.*;
import java.security.SecureRandom;
public class KeyGenerator {
public static void main(String[] args) throws Exception {
SecureRandom sr = new SecureRandom();
KeyGenerator kg = KeyGenerator.getInstance("DES");
kg.init(sr);
SecretKey key = kg.generateKey();
byte[] raw = key.getEncoded();
try (FileOutputStream fos = new FileOutputStream("key.dat")) {
fos.write(raw);
}
System.out.println("Key saved to key.dat");
}
}
package com.neusoft.jiami;
import javax.crypto.*;
import java.io.*;
import java.security.spec.DESKeySpec;
public class ClassEncryptor {
public static void main(String[] args) throws Exception {
// 讀取金鑰
byte[] raw = readFile("key.dat");
DESKeySpec dks = new DESKeySpec(raw);
SecretKey key = SecretKeyFactory.getInstance("DES").generateSecret(dks);
// 初始化加密器
Cipher cipher = Cipher.getInstance("DES");
SecureRandom sr = new SecureRandom();
cipher.init(Cipher.ENCRYPT_MODE, key, sr);
// 讀取並加密類別檔
byte[] classBytes = readFile("DigestPass.class");
byte[] encrypted = cipher.doFinal(classBytes);
// 覆寫原檔 (部署時分發加密後的 .class)
writeFile("DigestPass.class", encrypted);
System.out.println("Class encrypted.");
}
static byte[] readFile(String path) throws IOException {
try (FileInputStream fis = new FileInputStream(path)) {
byte[] buf = new byte[fis.available()];
fis.read(buf);
return buf;
}
}
static void writeFile(String path, byte[] data) throws IOException {
try (FileOutputStream fos = new FileOutputStream(path)) { fos.write(data); }
}
}
package com.neusoft.classloader;
import javax.crypto.*;
import java.io.*;
import java.security.spec.DESKeySpec;
public class MyClassLoader extends ClassLoader {
private final String classPath;
private final SecretKey key;
private final Cipher cipher;
public MyClassLoader(String classPath) throws Exception {
this.classPath = classPath;
byte[] raw = readFile(classPath + "key.dat");
DESKeySpec dks = new DESKeySpec(raw);
this.key = SecretKeyFactory.getInstance("DES").generateSecret(dks);
this.cipher = Cipher.getInstance("DES");
this.cipher.init(Cipher.DECRYPT_MODE, key, new SecureRandom());
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
try {
// 1. 讀取加密位元碼
String path = classPath + name.replace('.', '/') + ".class";
byte[] encrypted = readFile(path);
// 2. 解密
byte[] decrypted = cipher.doFinal(encrypted);
// 3. 定義類別
return defineClass(name, decrypted, 0, decrypted.length);
} catch (Exception e) {
throw new ClassNotFoundException(name, e);
}
}
private byte[] readFile(String path) throws IOException {
try (FileInputStream fis = new FileInputStream(path)) {
byte[] buf = new byte[fis.available()];
fis.read(buf);
return buf;
}
}
}
// 使用範例
public class App {
public static void main(String[] args) throws Exception {
MyClassLoader cl = new MyClassLoader("E:/classes/");
Class> c = cl.loadClass("com.neusoft.jiami.DigestPass");
Object instance = c.getDeclaredConstructor().newInstance();
// 使用 instance...
}
}
| 弱點 | 風險 | 強化對策 |
|---|---|---|
| 金鑰檔案外洩 | 完全破解 | 金鑰內建於載入器原生代碼 (JNI)、硬體安全模組 (HSM)、白盒加密 |
| JVM 記憶體傾印 | 解密後位元碼可被提取 | 運行時加密 (JVMTI Agent)、記憶體保護、混淆器配合 |
| 自訂載入器被替換 | 繞過解密邏輯 | 載入器簽名驗證、啟動類別鏈完整性檢查 |
| DES 演算法脆弱 | 暴力破解可行 | 升級 AES-256、ChaCha20、國密 SM4 |
| 單一金鑰全專案通用 | 一把鑰匙開所有鎖 | 每類別/模組獨立金鑰、金鑰分級派生 |