切換語言為:簡體

在SpringBoot 專案簡單實現一個 Jar 包加密,防止反編譯

  • 爱糖宝
  • 2024-11-10
  • 2037
  • 0
  • 0

在現實場景中,某金融公司開發了一個基於 Spring Boot 的應用程式,該程式用於處理金融資料,具有高敏感性。爲了防止該程式的核心程式碼(如資料加密、交易演算法等)被反編譯或篡改,公司希望透過加密 JAR 包並在執行時解密的方式來保護核心程式碼。

為實現這個需求,我們設計了一個簡單的加密和解密方案,其中:

  1. 使用對稱加密演算法(AES)加密 JAR 檔案。

  2. 在程式啟動時動態解密 JAR 檔案,並載入解密後的類。

  3. 將解密金鑰儲存在安全的金鑰管理系統中(例如 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

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.