在现实场景中,某金融公司开发了一个基于 Spring Boot 的应用程序,该程序用于处理金融数据,具有高敏感性。为了防止该程序的核心代码(如数据加密、交易算法等)被反编译或篡改,公司希望通过加密 JAR 包并在运行时解密的方式来保护核心代码。
为实现这个需求,我们设计了一个简单的加密和解密方案,其中:
- 使用对称加密算法(AES)加密 JAR 文件。
- 在程序启动时动态解密 JAR 文件,并加载解密后的类。
- 将解密密钥存储在安全的密钥管理系统中(例如 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 文件,仍然无法直接查看到源码内容。