在SpringBoot 项目简单实现一个 Jar 包加密,防止反编译

在现实场景中,某金融公司开发了一个基于 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 文件进行加密。

java 复制代码
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 项目的启动类中,编写如下代码。

java 复制代码
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 文件,仍然无法直接查看到源码内容。

相关推荐
网络安全-杰克11 分钟前
网络安全常见面试题--含答案
安全·web安全
A_cot1 小时前
一篇Spring Boot 笔记
java·spring boot·笔记·后端·mysql·spring·maven
@sinner2 小时前
【Spring Boot 入门五】Spring Boot中的测试 - 确保应用质量
spring boot·后端·log4j
江梦寻3 小时前
解决SLF4J: Class path contains multiple SLF4J bindings问题
java·开发语言·spring boot·后端·spring·intellij-idea·idea
鸡鸭扣3 小时前
springboot苍穹外卖实战:五、公共字段自动填充(aop切面实现)+新增菜品功能+oss
java·spring boot·后端
2401_857636395 小时前
计算机课程管理平台:Spring Boot与工程认证的结合
java·spring boot·后端
冰水°8 小时前
2.5_XXE(XML外部实体注入)
xml·安全·网络安全·xml外部实体注入·外部实体注入·xml漏洞
2401_857610039 小时前
多维视角下的知识管理:Spring Boot应用
java·spring boot·后端
代码小鑫9 小时前
A027-基于Spring Boot的农事管理系统
java·开发语言·数据库·spring boot·后端·毕业设计
CoderJia程序员甲11 小时前
重学SpringBoot3-整合 Elasticsearch 8.x (三)使用Repository
java·大数据·spring boot·elasticsearch