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

相关推荐
编程乐学(Arfan开发工程师)42 分钟前
42、响应处理-【源码分析】-浏览器与PostMan内容协商完全适配
java·spring boot·后端·测试工具·lua·postman
珹洺1 小时前
数据库系统概论(十七)超详细讲解数据库规范化与五大范式(从函数依赖到多值依赖,再到五大范式,附带例题,表格,知识图谱对比带你一步步掌握)
java·数据库·sql·安全·oracle
javadaydayup1 小时前
明明说好的国际化,可你却还是返回了中文
spring boot·后端·spring
eternal__day2 小时前
Spring Cloud 多机部署与负载均衡实战详解
java·spring boot·后端·spring cloud·负载均衡
恰薯条的屑海鸥2 小时前
零基础在实践中学习网络安全-皮卡丘靶场(第十期-Over Permission 模块)
学习·安全·web安全·渗透测试·网络安全学习
程序员秘密基地2 小时前
基于vscode,idea,java,html,css,vue,echart,maven,springboot,mysql数据库,在线考试系统
java·vue.js·spring boot·spring·web app
风象南3 小时前
SpringBoot的5种日志输出规范策略
java·spring boot·后端
梁云亮3 小时前
Spring Boot + Thymeleaf 防重复提交
spring boot·防抖·防重复提交
XMYX-010 小时前
Spring Boot + Prometheus 实现应用监控(基于 Actuator 和 Micrometer)
spring boot·后端·prometheus
@yanyu66612 小时前
springboot实现查询学生
java·spring boot·后端