![](https://i-blog.csdnimg.cn/blog_migrate/41992dcd99ee8cc8dee4beabfe505596.png)
由于JAVA是一种解释型的语言,很容易被反编译,也许现在很多公司的产品在出厂之前都会加上License验证的步骤,那么License验证真的安全吗?
NO,稍微有一点JAVA经验的人就知道将你的war包或jar包反编译,然后轻松的找到你的License验证的代码,将它删除,或者直接改为return true,轻松的绕过你的License验证,所以说License验证只能防君子,不能防小人。
那么怎么样使你的JAVA程序更安全呢?利用数字摘要技术+代码混淆。
![](https://i-blog.csdnimg.cn/blog_migrate/3a84f8a16f6f3569dab2a78a6cab413c.png)
借助于数字签名的思想,我们利用数字摘要来加密我们的class文件,如果我们不想让用户修改license验证相关的代码,我们就将LicenseCheck.class做一个HASH算法,计算出一个HASH值(即摘要信息),用户在使用软件之前需要去验证这个摘要信息,如果用户修改过这个LicenseCheck.class文件,那么摘要信息就会改变,验证就不通过。具体代码如下:
import java.io.BufferedInputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.zip.CRC32;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class CheckAspect {
private final String POINT_CUT = "execution(public * com.founder.vt.controllers.*.*(..))";
@Pointcut(POINT_CUT)
public void pointCut(){}
@Before(value = "pointCut()")
public void before(JoinPoint joinPoint){
System.out.println("aspect-----------------------");
if(!isCracked()) {
System.out.println("为保证系统的完整性,本系统不允许任何未经许可的反编译和修改,谢谢合作!");
Thread.currentThread().stop();
}else {
System.out.println("验证通过。");
}
}
private long crc32 = 0L;
private boolean isCracked()
{
if(crc32 == 0L)
crc32 = getCRC32();
System.out.println(crc32);
if(crc32 == 0x59416716L) //相当于十进制的1497458454
return true;
return false;
}
private long getCRC32()
{
List classList = new ArrayList();
classList.add(com.licence.LicenseCheck.class);
CRC32 crc = new CRC32();
try
{
byte buffer[] = new byte[1024];
int len = -1;
BufferedInputStream bis = null;
Iterator it = classList.iterator();
while(it.hasNext())
{
Class clazz = (Class)it.next();
String className = clazz.getName();
className = className.substring(className.lastIndexOf(".") + 1);
bis = new BufferedInputStream(clazz.getResourceAsStream(className+".class"));
while((len = bis.read(buffer, 0, 1024)) > 0)
crc.update(buffer,0,len);
bis.close();
}
return crc.getValue();
}
catch(Exception ex)
{
return 0;
}
}
}
验证数字摘要防止代码被篡改,需要配合Aspect切面编程才能发挥威力,因为如果你只是在代码中加入验证数字摘要的算法,用户也可以很方便的找出来,反编译后将这段代码移除掉,如果用面向切面编程,你可以将切面逻辑或者摘要信息隐藏起来(比如放在一个很大的类的中间),增加用户找到它的难度。
![](https://i-blog.csdnimg.cn/blog_migrate/4e3cf72f72931cdf2da0ab735b3d19d9.png)
你可能会问,即便是这样,也有被用户找到的可能,这时候我们可以出绝招了:代码混淆。
代码混淆的原理是将编译后的class文件中的包名、类名、方法名、变量名全部替换成毫无意义的a,b,c,提高代码阅读成本。
利用Proguard混淆之后,利用jd-gui反编译出来的代码是这样的:
![](https://i-blog.csdnimg.cn/blog_migrate/8347006367d0cce4ac30b3f0caff3480.png)
ps:能读懂这种代码的高手不在我们的讨论范围之列!
![](https://i-blog.csdnimg.cn/blog_migrate/1790721b92f3962753f190eb52350512.png)
混淆有两种方法,下面以maven工程为例,配置pom.xml如下:
<!-- ProGuard混淆插件-->
<plugin>
<groupId>com.github.wvengen</groupId>
<artifactId>proguard-maven-plugin</artifactId>
<version>2.0.14</version>
<executions>
<execution>
<!--混淆时刻,这里是打包的时候混淆-->
<phase>package</phase>
<goals>
<!--使用插件的什么功能,当然是混淆-->
<goal>proguard</goal>
</goals>
</execution>
</executions>
<configuration>
<proguardVersion>6.0.2</proguardVersion>
<obfuscate>true</obfuscate>
<!--是否将生成的PG文件安装部署-->
<attach>true</attach>
<!--是否混淆-->
<obfuscate>true</obfuscate>
<!--指定生成文件分类-->
<attachArtifactClassifier>pg</attachArtifactClassifier>
<options>
<!--JDK目标版本1.8-->
<option>-target 1.8</option>
<!--不做收缩(删除注释、未被引用代码)-->
<option>-dontshrink</option>
<!--不做优化(变更代码实现逻辑)-->
<option>-dontoptimize</option>
<!--不路过非公用类文件及成员-->
<option>-dontskipnonpubliclibraryclasses</option>
<option>-dontskipnonpubliclibraryclassmembers</option>
<!--优化时允许访问并修改有修饰符的类和类的成员-->
<option>-allowaccessmodification</option>
<!--确定统一的混淆类的成员名称来增加混淆,防止冲突-->
<option>-useuniqueclassmembernames</option>
<!--不用大小写混合类名机制-->
<option>-dontusemixedcaseclassnames</option>
<!--不混淆所有包名,Spring配置中有大量固定写法的包名-->
<!-- <option>-keeppackagenames</option>
<option>-adaptclassstrings</option> -->
<!--不混淆所有特殊的类-->
<option>
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod
</option>
<!--不混淆所有的set/get方法,毕竟项目中使用的部分第三方框架(例如Shiro)会用到大量的set/get映射-->
<option>-keepclassmembers public class *{void set*(***);*** get*();} </option>
<!-- 只混淆这个包下的类 -->
<!-- <option>-keep class !com.licence.** { *; }</option> -->
<!-- <option>-keep class !com.util.bak.* { *; }</option> -->
<!-- 不混淆这个包下的类 -->
<option>-keep class com.controllers.* { *; }</option>
<option>-keep class com.mapper.* { *; }</option>
<!-- 不混淆main方法 -->
<option>-keep class com.App { *; }</option>
<!--不显示警告信息,如果显示则会出现Error无法完成混淆!-->
<option>-dontwarn **</option>
</options>
<outjar>${project.build.finalName}-pg.jar</outjar>
<!--添加依赖,这里你可以按你的需要修改,这里测试只需要一个JRE的Runtime包就行了-->
<libs>
<lib>${java.home}/lib/rt.jar</lib>
</libs>
<!--加载文件的过滤器,就是你的工程目录了-->
<!--<inFilter>com/test/prog/**</inFilter>-->
<!--<inFilter>com/itdct/es/**</inFilter>-->
<!--对什么东西进行加载,这里仅有classes成功,毕竟你也不可能对配置文件及JSP混淆吧-->
<injar>classes</injar>
<!--输出目录-->
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
<dependencies>
<!--使用6.0.2版本来混淆-->
<dependency>
<groupId>net.sf.proguard</groupId>
<artifactId>proguard-base</artifactId>
<version>6.0.2</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</plugin>
以上代码亲测可用,通常不是所有的方法都可以混淆的,比如controller的接口方法,是要供前端调用的,如果混淆了则前端就调用不了了,再比如mabatis的映射文件也是不能混淆的,这个根据各自的项目情况利用-keep语句排除就可以了。
当然,道高一尺,魔高一丈,混淆只是尽量提高破解的难度,并不能完全保证不被破解,如果想继续增加难度,可以考虑自己写classloader:
![](https://i-blog.csdnimg.cn/blog_migrate/e07019b6c7a038cb3f40dccbad50289f.png)
以上方法感兴趣的可以自行研究一下,不在本文的讨论范畴。
对JAVA软件加密和数字签名感兴趣的童鞋可以加QQ群(936800640)讨论,一起学习!