代码加固:使用ClassFinal实现对代码加密混淆

一、背景

在部分软件项目中,存在需要将业务代码提供给第三方使用的场景,比如提供一个Java Jar SDK包给第三方引用,提供一个Java Jar启动包部署到第三方环境。如果我们不想让第三方通过逆向工程反编译程序包,防止代码泄露,我们一般需要对程序包的代码做加密或者混淆。

本文通过使用ClassFinal对项目中的业务代码进行加密打包,从而实现对程序包的代码加固和反逆向破解。

二、组件介绍

ClassFinal

ClassFinal是一款java class文件安全加密工具,支持直接加密jar包或war包,无需修改任何项目代码,兼容spring-framework;可避免源码泄漏或字节码被反编译。

功能特性:

1.无需修改原项目代码,只要把编译好的jar/war包用本工具加密即可。
2.运行加密项目时,无需求修改tomcat,spring等源代码。
3.支持普通jar包、springboot jar包以及普通java web项目编译的war包。
4.支持spring framework、swagger等需要在启动过程中扫描注解或生成字节码的框架。
5.支持maven插件,添加插件后在打包过程中自动加密。
6.支持加密WEB-INF/lib或BOOT-INF/lib下的依赖jar包。
7.支持绑定机器,项目加密后只能在特定机器运行。
8.支持加密springboot的配置文件。

GItHub地址:https://github.com/roseboy/classfinal

三、项目集成

下文以一个采用Maven打包的SpringBoot多子项目工程为例,介绍如何使用ClassFinal对业务代码进行加密。(ClassFinal也支持对单独的程序包,比如jar,通过命令行的方式进行代码加密)

项目结构如下图所示:

该项目包含yhaf-admin-base-api、yhaf-admin-base-servic、yhaf-admin-base-security-core、yhaf-admin-base-security、yhaf-admin-base-web等多个子模块。yhaf-admin-base-web子模块依赖其他子模块,并最终通过maven打包出Jar运行包。

我们想要实现的目标是对最终交付的Jar运行包加密yhaf-admin-base-api、yhaf-admin-base-servic、yhaf-admin-base-security-core、yhaf-admin-base-security、yhaf-admin-base-web等多个子模块的业务代码。

1.使用ClassFinal对业务代码加密

首先,在子模块yhaf-admin-base-web的pom文件引入ClassFinal插件。

<build>
    <plugins>
        <!--springBoot项目打包插件-->
        <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
           <configuration>
              <executable>true</executable>
           </configuration>
        </plugin>
        <!--classfinal项目打包插件-->
        <plugin>
            <groupId>net.roseboy</groupId>
            <artifactId>classfinal-maven-plugin</artifactId>
            <version>1.2.1</version>
           <configuration>
              <!--加密密码,如果是#号,则使用无密码模式加密,加密打包之后pom.xml会被删除,不用担心在jar包里找到此密码-->
              <password>shemg</password>
              <!--加密的包名(可为空,多个用","分割)-->
              <packages>com.yhtech.backend</packages>
              <!--需要加密的配置文件,一般是classes目录下的yml或properties文件(可为空,多个用","分割)-->
              <cfgfiles>application.yml</cfgfiles>
              <!--排除的类名(可为空,多个用","分割)-->
              <excludes>org.spring</excludes>
              <!--jar/war包lib下要加密jar文件名(可为空,多个用","分割)-->
              <libjars>
                 yhaf-admin-base-api-1.6.6-SNAPSHOT.jar,
                 yhaf-admin-base-service-1.6.6-SNAPSHOT.jar,
                 yhaf-admin-base-security-core-1.6.6-SNAPSHOT.jar,
                 yhaf-admin-base-security-1.6.6-SNAPSHOT.jar
              </libjars>
              <debug>true</debug>
           </configuration>
           <executions>
              <execution>
                 <phase>package</phase>
                 <goals>
                    <goal>classFinal</goal>
                 </goals>
              </execution>
           </executions>
        </plugin>
    </plugins>
</build>
参数说明
-file        加密的jar/war完整路径
-packages    加密的包名(可为空,多个用","分割)
-libjars     jar/war包lib下要加密jar文件名(可为空,多个用","分割)
-cfgfiles    需要加密的配置文件,一般是classes目录下的yml或properties文件(可为空,多个用","分割)
-exclude     排除的类名(可为空,多个用","分割)
-classpath   外部依赖的jar目录,例如/tomcat/lib(可为空,多个用","分割)
-pwd         加密密码,如果是#号,则使用无密码模式加密
-code        机器码,在绑定的机器生成,加密后只可在此机器上运行
-Y           无需确认,不加此参数会提示确认以上信息

上述配置中,libjars配置了对yhaf-admin-base-web引用的yhaf-admin-base-api、yhaf-admin-base-servic、yhaf-admin-base-security-core、yhaf-admin-base-security等子模块的jar包进行加密。packages配置了加密的代码包名,password配置了密码,如果是#则使用无密码加密方式,插件将自动生成一个随机密码进行加密。

使用mvn clean package命令进行打包,打包后将生成一个yhaf-admin-base-web-1.6.6-SNAPSHOT-encrypted.jar加密包

我们使用解压工具对yhaf-admin-base-web-1.6.6-SNAPSHOT-encrypted.jar进行解压,可以看到这个包是一个正常的springboot项目结构

正常springboot项目的class文件在\BOOT-INF\classes\路径下,我们在这个路径下查看业务代码,通过反编译工具可以看到,ClassFinal加密完代码后,原始的class文件并不会完全被加密,只是方法体被清空,保留方法参数、注解等信息,这是为了兼容spring,swagger等扫描注解的框架; 方法体被清空后,反编译者只能看到方法名和注解,看不到方法的具体内容;

