JAVA软件加密-数字摘要和代码混淆

        由于JAVA是一种解释型的语言,很容易被反编译,也许现在很多公司的产品在出厂之前都会加上License验证的步骤,那么License验证真的安全吗?

        NO,稍微有一点JAVA经验的人就知道将你的war包或jar包反编译,然后轻松的找到你的License验证的代码,将它删除,或者直接改为return true,轻松的绕过你的License验证,所以说License验证只能防君子,不能防小人

        那么怎么样使你的JAVA程序更安全呢?利用数字摘要技术+代码混淆。

        讲数字摘要之前有必要先了解一下什么是数字签名。详见通俗易懂讲解什么是《数字签名和数字摘要》

        借助于数字签名的思想,我们利用数字摘要来加密我们的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切面编程才能发挥威力,因为如果你只是在代码中加入验证数字摘要的算法,用户也可以很方便的找出来,反编译后将这段代码移除掉,如果用面向切面编程,你可以将切面逻辑或者摘要信息隐藏起来(比如放在一个很大的类的中间),增加用户找到它的难度。

你可能会问,即便是这样,也有被用户找到的可能,这时候我们可以出绝招了:代码混淆

代码混淆的原理是将编译后的class文件中的包名、类名、方法名、变量名全部替换成毫无意义的a,b,c,提高代码阅读成本。

利用Proguard混淆之后,利用jd-gui反编译出来的代码是这样的:

ps:能读懂这种代码的高手不在我们的讨论范围之列!

混淆有两种方法,下面以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:

        以上方法感兴趣的可以自行研究一下,不在本文的讨论范畴。

        对JAVA软件加密和数字签名感兴趣的童鞋可以加QQ群(936800640)讨论,一起学习!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值