文章目录

  • 一、背景
  • 二、代码混淆
  • 1.常用的混淆工具
  • 2.proguard实际配置
  • 三、配置文件加密
  • 四、jar包加密
  • 1.自定义编译
  • 2.通过已有jar直接加密
  • 五、前后对比效果
  • 1.混淆前
  • 2.混淆后
  • 3.加密后
  • 六、参考

一、背景

项目组核心代码模块部署于用户服务器上,另外一家公司获取了该服务器的root密码,常规的通过配置环境变量来进行数据库加密处理的方式,直接甩jar包到服务器的方式,极有可能导致数据泄露和代码泄露。

二、代码混淆

1.常用的混淆工具

软件名称

推荐理由

Allatori Java Obfuscator

轻量级可集成在IDE工具中,通过配置文件引入使用

DashO for Android and Java

收费,可与eclipse集成,防止Java程序被逆向工程和篡改,还能压缩代码量

Zelix KlassMaster

能读取和修改Java类文件,可以运行在任何支持1.1.6版Java虚拟机的平台上。

Cinnabar Canner

通过创建一个原生Windows可执行文件(EXE文件)保护你的代码不被逆向工程反编译,这个可执行文件包含了你的应用程序类和资源的全部加密版本,只有在被JVM调用到内存中时才处于非加密状态

Jmangle Java类粉碎机

来阻止反编译Java程序,降低盗版的软件,开发者可用其粉碎类文件中的符号。

RetroGuard

通用的字节码混淆器,用来无缝融入你的日常构建和测试过程中,使得你辛苦编写宝贵的Java代码更加安全

JODE

含Java解码器和优化器的Java包

ProGuard

免费的 Java类文件的压缩,优化,混肴器。它删除没有用的类,字段,方法与属性。使字节码最大程度地优化,使用简短且无意义的名字来重命名类、字段和方法 。eclipse已经把Proguard集成在一起了。

Java 字节码操纵框架 ASM

ASM 是一个 Java 字节码操纵框架。它可以直接以二进制形式动态地生成 stub 类或其他代理类,或者在装载时动态地修改类。ASM 提供类似于 BCEL 和 SERP 之类的工具包的功能,但是被设计得更小巧、更快速,这使它适用于实时代码插装

经过比对,最终选择了proguard,选择该工具主要有以下原因:

  • 支持pom内直接集成
  • 配置文件可以集成pom内,也可以单独配置
  • 有很多相关的博文介绍
  • 公司其他项目组有使用这个的经验

2.proguard实际配置

以springboot单体应用为例,在原有项目配置文件的基础上,需要修改 plugins的相关配置

  • 【可选】修改springboot的maven-plugin配置
<!--Springboot repackage 打包-->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <!-- springboot 打包需要repackage否则不是可执行jar -->
            <goals>
                <goal>repackage</goal>
            </goals>
            <configuration>
                <mainClass>com.xxx.xxx.MainApplication</mainClass>
            </configuration>
        </execution>
    </executions>
</plugin>
  • 【必选】配置proguard
