一、代码混淆的背景
代码的交付一般都是以Jar/War/Ear的形式进行交付的,但是现在有不少工具可以在获得如上的发布包之后进行反编译,使得核心代码逻辑泄露,得不到应有的保护。
- Jar,Java Archive Resour,指纯Java类的发布包;
- War,Web Archive Resour,在Jar的基础上还包含了前端静态资源等内容;
- Ear,Enterprise Archive Resour,在War的基础上还包含了EJB组件等内容;
流行的反编译工具,比如jd-gui可以很方便地对发布包进行反编译,从而破解核心代码逻辑。倘若团队产出的发布包是部署到不受控制的机房的,那么极有可能被潜在的竞争对手获取,造成极大的损失。
二、技术选择
2.1 代码加密
使用加密算法对发布包进行整个的加密,在部署到服务器上的时候,需要配置容器参数和启动参数,并将解密方法也放置在服务器上,使得加密包被解密加载和执行。
这种方案对发布包的保护比较彻底,但是由于解密方法也放在服务器上,所以一旦服务器被攻陷,还是有一定几率导致发布包被解密破解,但是概率小很多,被破解的代价也会很大。
这种方法不是我们今天讨论的主题,因此简要提及即可,对运维能力要求较高,对代码侵入性较低。
2.2 代码混淆
代码一般都是由人写的,那么势必具有可读性,一旦被反编译后,竞争对手可以很快读懂代码逻辑,进行分析和改造。那么我们就需要代码混淆来对反编译后的代码进行可读性的干扰,降低被读懂的概率。
- ProGuard
- Allatori
本文先对Proguard进行简单的入门介绍。
三、混淆示例
我们先从start.spring.io上下载一个springboot项目,里面增加一个冒泡排序算法的实现。
@Slf4j
public class BubbleSort {
public static void bubbleSort(int[] data, int n){
if(n <= 1){
log.info("数组中元素少于2个,无需进行排序!");
return;
}
// 最多需要迭代n次冒泡
for(int i=0;i<n;i++){
// 表示当前迭代冒泡中是否发生了元素交换
boolean switchFlag = false;
for(int j=0;j<n-i-1;j++){
if(data[j] > data[j+1]){
// 不满足从小到大的关系,需要交换
int temp = data[j];
data[j] = data[j+1];
data[j+1] = temp;
switchFlag = true;
}
}
// 当前冒泡迭代中没有发生元素交换,说明数列已经有序,完成了排序,可以提前结束
if(!switchFlag){
break;
}
}
}
}
其它的pom文件和启动类不做任何改动,此时运行 mvn clean package,就能在项目目录下生成tartget文件夹,找到其中打包好的jar,使用jd-gui打开,就可以看到反编译后的内容为:

代码逻辑被很轻易的破解和复制了。
接下来我们引入ProGuard:
3.2 引入plugin
<plugin>
<groupId>com.github.wvengen</groupId>
<artifactId>proguard-maven-plugin</artifactId>
<version>2.0.11</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>proguard</goal></goals>
</execution>
</executions>
<configuration>
<proguardVersion>6.2.2</proguardVersion>
<injar>${project.build.finalName}.jar</injar>
<outjar>${project.build.finalName}.jar</outjar>
<obfuscate>true</obfuscate>
<libs>
<lib>${java.home}/lib/rt.jar</lib>
<lib>${java.home}/lib/jce.jar</lib>
</libs>
</configuration>
<dependencies>
<dependency>
<groupId>net.sf.proguard</groupId>
<artifactId>proguard-base</artifactId>
<version>6.2.2</version>
</dependency>
</dependencies>
</plugin>
3.3 增加配置
配置可以引入外部的文件,也可以直接配置在xml中,我们以后者为例,在<configuration>的最后增加一些配置内容:
<options>
<option>-target 1.8</option>
<option>-dontshrink</option>
<option>-dontoptimize</option>
<option>-useuniqueclassmembernames</option>
<option>-adaptclassstrings</option>
<option>dontusemixedcaseclassnames</option>
<option>-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod</option>
<option>-keepclasseswithmembers public class * { public static void main(java.lang.String[]);}</option>
<option>-keepclassmembers enum * { *; }</option>
</options>
3.4 修改启动类
在启动类中增加内部类定义:
public static class CustomerGenerator implements BeanNameGenerator{
@Override
public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
return beanDefinition.getBeanClassName();
}
}
此时,我们就完成了Proguard的入门配置,此时运行 mvn clean package,在target中找到jar,再次使用反编译工具jd-gui,就能看到,代码被混淆了,可读性被降低了。

Proguard的官方参考手册如下所示,需要仔细阅读对应的选项内容,然后进行一些适用的配置。
本文仅做为入门示例进行记录,后续有时间再对ProGuard进行详细的研究。
网友评论