在現實場景中,某金融公司開發了一個基於 Spring Boot 的應用程式,該程式用於處理金融資料,具有高敏感性。爲了防止該程式的核心程式碼(如資料加密、交易演算法等)被反編譯或篡改,公司希望透過加密 JAR 包並在執行時解密的方式來保護核心程式碼。
為實現這個需求,我們設計了一個簡單的加密和解密方案,其中:
使用對稱加密演算法(AES)加密 JAR 檔案。
在程式啟動時動態解密 JAR 檔案,並載入解密後的類。
將解密金鑰儲存在安全的金鑰管理系統中(例如 AWS KMS 或 Azure Key Vault),並在啟動時從安全儲存中提取金鑰。
實現步驟
1. 加密原始 JAR 檔案
在構建完成 Spring Boot 專案後,獲得一個原始的 JAR 檔案(如 app.jar
)。我們首先編寫一個 Java 工具類 JarEncryptor
,利用 AES 加密演算法對該 JAR 檔案進行加密。
import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.security.SecureRandom; import java.util.Base64; public class JarEncryptor { private static final String AES = "AES"; private static final int KEY_SIZE = 128; public static void main(String[] args) throws Exception { String jarPath = "app.jar"; // 原始 JAR 檔案路徑 String encryptedJarPath = "app_encrypted.jar"; // 加密後的 JAR 檔案路徑 String secretKey = generateSecretKey(); // 生成並輸出金鑰 System.out.println("Secret Key (Store this securely): " + secretKey); encryptJar(jarPath, encryptedJarPath, secretKey); } // 生成 AES 金鑰 public static String generateSecretKey() throws Exception { KeyGenerator keyGen = KeyGenerator.getInstance(AES); keyGen.init(KEY_SIZE, new SecureRandom()); SecretKey secretKey = keyGen.generateKey(); return Base64.getEncoder().encodeToString(secretKey.getEncoded()); } // 加密 JAR 檔案 public static void encryptJar(String jarPath, String encryptedJarPath, String secretKey) throws Exception { SecretKeySpec keySpec = new SecretKeySpec(Base64.getDecoder().decode(secretKey), AES); Cipher cipher = Cipher.getInstance(AES); cipher.init(Cipher.ENCRYPT_MODE, keySpec); try (FileInputStream fis = new FileInputStream(jarPath); FileOutputStream fos = new FileOutputStream(encryptedJarPath); CipherOutputStream cos = new CipherOutputStream(fos, cipher)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { cos.write(buffer, 0, bytesRead); } } } }
此程式生成了加密後的 JAR 檔案 app_encrypted.jar
,同時生成了 AES 金鑰。該金鑰需要妥善儲存在安全的金鑰管理系統中。
2. 動態解密和載入 JAR 檔案
爲了在程式啟動時解密並載入 JAR,我們自定義 ClassLoader
來實現動態解密並載入類。在 Spring Boot 專案的啟動類中,編寫如下程式碼。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.security.Key; import java.util.Base64; import java.util.jar.JarInputStream; import java.util.jar.JarEntry; @SpringBootApplication public class SecureBootApplication { public static void main(String[] args) throws Exception { // 從金鑰管理系統獲取解密金鑰(此處硬編碼,僅為示例) String secretKey = "從金鑰管理系統獲取的AES金鑰"; // 解密並載入 JAR File encryptedJar = new File("app_encrypted.jar"); SecureClassLoader loader = new SecureClassLoader(secretKey, encryptedJar); // 設定自定義 ClassLoader 並啟動 Spring Boot 應用 Thread.currentThread().setContextClassLoader(loader); SpringApplication.run(SecureBootApplication.class, args); } // 自定義 ClassLoader 實現解密邏輯 static class SecureClassLoader extends ClassLoader { private final Key secretKey; private final File encryptedJarFile; public SecureClassLoader(String base64Key, File encryptedJarFile) throws Exception { this.secretKey = new SecretKeySpec(Base64.getDecoder().decode(base64Key), "AES"); this.encryptedJarFile = encryptedJarFile; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try (JarInputStream jarIn = new JarInputStream(new FileInputStream(encryptedJarFile))) { JarEntry entry; Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, secretKey); while ((entry = jarIn.getNextJarEntry()) != null) { if (entry.getName().replace("/", ".").equals(name + ".class")) { byte[] encryptedClassData = jarIn.readAllBytes(); byte[] classData = cipher.doFinal(encryptedClassData); return defineClass(name, classData, 0, classData.length); } } } catch (Exception e) { throw new ClassNotFoundException("Class " + name + " not found or decryption failed", e); } throw new ClassNotFoundException("Class " + name + " not found"); } } }
3. 安全執行和驗證
此時,我們完成了加密 JAR 檔案的動態解密和載入。完整的專案結構如下:
app.jar
:原始 JAR 檔案。app_encrypted.jar
:加密後的 JAR 檔案。JarEncryptor
:用於加密 JAR 檔案的工具類。SecureBootApplication
:Spring Boot 專案啟動類,包含自定義ClassLoader
。
注意事項
金鑰管理:解密金鑰應當儲存在安全的金鑰管理系統中,避免直接在程式碼中硬編碼。
效能問題:由於自定義
ClassLoader
需要解密每個類檔案,因此首次載入可能存在效能開銷。防護加強:可結合其他技術手段(如程式碼混淆、環境監測)提升安全性。
透過上述方案,我們有效防止了核心程式碼被反編譯,即使攻擊者獲得加密的 JAR 檔案,仍然無法直接檢視到原始碼內容。
作者:一隻愛擼貓的程式猿
連結:https://juejin.cn/post/7435184443596587027