<!-- proguard混淆插件-->
<plugin>
    <groupId>com.github.wvengen</groupId>
    <artifactId>proguard-maven-plugin</artifactId>
    <version>2.2.0</version>
    <executions>
        <execution>
            <!-- 打包的时候开始混淆-->
            <phase>package</phase>
            <goals>
                <goal>proguard</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <injar>${project.build.finalName}.jar</injar>
        <!--输出的jar-->
        <outjar>${project.build.finalName}.jar</outjar>
        <!-- 是否混淆-->
        <obfuscate>true</obfuscate>
        <options>
			<!--指定java版本号-->
            <option>-target 1.8</option> 
			<!--默认开启,不做收缩(删除注释、未被引用代码)-->
            <option>-dontshrink</option> 
			<!--默认是开启的,这里关闭字节码级别的优化-->
            <option>-dontoptimize</option>
			<!--混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代-->
            <option>-adaptclassstrings</option>
			<!-- 忽略warn消息,如果提示org.apache.http.* 这个包里的类有问题,那么就加入下述代码:-keep class org.apache.http.** { *; }    -dontwarn org.apache.http.**-->
            <option>-ignorewarnings</option>
            <option>-keep class org.apache.logging.log4j.util.* { *; }</option>
            <option>-dontwarn org.apache.logging.log4j.util.**</option>
			<!--对异常、注解信息在runtime予以保留,不然影响springboot启动-->
            <option>-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod</option>
            <!--不混淆所有interface接口,可选-->
            <!--<option>-keepnames interface **</option>-->
			<!--保留枚举成员及方法-->
            <option>-keepclassmembers enum * { *; }</option>
			<!--包名-->
            <option>-keeppackagenames com.xxx.controller.**</option>
            <option>-keeppackagenames com.xxx.entity.**</option>
			<!--保留方法参数名-->
            <option>-keepparameternames</option>
            <option>-keepclasseswithmembers public class * {
                public static void main(java.lang.String[]);}
            </option> <!--保留main方法的类及其方法名-->
            <option>-keepclassmembers public class * {void set*(***);*** get*();}</option>
            <!--忽略note消息,如果提示javax.annotation有问题,那麽就加入以下代码-->
            <option>-dontnote javax.annotation.**</option>
            
            <option>-dontnote sun.applet.**</option>
            <option>-dontnote sun.tools.jar.**</option>
            <option>-dontnote org.apache.commons.logging.**</option>
            <option>-dontnote javax.inject.**</option>
            <option>-dontnote org.aopalliance.intercept.**</option>
            <option>-dontnote org.aopalliance.aop.**</option>
            <option>-dontnote org.apache.logging.log4j.**</option>

            <!--入口程序类不能混淆,混淆会导致springboot启动不了-->
			<option>-keep class com.xxx.MainApplication</option>
			<option>-keep class org.apache.logging.log4j.util.*  {*;}</option>
            
            <option>-keep class com.xxx.entity.* {*;}</option>
            <option>-keep class com.xxx.config.* {*;}</option>
            <option>-keep class com.xxx.controller.*.* {*;}</option>
            <option>-keep class com.xxx.service.kafka.* {*;}</option>
            <option>-keep class com.xxx.service.xxljob.* {*;}</option>

            <option>-keep interface * extends * { *; }</option>
            <option>-keep interface com.xxx.entity.*.*.* {*;}</option>
            <!--不混淆所有类,保存原始定义的注释-->
            <option>-keepclassmembers class * {
                @org.springframework.beans.factory.annotation.Autowired *;
                @org.springframework.beans.factory.annotation.Value *;
                @com.xxl.job.core.handler.annotation.XxlJob *;
                @org.springframework.kafka.annotation.KafkaListener *;
                }
            </option>
        </options>
        <libs>
            <!-- 添加依赖 java8-->
            <lib>${java.home}/lib/rt.jar</lib>
            <lib>${java.home}/lib/jce.jar</lib>
        </libs>
    </configuration>


    <dependencies>
        <!-- https://mvnrepository.com/artifact/net.sf.proguard/proguard-base -->
        <dependency>
            <groupId>net.sf.proguard</groupId>
            <artifactId>proguard-base</artifactId>
            <version>6.1.1</version>
        </dependency>
    </dependencies>
</plugin>

三、配置文件加密

配置文件加密采用 Jasypt

四、jar包加密

采用Xjar进行jar包加密,网上普遍使用自定义编译的方式进行配置,如果要简化的话,可以通过已生成的jar直接进行加密,以下方法不适用于springBoot+jpa(hibernate)技术研发的程序。

1.自定义编译

2.通过已有jar直接加密

  1. github下载xjar demo,下载地址:https://github.com/yumingzhu/xjarDemo/tree/master/target
  2. 自己代码的pom同级目录下,新建文件夹(如xjarEncode),并将xjarDemo-1.0-SNAPSHOT.jar拷贝放进来
  3. 编译已混淆的代码,pom.xml下命令窗口执行:mvn clean package -DskipTests
  4. 在第2步新建的文件夹中创建bat脚本文件(如encode.bat),方便后续快速执行
    【123456】 自定义密码。根据自己的需要填写
    【targets-0.0.1-SNAPSHOT.jar】 maven打包生成的jar包名称
    【targets.jar】 目标jar包名称
