⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7447872749756989440 「考虑考虑」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

在现实场景中,某金融公司开发了一个基于 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 文件,仍然无法直接查看到源码内容。

文章目录
  1. 1. 实现步骤
    1. 1.1. 1. 加密原始 JAR 文件
    2. 1.2. 2. 动态解密和加载 JAR 文件
    3. 1.3. 3. 安全运行和验证
  2. 2. 注意事项