真实的代码和配置被移动到\META-INF.classes\中加密保存

现在,使用正常的jar启动命令已无法正常启动程序,系统将提示:Startup failed, invalid password.
启动加密后的jar命令:

java -javaagent:yhaf-admin-base-web-1.6.6-SNAPSHOT-encrypted.jar -jar yhaf-admin-base-web-1.6.6-SNAPSHOT-encrypted.jar

如果密码不是设置为#,则在启动中会提示输入密码,输入正确密码后程序将正常启动

也可以使用如下命令进行启动:

java -javaagent:yhaf-admin-base-web-1.6.6-SNAPSHOT-encrypted.jar='-pwd shemg' -jar yhaf-admin-base-web-1.6.6-SNAPSHOT-encrypted.jar

//参数说明
// -pwd      加密项目的密码
// -pwdname  环境变量中密码的名字

ClassFinal使用javaagent机制,当class被classloader加载时,真正的方法体会被解密注入。

ClassFinal使用AES算法加密class文件,密码是保证不被破解的关键,请保存好密码,请勿泄漏。为了保证项目在运行时的安全,启动jvm时请加参数: -XX:+DisableAttachMechanism 。

更多详细的配置可查看GItHub地址:https://github.com/roseboy/classfinal

至此,已完成了原生的ClassFinal代码加密集成。

四、加固优化

原生的ClassFinal是开源的,插件会在启动的时候打印出ClassFinal Logo,同时解密代码直接暴露在根目录的net/roseboy包下,即使代码混淆了也很容易被人找到源码,通过阅读源码很容易被破解。

为了防止通过反编译解密代码,从而导致业务代码被解密,我们需要对ClassFinal的解密代码进行混淆。

我们可以Fork ClassFinal的源码,对其进行改造,比如修改加解密逻辑,隐藏解密代码包的路径,混淆加解密代码、取消控制台logo打印等,从而加大逆向工程的难度。

五、加固后项目集成

下文使用加固优化后的classfinal插件进行代码加密和混淆

在子模块yhaf-admin-base-web的pom文件引入ClassFinal插件。

<build>
   <plugins>
      <!--springBoot项目打包插件-->
      <plugin>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-maven-plugin</artifactId>
         <configuration>
            <executable>true</executable>
         </configuration>
      </plugin>
      <!--classfinal加密打包插件-->
      <plugin>
         <groupId>com.yhtech.classfinal</groupId>
         <artifactId>classfinal-maven-plugin</artifactId>
         <version>1.0.0-SNAPSHOT</version>
         <configuration>
            <!--加密密码,如果是#号,则使用无密码模式加密,加密打包之后pom.xml会被删除,不用担心在jar包里找到此密码-->
            <password>123</password>
            <!--加密的包名(可为空,多个用","分割)-->
            <packages>com.yhtech.backend</packages>
            <!--需要加密的配置文件,一般是classes目录下的yml或properties文件(可为空,多个用","分割)-->
            <cfgfiles>application.yml</cfgfiles>
            <!--排除的类名(可为空,多个用","分割)-->
            <excludes>org.spring</excludes>
            <!--jar/war包lib下要加密jar文件名(可为空,多个用","分割)-->
            <libjars>
               yhaf-admin-base-api-1.6.6-SNAPSHOT.jar,
               yhaf-admin-base-service-1.6.6-SNAPSHOT.jar,
               yhaf-admin-base-security-core-1.6.6-SNAPSHOT.jar,
               yhaf-admin-base-security-1.6.6-SNAPSHOT.jar
            </libjars>
            <debug>true</debug>
         </configuration>
         <executions>
            <execution>
               <phase>package</phase>
               <goals>
                  <goal>classFinal</goal>
               </goals>
            </execution>
         </executions>
      </plugin>
   </plugins>
</build>

其中,com.yhtech.classfinal:classfinal-maven-plugin:1.0.0-SNAPSHOT为我们优化后的插件。
使用mvn clean package命令进行打包,打包后将生成一个yhaf-admin-base-web-1.6.6-SNAPSHOT-final.jar加密混淆包。

六、使用命令行加密和混淆

1.ClassFinal使用命令行加密

对classFinal项目打包后,会获得classfinal-fatjar.jar程序包,也可以在官网下载到。

通过使用classfinal-fatjar执行下面命令可对jar、war包进行加密

java -jar classfinal-fatjar.jar -file yourpaoject.jar -libjars a.jar,b.jar -packages com.yourpackage,com.yourpackage2 -exclude com.yourpackage.Main -pwd 123456 -Y
参数说明
-file        加密的jar/war完整路径
-packages    加密的包名(可为空,多个用","分割)
-libjars     jar/war包lib下要加密jar文件名(可为空,多个用","分割)
-cfgfiles    需要加密的配置文件,一般是classes目录下的yml或properties文件(可为空,多个用","分割)
-exclude     排除的类名(可为空,多个用","分割)
-classpath   外部依赖的jar目录,例如/tomcat/lib(可为空,多个用","分割)
-pwd         加密密码,如果是#号,则使用无密码模式加密
-code        机器码,在绑定的机器生成,加密后只可在此机器上运行
-Y           无需确认,不加此参数会提示确认以上信息

以上示例是直接用参数执行,也可以直接执行 java -jar classfinal-fatjar.jar按照步骤提示输入信息完成加密。

结果: 生成 yourpaoject-encrypted.jar,这个就是加密后的jar文件;加密后的文件不可直接执行,需要配置javaagent。