java -cp .\xjarDemo-1.0-SNAPSHOT.jar XjarDemo 123456 ..\target\targets-0.0.1-SNAPSHOT.jar .\targets.jar
  1. 执行
  1. windows执行方法
java -jar xxx.jar

在弹出的窗口内输入第四步你设置的密码即可

  1. linux执行方法,新建 encode.key文件
password: 123456
hold: true

配置文件说明:

参数名称

参数含义

缺省

说明

password

密码


密码字符串

algorithm

密钥算法

AES

支持JDK所有内置算法,如AES/DES

keysize

密钥长度

128

根据不同算法选择不同秘钥长度

ivsize

向量长度

128

根据不同的算法选择不同的向量长度

hold

是否保留

false

读取后是否保留密钥文件,true/1/yes/y,代表读取后不进行删除

  1. 修改启动脚本,添加 --xjar.keyfile=encode.key参数,如使用linux,推荐以下启动脚本,可实现启动/停止/重启操作
#!/bin/bash
#################################################################
# 作者:拾叁
# 时间:2022-07-14
# 用途:启动管理
#################################################################
if [ -f /etc/init.d/functions ]; then 
    . /etc/init.d/functions
fi


#################################################################
# 定义变量
#################################################################
# application name
SERVICE_NAME='自定义'
SERVICE_PACKAGE="${SERVICE_NAME}.jar"
# application path
SERVICE_PATH='自定义'



#################################################################
# 定义命令
#################################################################
function START_COMMAND()
{
    echo "${SERVICE_PATH}/${SERVICE_PACKAGE}"
    java -Duser.timezone=Asia/Shanghai -Xms4g -Xmx4g -jar ${SERVICE_PATH}/${SERVICE_PACKAGE} --xjar.keyfile=encode.key  2>/dev/null 1>&2 &
    if [[ $? -eq 0 ]]; then
        action "${SERVICE_NAME} start successed" /bin/true
    else
        action "${SERVICE_NAME} start failed" /bin/false
    fi
}

function STOP_COMMAND()
{
    SERVICE_PID=`ps -ef | grep "${SERVICE_PACKAGE}" | grep -v 'grep' | awk '{print $2}'`
    if [[ ${SERVICE_PID} == '' ]]; then
        action "${SERVICE_NAME} is not running" /bin/false
    else
        kill -9 ${SERVICE_PID} >/dev/null 2>&1
        if [[ $? -eq 0 ]]; then
            action "${SERVICE_NAME} stop successed" /bin/true
        else
            action "${SERVICE_NAME} stop failed" /bin/false
        fi
    fi
}

function STATUS_COMMAND()
{
    SERVICE_PID=`ps -ef | grep "${SERVICE_PACKAGE}" | grep -v 'grep' | awk '{print $2}'`
    if [[ ${SERVICE_PID} == '' ]]; then
        action "${SERVICE_NAME} is not running" /bin/false
    else
        action "${SERVICE_NAME} is running" /bin/true
    fi
}


#################################################################
# 定义命令
#################################################################
case "$1" in
    start)
        START_COMMAND
        ;;
    stop)
        STOP_COMMAND
        ;;
    restart|reload)
        STOP_COMMAND
        START_COMMAND
        ;;
    status)
        STATUS_COMMAND
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|status|reload}"
        ;;
esac
  1. 将jar,encode.key,脚本文件拷贝到服务器相应目录
  2. 执行sh xxx.sh restart

五、前后对比效果

1.混淆前

Spring Boot Allatori混淆 springboot代码混淆加密_java

2.混淆后

Spring Boot Allatori混淆 springboot代码混淆加密_spring boot_02

此时配置文件和ORM mybatis xml文件还未加密

Spring Boot Allatori混淆 springboot代码混淆加密_Java_03

3.加密后

Spring Boot Allatori混淆 springboot代码混淆加密_java_04

Spring Boot Allatori混淆 springboot代码混淆加密_jar_05

Spring Boot Allatori混淆 springboot代码混淆加密_spring boot_06

六、参考

  1. proguard官网
  2. ProGuard 最全混淆规则说明