这里写自定义目录标题
xjar安全加密运行工具
GitHub: https://github.com/core-lib/xjar
Spring Boot JAR 安全加密运行工具, 同时支持的原生JAR.
基于对JAR包内资源的加密以及拓展ClassLoader来构建的一套程序加密启动, 动态解密运行的方案, 避免源码泄露以及反编译.
功能特性
无代码侵入, 只需要把编译好的JAR包通过工具加密即可.
完全内存解密, 降低源码以及字节码泄露或反编译的风险.
支持所有JDK内置加解密算法.
可选择需要加解密的字节码或其他资源文件.
支持Maven插件, 加密更加便捷.
动态生成Go启动器, 保护密码不泄露.
创建项目
源码: https://gitee.com/starsky20/starsky-xjar.git
1、使用idea创建一个java项目,然后引入xjar需要的jar包。
打开idea, 点击菜单 File-》new-》Project-》选择maven项目-》
创建完成如下:
编写代码
pom.xml 引入xjar依赖包:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>demo-xjar</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- 添加 XJar 依赖 -->
<dependency>
<groupId>com.github.core-lib</groupId>
<artifactId>xjar</artifactId>
<version>4.0.2</version>
<!-- <scope>test</scope> -->
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
<!-- 设置 jitpack.io 仓库 -->
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
</project>
2、编写代码,生成加密文件
参考文档:https://www.cnblogs.com/-flq/p/14297274.html
package com.starsky.xjar;
import io.xjar.XKit;
import io.xjar.boot.XBoot;
import io.xjar.key.XKey;
import org.apache.commons.lang.StringUtils;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @desc:xjar 加密工具,针对java开发项目jar包进行加密,jar加密后,大小会翻倍,同时swagger无法访问,接口暂时未发现问题
* @author: wangsh
* @time: 2021/4/15 9:34
*/
public class XjarEncryUtil {
public static void main(String[] args) {
createJFrame();
}
/**
* 创建面板,这个类似于 HTML 的 div 标签,我们可以创建多个面板并在 JFrame 中指定位置,面板中我们可以添加文本字段,按钮及其他组件。
*/
public static void createJFrame() {
// 创建 JFrame 实例
JFrame frame = new JFrame("XJar加密,防止反编译");
// 设置窗口大小
frame.setSize(600, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//创建面板,这个类似于 HTML 的 div 标签,可以创建多个面板并在 JFrame 中指定位置,
// 面板中可以添加文本字段,按钮及其他组件。
JPanel panel = new JPanel();
// 添加面板
frame.add(panel);
//调用用户定义的方法并添加组件到面板
addComponents(panel);
// 设置界面可见
frame.setVisible(true);
}
/**
* 调用用户定义的方法并添加组件到面板
*/
private static void addComponents(JPanel panel) {
//布局部分我们这边不多做介绍,这边设置布局为 null
panel.setLayout(null);
// 创建 需要加密的jar JLabel
JLabel fromLabel = new JLabel("选择加密的jar:");
fromLabel.setBounds(10, 20, 120, 30);
panel.add(fromLabel);
//用于记录未加密jar的文本域
final JTextField fromText = new JTextField(20);
fromText.setBounds(100, 20, 300, 30);
panel.add(fromText);
// 创建选择按钮
JButton fromButton = new JButton("选择");
fromButton.setBounds(420, 20, 80, 30);
panel.add(fromButton);
// 加密后jar要保存的位置
JLabel toLabel = new JLabel("选择保存位置:");
toLabel.setBounds(10, 50, 120, 30);
panel.add(toLabel);
//文本域用于记录保存路径
final JTextField toText = new JTextField(20);
toText.setBounds(100, 50, 300, 30);
panel.add(toText);
// 创建选择按钮
JButton toButton = new JButton("选择");
toButton.setBounds(420, 50, 80, 30);
panel.add(toButton);
// 输入密码的文本域
JLabel passwordLabel = new JLabel("加密密码:");
passwordLabel.setBounds(10, 80, 80, 30);
panel.add(passwordLabel);
//这个类似用于输入的文本域,但是输入的信息会以点号代替,用于包含密码的安全性
final JPasswordField passwordText = new JPasswordField(20);
passwordText.setBounds(100, 80, 300, 30);
panel.add(passwordText);
// 创建开始按钮
JButton startButton = new JButton("加密");
startButton.setBounds(100, 110, 80, 30);
panel.add(startButton);
// 创建开始按钮
JButton endButton = new JButton("解密");
endButton.setBounds(200, 110, 80, 30);
panel.add(endButton);
//日志显示框
final JTextArea textArea = new JTextArea("");
textArea.setBounds(10, 150, 550, 150);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
panel.add(textArea);
//选择jar按钮监听事件
fromButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { //按钮点击事件
JFileChooser chooser = new JFileChooser(); //设置选择器
chooser.setMultiSelectionEnabled(false); //设为单选
int returnVal = chooser.showOpenDialog(null); //是否打开文件选择框
if (returnVal == JFileChooser.APPROVE_OPTION) { //如果符合文件类型
String filepath = chooser.getSelectedFile().getAbsolutePath(); //获取绝对路径
if (!".jar".equals(filepath.substring(filepath.length() - 4))) {
JOptionPane.showMessageDialog(null, "文件格式不正确,请选择jar文件!", "文件格式错误", JOptionPane.ERROR_MESSAGE);
} else {
fromText.setText(filepath);
}
System.out.println(filepath);
}
}
});
//选择加密后jar保存路径按钮监听事件
toButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { //按钮点击事件
JFileChooser chooser = new JFileChooser(); //设置选择器
chooser.setMultiSelectionEnabled(false); //设为单选
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); //设置只选目录
chooser.setDialogTitle("选择加密后jar保存位置");
int returnVal = chooser.showOpenDialog(null); //是否打开文件选择框
if (returnVal == JFileChooser.APPROVE_OPTION) { //如果符合文件类型
String filepath = chooser.getSelectedFile().getAbsolutePath(); //获取绝对路径
toText.setText(filepath);
System.out.println(filepath);
}
}
});
//选择开始径按钮的监听事件
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String fromJarPath = fromText.getText();
String toJarPath = toText.getText() + "\\encrypt" + getNowDateTime() + ".jar";
String password = new String(passwordText.getPassword());
System.out.println("fromJarPath=" + fromJarPath);
System.out.println("toJarPath=" + toJarPath);
System.out.println("password=" + password);
if (StringUtils.isEmpty(fromJarPath)) {
JOptionPane.showMessageDialog(null, "jar文件不能为空!", "jar文件不能为空!", JOptionPane.ERROR_MESSAGE);
return;
}
if (StringUtils.isEmpty(toJarPath)) {
JOptionPane.showMessageDialog(null, "保存路径不能为空!", "保存路径不能为空!", JOptionPane.ERROR_MESSAGE);
return;
}
if (StringUtils.isEmpty(toJarPath)) {
JOptionPane.showMessageDialog(null, "请输入加密的密码!", "密码不能为空!", JOptionPane.ERROR_MESSAGE);
return;
}
//打印输入日志
StringBuilder builder = new StringBuilder();
builder.append("fromJarPath=" + fromJarPath + "\n");
builder.append("toJarPath=" + toJarPath + "\n");
textArea.setText(builder.toString());
//开始加密文件
encryptJar(fromJarPath, toJarPath, password);
textArea.append("jar加密成功!\n请测试接口是否正常(注意:Swagger不可用)");
}
});
//选择开始径按钮的监听事件
endButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String fromJarPath = fromText.getText();
String toJarPath = toText.getText() + "\\encrypt" + getNowDateTime() + ".jar";
String password = new String(passwordText.getPassword());
System.out.println("fromJarPath=" + fromJarPath);
System.out.println("toJarPath=" + toJarPath);
System.out.println("password=" + password);
if (StringUtils.isEmpty(fromJarPath)) {
JOptionPane.showMessageDialog(null, "jar文件不能为空!", "jar文件不能为空!", JOptionPane.ERROR_MESSAGE);
return;
}
if (StringUtils.isEmpty(toJarPath)) {
JOptionPane.showMessageDialog(null, "保存路径不能为空!", "保存路径不能为空!", JOptionPane.ERROR_MESSAGE);
return;
}
if (StringUtils.isEmpty(toJarPath)) {
JOptionPane.showMessageDialog(null, "请输入加密的密码!", "密码不能为空!", JOptionPane.ERROR_MESSAGE);
return;
}
//打印输入日志
StringBuilder builder = new StringBuilder();
builder.append("fromJarPath=" + fromJarPath + "\n");
builder.append("toJarPath=" + toJarPath + "\n");
textArea.setText(builder.toString());
//开始加密文件
decryptJar(fromJarPath, toJarPath, password);
textArea.append("jar加密成功!\n请测试接口是否正常(注意:Swagger不可用)");
}
});
}
/**
* 获取当前格式化时间的方法
*/
private static String getNowDateTime() {
String dateNow = "";
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
Date date = new Date();
dateNow = format.format(date);
return dateNow;
}
/**
* jar包加密,防止反编译
* 此编译方式在运行jar包时需要输入密码
* 运行方式一 :
* // 命令行运行JAR 然后在提示输入密码的时候输入密码后按回车即可正常启动
* java -jar /path/to/encrypted.jar
* 运行方式二:
* // 也可以通过传参的方式直接启动,不太推荐这种方式,因为泄露的可能性更大!
* java -jar /path/to/encrypted.jar --xjar.password=PASSWORD
* 运行方式三:
* // 对于 nohup 或 javaw 这种后台启动方式,无法使用控制台来输入密码,推荐使用指定密钥文件的方式启动
* nohup java -jar /path/to/encrypted.jar --xjar.keyfile=/path/to/xjar.key
* xjar.key 文件说明:
* 格式:
* password: PASSWORD
* algorithm: ALGORITHM
* keysize: KEYSIZE
* ivsize: IVSIZE
* hold: HOLD
* 参数说明:
* password 密码 无 密码字符串
* algorithm 密钥算法 AES 支持JDK所有内置算法,如AES / DES ...
* keysize 密钥长度 128 根据不同的算法选取不同的密钥长度。
* ivsize 向量长度 128 根据不同的算法选取不同的向量长度。
* hold 是否保留 false 读取后是否保留密钥文件。
*
* @param fromJarPath 需要加密的jar
* @param toJarPath 加密后的jar
* @param password 加密密码
*/
public static void encryptJar(String fromJarPath, String toJarPath, String password) {
try {
// Spring-Boot Jar包加密
XKey xKey = XKit.key(password);
XBoot.encrypt(fromJarPath, toJarPath, xKey);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* jar包解密
*
* @param fromJarPath 已通过Xjar加密的jar文件路径
* @param toJarPath 解密后的jar文件
* @param password 密码
*/
public static void decryptJar(String fromJarPath, String toJarPath, String password) {
try {
XKey xKey = XKit.key(password);
XBoot.decrypt(fromJarPath, toJarPath, xKey);
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上编写完成后可以直接运行main,结果如下:
以上是通过工具直接运行结果,如果我们需要将项目放在其他地方使用,很不方便,故需要将项目打包成jar包运行,下面介绍通过idea将maven项目打包成可运行的jar包。
idea将maven项目打包成可运行的jar包
选择 File-》Project Structure
然后再弹出框中选择 Atifacts -》“+“ -》from modules with dependcents
这里选择main函数运行java类,manifest.mf 文件目录这里 需要将src目录后面的文件夹取掉, 点击ok确认后如下:
以上表示jar包基本配置完成,下来就开始生产jar包。
选择build-》Artifacts
选择jar包-》build即可生成jar包。build是指首次编译,rebuild是首次编译后使用的, 编译后生产jar包如下:
进入到jar包目录
通过cmd命令行运行 java -jar xxx.jar , 如下:
运行后,在弹出框选择需要加密的jar文件、输出加密目录、加密密码。
加密后再d盘生产一个加密文件、xjar.go、xjar_agentable.go .
以上表示加密完成,下面介绍如下运行加密后的文件。
运行加密jar文件
1、安装编译脚本
安装go语言环境下载地址:https://studygolang.com/dl
我这里使用windows开发,下载go1.16.3.windows-amd64.msi , 下载完成直接安装即可。
安装后验证是否安装成功,打开cmd,执行 go env
2、进入到生成加密文件目录,执行编译
go build xjar.go
编译后生产一个xjar.exe文件,后面需要通过该文件运行密码jar包。
通过步骤2加密成功后XJar会在输出的JAR包同目录下生成一个名为 xjar.go 的的Go启动器源码文件.
将 xjar.go 在不同的平台进行编译即可得到不同平台的启动器可执行文件, 其中Windows下文件名为 xjar.exe 而Linux下为 xjar.
用于编译的机器需要安装 Go 环境, 用于运行的机器则可不必安装 Go 环境, 具体安装教程请自行搜索.
由于启动器自带JAR包防篡改校验, 故启动器无法通用, 即便密码相同也不行.
- 启动运行
xjar java -jar /path/to/encrypted.jar
xjar javaw -jar /path/to/encrypted.jar
nohup xjar java -jar /path/to/encrypted.jar
centos 安装go
1.下载二进制包
wget https://dl.google.com/go/go1.11.4.linux-amd64.tar.gz
2.将下载的二进制包解压至 /usr/local目录
tar -C /usr/local -xzf go1.4.linux-amd64.tar.gz
3.打开/etc/profile文件
sudo vim /etc/profile
4.将 /usr/local/go/bin 目录添加至PATH环境变量,在/etc/profile文件底部添加如下行
export PATH=$PATH:/usr/local/go/bin
5.应用修改的配置
source /etc/profile
如上则环境配置完成,执行测试看是否安装成功
输入 go version能正常显示版本号,正常安装。
以上表示go安装成功
2、将加密jar包、xjar.go、xjar_agentable.go 上传到服务器。
执行命令: ./xjar java -jar encrypt20210415162050.jar
springboot项目集成xjar 加密工具
源码:https://gitee.com/starsky20/springboot-xjar.git
通过idea创建一个springboot项目,然后引入xjar依赖包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>starsky-xjar</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>starsky-xjar</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring.boot.version>2.0.7</spring.boot.version>
<spring.boot.admin.version>2.0.3</spring.boot.admin.version>
<spring.cloud.version>Finchley.SR2</spring.cloud.version>
<spring.cloud.alibaba.version>2.0.3.RELEASE</spring.cloud.alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 添加 XJar 依赖 -->
<dependency>
<groupId>com.github.core-lib</groupId>
<artifactId>xjar</artifactId>
<version>4.0.2</version>
<!-- <scope>test</scope> -->
</dependency>
<dependency>
<groupId>com.github.core-lib</groupId>
<artifactId>loadkit</artifactId>
<version>v1.0.0</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
<plugin>
<groupId>com.github.core-lib</groupId>
<artifactId>xjar-maven-plugin</artifactId>
<version>4.0.2</version>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
<!--可以改成 install-->
<phase>package</phase>
<configuration>
<password>io.xjar</password>
<!-- 需要加密的资源路径表达式 -->
<includes>
<include>com/starsky/**</include>
<include>mapper/*Mapper.xml</include>
<include>config/**</include>
</includes>
<!-- 无需加密的资源路径表达式 -->
<excludes>
<exclude>static/**</exclude>
<exclude>templates/**</exclude>
<exclude>resources/**</exclude>
<exclude>META-INF/resources/**</exclude>
</excludes>
<!-- 目标jar存放目录及目标jar名称,也可以用表达式(参考官网) -->
<targetJar>web-app.jar</targetJar>
<!-- <targetJar>你的jar包名字</targetJar>-->
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<!-- 资源目录 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
<!-- 资源根目录排除各环境的配置,防止在生成目录中多余其它目录 -->
<excludes>
<exclude>application.properties</exclude>
<exclude>bootstrap-dev.yml</exclude>
<exclude>bootstrap-prod.yml</exclude>
<exclude>bootstrap-test.yml</exclude>
</excludes>
<filtering>true</filtering>
</resource>
<!--激活指定文件-->
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>application.properties</include>
</includes>
</resource>
<!--打包java目录-->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.*</include>
</includes>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
<filtering>true</filtering>
</resource>
</resources>
</build>
<!-- 设置 jitpack.io 仓库 -->
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<!-- 设置 jitpack.io 插件仓库 -->
<pluginRepositories>
<pluginRepository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</pluginRepository>
</pluginRepositories>
</project>
注意事项
- 不兼容 spring-boot-maven-plugin 的 executable = true 以及 embeddedLaunchScript
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- 需要将executable和embeddedLaunchScript参数删除, 目前还不能支持对该模式Jar的加密!后面可能会支持该方式的打包.
<configuration>
<executable>true</executable>
<embeddedLaunchScript>...</embeddedLaunchScript>
</configuration>
-->
</plugin>
插件集成
Maven项目可通过集成 xjar-maven-plugin 以免去每次加密都要执行一次上述的代码, 随着Maven构建自动生成加密后的JAR和Go启动器源码文件.
xjar-maven-plugin GitHub: https://github.com/core-lib/xjar-maven-plugin
对于Spring Boot 项目或模块, 该插件要后于 spring-boot-maven-plugin 插件执行, 有两种方式:
将插件放置于 spring-boot-maven-plugin 的后面, 因为其插件的默认 phase 也是 package
将插件的 phase 设置为 install(默认值为:package), 打包命令采用 mvn clean install
也可以通过Maven命令执行
mvn xjar:build -Dxjar.password=io.xjar
mvn xjar:build -Dxjar.password=io.xjar -Dxjar.targetDir=/directory/to/save/target.xjar
但通常情况下是让XJar插件绑定到指定的phase中自动执行, 这样就能在项目构建的时候自动构建出加密的包.
mvn clean package -Dxjar.password=io.xjar
mvn clean install -Dxjar.password=io.xjar -Dxjar.targetDir=/directory/to/save/target.xjar
强烈建议
强烈建议不要在 pom.xml 的 xjar-maven-plugin 配置中写上密码,这样会导致打包出来的 xjar 包中的 pom.xml 文件保留着密码,极其容易暴露密码!强烈推荐通过 mvn 命令来指定加密密钥!
打包运行
进入到项目目录,执行命令:
mvn clean package -Dxjar.password=123456789 -Dmaven.test.skip=true
这里打包排除test测试相关代码不打包。
打包完成如下:
进入到打包目录,执行编译 go build xjar.go , 执行后会生成一个xjar.exe文件
通过cmd命令行运行如下:
针对springboot项目加密后,swagger接口 不能通过页面访问,加密后jar包体积大约增加2倍。
至于项目部署是否需要加密处理,根据实际情况进行调整。
实际部署项目为例测试:
1、未加密swagger接口:
未加密jar包,访问swagger接口,可以查看所有接口。
2、加密后,swagger接口访问测试:
可以看到,加密直接访问swagger接口没有任何结果。
3、通过gateway网关测试swagger接口:
通过gateway访问加密服务swagger可以。
通过反编译工具查看加密的jar包
1、正常jar包,通过反编译工具可直接查看,如下:
生成未加密的jar包,通过反编译工具可以直接查看源码,没有任何安全错误,容易被窃取。
2、通过xjar进行加密jar包,如下:
可以看到,我们查看加密后的class文件,直接提示class文件无效,不能直接查看源码了。