赞
踩
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包。
选择 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 .
以上表示加密完成,下面介绍如下运行加密后的文件。
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
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
源码: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>
注意事项
<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可以。
1、正常jar包,通过反编译工具可直接查看,如下:
生成未加密的jar包,通过反编译工具可以直接查看源码,没有任何安全错误,容易被窃取。
2、通过xjar进行加密jar包,如下:
可以看到,我们查看加密后的class文件,直接提示class文件无效,不能直接查看源码了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。