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> &lt;!&ndash; 指定机器启动,机器码 &ndash;&gt;-->
    </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包内容详细分析

原因分析

版本适配

点击了解版本是否适配

源码了解

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