加密Jar包实战-classfinal
摘要:ClassFinal-maven-plugin是一款Java字节码加密工具(已暂停维护),通过JNI技术实现类文件混淆和AES加密。使用时需在pom.xml配置插件,指定加密包名和配置文件。加密后方法体被清空,仅保留方法签名,需通过javaagent启动解密(支持密码/无密码模式)。其工作原理包括混淆重命名、AES加密、运行时内存解密三个阶段,利用JavaAgent技术实现动态解密。注意该插
ClassFinal-maven-plugin插件是一个用于加密Java字节码的工具
开源地址官网:ClassFinal: Java字节码加密工具 (目前已经暂停维护了)
使用方法
在pom文件中直接加入插件
<plugin>
<!--
1. 加密后,方法体被清空,保留方法参数、注解等信息.主要兼容swagger文档注解扫描
2. 方法体被清空后,反编译只能看到方法名和注解,看不到方法体的具体内容
3. 加密后的项目需要设置javaagent来启动,启动过程中解密class,完全内存解密,不留下任何解密后的文件
4. 启动加密后的jar,生成xxx-encrypted.jar,这个就是加密后的jar文件,加密后不可直接执行
5. 无密码启动方式,java -javaagent:xxx-encrypted.jar -jar xxx-encrypted.jar
6. 有密码启动方式,java -javaagent:xxx-encrypted.jar='-pwd= 密码' -jar xxx-encrypted.jar
-->
<groupId>net.roseboy</groupId>
<artifactId>classfinal-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<password>#</password><!-- #表示启动时不需要密码,事实上对于代码混淆来说,这个密码没什么用,它只是一个启动密码 -->
<excludes>org.spring</excludes>
<packages>${groupId}</packages><!-- 加密的包名,多个包用逗号分开 -->
<cfgfiles>application.properties,application.yml,application-dev.yml</cfgfiles><!-- 加密的配置文件,多个包用逗号分开 -->
<libjars>hutool-all.jar</libjars> <!-- jar包lib下面要加密的jar依赖文件,多个包用逗号分开 -->
<!-- <code>xxxxx</code> <!– 指定机器启动,机器码 –>-->
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>classFinal</goal>
</goals>
</execution>
</executions>
</plugin>
然后直接打包
<!--加密打包之后pom.xml会被删除,不用担心在jar包里找到此密码-->
使用命令下方命令运行,并且可以在控制台输入密码
java -javaagent:xxx-encrypted.jar -jar xxx-encrypted.jar
结束
工作原理
ClassFinal-maven-plugin插件通过调用Java Native Interface(JNI)实现对Java字节码的加密。具体来说,它会在编译阶段对类文件进行混淆和加密,然后在运行时动态解密这些类文件。
首先,插件会遍历项目中的所有类文件,并对其进行混淆处理。混淆过程包括重命名类名、方法名、字段名以及改变控制流结构等,目的是使反编译后的代码难以阅读。
接着,插件会对混淆后的类文件进行加密处理。加密过程采用了一种名为CFProtect的算法,该算法基于AES加密标准,具有较高的安全性。
加密后的类文件存储为二进制格式,不能直接被Java虚拟机加载。当class被classloader加载时,真正的方法体会被解密注入。
运用的是Java Agent可以去实现字节码插桩、动态跟踪分析。比如skywalking、arms等就是Java Agent技术。
最后,插件会生成一个代理模块(agent module),该模块负责在应用程序启动时加载,并负责解密加密的类文件。代理模块采用JVMTI(Java Virtual Machine Tool Interface)技术实现,可以在运行时监控和控制Java虚拟机的行为。
加密效果
1.加密后,方法体被清空,保留方法参数、注解等信息.主要兼容swagger文档注解扫描
2.方法体被清空后,反编译只能看到方法名和注解,看不到方法体的具体内容
3.加密后的项目需要设置javaagent来启动,启动过程中解密class,完全内存解密,不留下任何解密后的文件
4.启动加密后的jar,生成xxx-encrypted.jar,这个就是加密后的jar文件,加密后不可直接执行
5.无密码启动方式,java-javaagent:xxx-encrypted.jar-jarxxx-encrypted.jar
6.有密码启动方式,java-javaagent:xxx-encrypted.jar='-pwd=密码'-jarxxx-encrypted.jar
插件标签
<plugins>元素的作用是: 给出构建过程中用到的插件.
- <groupId> : 项目或者组织的唯一标识
- <artifactId> 项目的通用名称
- <version> 项目的版本
- <extensions> 是否加载该插件的扩展,默认为false
- <inherited> 该插件的<configuration>中的配置是否可以被继承,默认为true
- <configuration> 该插件所需要的特色配置,在父子项目项目之间可以覆盖或被合并
- <dependencies> 该插件所特有的依赖类库
- <executions> 该插件的某个goal(一个插件可能有多个goal)的执行方式.一个<executions>有如下设置
- <id> 唯一标识
- <goals> 要执行的插件的goal.这个是可以有多个的
- <phase> 插件的goal要嵌入到Maven的phase中执行
- <inherited> 该execution是否可被子项目继承
- <configuration> 该execution的其他配置参数
不支持SpringBoot3.2.0及以上
会产生报错如下:
Startup failed, invalid password.

影响源码如下
//验证密码,jar包是才验证
byte[] passHash = JarDecryptor.readEncryptedFile(new File(JarUtils.getRootPath(null)), Const.CONFIG_PASSHASH);
if (passHash != null) {
char[] p1 = StrUtils.toChars(passHash);
char[] p2 = EncryptUtils.md5(StrUtils.merger(pwd, EncryptUtils.SALT));
p2 = EncryptUtils.md5(StrUtils.merger(EncryptUtils.SALT, p2));
if (!StrUtils.equal(p1, p2)) {
Log.println("\nERROR: Startup failed, invalid password.\n");
System.exit(0);
}
}
/**
* 获取class运行的classes目录或所在的jar包目录
*
* @return 路径字符串
*/
public static String getRootPath(String path) {
if (path == null) {
path = JarUtils.class.getResource("").getPath();
}
try {
path = java.net.URLDecoder.decode(path, "utf-8");
} catch (UnsupportedEncodingException e) {
}
if (path.startsWith("jar:") || path.startsWith("war:")) {
path = path.substring(4);
}
if (path.startsWith("file:")) {
path = path.substring(5);
}
//没解压的war包
if (path.contains("*")) {
return path.substring(0, path.indexOf("*"));
}
//war包解压后的WEB-INF
else if (path.contains("WEB-INF")) {
return path.substring(0, path.indexOf("WEB-INF"));
}
//jar
else if (path.contains("!")) {
return path.substring(0, path.indexOf("!"));
}
//普通jar/war
else if (path.endsWith(".jar") || path.endsWith(".war")) {
return path;
}
//no
else if (path.contains("/classes/")) {
return path.substring(0, path.indexOf("/classes/") + 9);
}
return null;
}
jar包目录含义如下:
BOOT-INF/classes:目录存放应用编译后的class文件。
BOOT-INF/lib:目录存放应用依赖的第三方JAR包文件。
META-INF:目录存放应用打包信息(Maven坐标、pom文件)和MANIFEST.MF文件。
org:目录存放SpringBoot相关class文件Jar包内容详细分析
原因分析

版本适配
源码了解


更多推荐



所有评论(0)