APP下载

Java软件保护方案的设计和实现

2018-12-22龚少麟

计算机时代 2018年5期
关键词:源文件序列号加密算法

龚少麟

(中国电子科技集团公司第二十八研究所,江苏 南京 210007)

0 引言

Java是一种跨平台的解释型语言,由于跨平台的需求,Java的指令集比较简单而通用,较容易得出程序的语义信息。Java编译器将每一个类编译成一个单独的文件,这也简化了反编译的工作[1]。Java源代码编译中间“字节码”存储于Class文件中。Class文件是一种字节码形式的中间代码,该字节码中仍然保留所有的方法名称、变量名称,并且通过这些名称来访问变量和方法,这些符号往往带有许多语义信息。由于Java程序自身的特点,对于不经过处理的Java程序,反编译的效果非常好。

目前,市场上有许多Java的反编译工具,这些工具的反编译速度和效果都很不错。好的反编译软件,能够反编译出非常接近源代码的程序。通过反编译器,黑客能够对这些程序进行更改,或者复用其中的程序。因此,如何保护Java程序不被反编译,是非常重要的一个问题。此外,Java程序基于JVM这一“中间层”能够在不同平台上运行,真正实现了“一次编译,到处运行”的目的。Java的这一特性对商业软件的版权保护也带来了挑战。

1 常见保护技术对比

1.1 源文件保护

目前针对Java源文件方法主要有本地编译、数字水印、ClassLoader加密,以及代码混淆技术。

⑴ 本地编译

Java本地编译[2]是指将Java应用程序编译成本地应用程序,如Windows平台下名为exe的应用程序。通过java虚拟机将源代码生成Java类文件,再将类文件编译成可执行文件。用该技术生成的本地应用程序是二进制格式的可执行文件,但该方法牺牲了Java的跨平台特性,对于桌面应用程序的开发问题不大,但是对于Web应用程序的开发,则是一个致命缺陷。同时该方法技术层面还存在不成熟,支持不完善的问题,不适合采用。

⑵ 数字水印

数字水印技术[3]是将一些标识信息(即数字水印)直接嵌入数字载体(包括多媒体、文档、软件等)当中,但不影响原载体的使用价值,也不容易被人的知觉系统觉察或注意到。通过这些隐藏在载体中的信息,可以达到确认内容创建者、购买者、传送隐秘信息或者判断载体是否被篡改等目的。在需要证明程序是否非法使用时,数字水印就变得很重要。使用水印技术并不能阻止类文件被反编译,但是可以在需要确认某些程序是否属于剽窃时提供有效证据。

⑶ ClassLoader加密

ClassLoader的基本目标是对类的请求提供服务。当JVM 需要使用类时,它根据名称向ClassLoader请求这个类,然后ClassLoader试图返回一个表示这个类的Class对象。通过覆盖对应于这个过程不同阶段的方法,可以创建定制的ClassLoader。在装入原始数据后先进行解密,再转换成Class对象。由于把原始字节码转换成Class对象的过程完全由系统负责,因此只需先获得原始数据,接着就可以进行包含解密在内的任何转换。这种方案比其他方案更加安全,然而这种加密方法存在一个漏洞,由于ClassLoader的类是用Java编写的,如果对ClassLoader类进行反编译,提取其中解密算法,就可解密所有被加密的其他类。

⑷ 代码混淆

代码混淆[4]是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为。代码混淆可以用于程序源代码,也可以用于程序编译而成的中间代码。代码混淆技术其本质上是类文件模糊技术,处理以后的类文件功能和处理以前的类文件功能在逻辑上是等同的,即运行后能够得到一样的输出结果。但是,经过测试发现目前市面上的一些代码混淆工具实际效果并不好,使用一些反编译工具仍然可以轻易地看到源码。

1.2 软件授权

一般商用软件的授权方式分为3种:序列号、联网认证、授权码[5]。

⑴ 序列号方式是最常见的软件授权方式,也为绝大多数商用软件所采用,它的安全性最低,最容易盗版,但这类软件大多为通用型软件,有广大的用户群,厂商依然能获得足够的利润。多为桌面系统软件所采用。

⑵ 联网认证则是随着互联网的飞速发展,而出现的新型软件授权方式,已经在Microsoft和Adobe等厂商的产品中得到广泛应用,首先要开发一个客户端验证软件,其次还要架设专门的提供验证授权服务的网站,从技术成本和资金成本上来讲,都不适合中小软件公司;也不适用于专用性较强,用户群较小的软件。

⑶ 授权码。程序获得运行机的惟一标识(如:网卡硬件地址、CPU ID序列号、硬盘序列号等);程序将获得的惟一标识加密(可灵活选用各种加密算法进行加密),然后由用户或者程序自动将加密后的标识作为申请码发送给软件的开发方;开发者将加密后的惟一标识解密,然后开发者再将惟一标识加密作为授权码发送给客户注册;客户将开发者发送的授权码进行注册解密,解密后的内容其实就是:惟一标识;每当程序启动,首先解密已获取的授权码,然后读取网卡硬件地址、CPU ID序列号、硬盘序列号等,进行验证,比较两个标识是否一致。如果经比较一致,则认为是合法授权安装,程序正常启动;如果不一致,则认为是非法授权安装,程序停止启动。

2 软件保护方案设计

2.1 方案设计

源文件保护和软件授权是软件保护需要解决的两大问题。考虑使用软件运行机器的惟一标识作为源文件加/解密的密钥,既可以实现对源文件的保护,又能达到软件和运行机器绑定,防止软件被随意复制扩散。基于此思路,本方案基于软件运行机器的惟一标识进行变换后作为加密的密钥,对核心文件进行加密保护。在Web工程启动时,程序同样基于软件运行机器的惟一标识进行变换后作为解密的密钥启动解密功能模块。如果解密正确则表示软件已被授权,Web工程可以正常启动,否则中断启动程序并提示。整个软件保护流程如下。

⑴ 密钥生成

首先,获取软件运行机器的惟一标识(如:CPU序列号、硬盘序列号、MAC序列号等)。由于使用工具可以伪造MAC序列号,这里我们选择使用CPU序列号、硬盘序列号的组合作为软件运行机器的惟一标识I。为了增强软件保护方案的安全性,将CPU序列号、硬盘序列号、用户信息等,按自定义的规则进行组合,以满足密钥的不可知性。

I=F(CPU序列号,硬盘序列号,用户信息)

使用散列函数对软件运行机器的惟一标识信息进行数学运算,生成符合加密算法要求的密钥K。MD5即Message-Digest Algorithm 5(信息-摘要算法5),是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法)。MD5的作用是以任意长度的信息作为输入进行计算,产生一个128-bit(16-byte)的指纹或报文摘要。对惟一标识I进行MD5计算后得到一个128位摘要信息,作为加密算法的密钥K。

K=Hash(I)

⑵ 加密解密

常用的加密算法有对称加密和非对称加密两大类型[6],对称加密指加密和解密使用相同密钥的加密算法。对称加密算法的特点是算法公开、计算量小、加密速度快、加密效率高。具体算法包括DES、3DES、AES等算法。非对称加密算法需要公开密钥和私有密钥两个密钥。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密,主要算法包括RSA、Elgamal、ECC等。非对称加密算法主要用于数字签名和密钥的加密传输,加密速度也比对称加密算法慢,本方案使用AES加密算法来加密工程源文件,并将源文件加密过程编译成一个独立的可执行程序。

在实际使用中也可以根据需要选择其他加密强度更高的对称加密算法。AES在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。AES加密数据块分组长度必须为128bit,密钥长度可以是128比特、192比特、256比特中的任意一个。上一步得到的密钥长度为128bit,满足AES加密算法的要求。为了提高加密效率,只选择工程中部分核心源文件进行加密,使用密钥K对文件进行加密计算,最终得到经过加密的文件。

由于我们使用的是对称加密算法,加密密钥和解密密钥是相同的,因此解密密钥的获取方法和第一步相同。使用AES解密算法对核心源文件进行解密,若解密正确则可得到正确的源文件,Java工程可以正常启动运行。为了保证解密过程不被破解,将解密过程封装在C++动态库中,由Java调用动态库中的解密方法获得源文件[7]。

⑶ 启动验证方案需要在Java程序启动时对加密的源文件进行解密。JVM每次装入类文件时都需要一个称为ClassLoader的对象,这个对象负责把新的类装入正在运行的JVM[8]。JVM给ClassLoader一个包含了待装入类(比如java.lang.Object)名字的字符串,然后由ClassLoader负责找到类文件,装入原始数据,并把它转换成一个Class对象。因此按照规则编写自定义的ClassLoader进行解密,这样我们就可以在程序中加载特定的类,并且这个类只能被我们自定义的加载器来进行加载,提高了程序的安全性。

2.2 可行性分析

⑴ 源文件保护

本方案将程序的核心源文件通过对称加密,转换为不可执行的、不可反编译的乱码。加密解密密钥是基于软件运行机器的惟一标识,使用散列函数计算得到。密钥具有以下特点:由于密钥是将CPU序列号、硬盘序列号、用户信息按自定义的规则进行组合计算得到,而破解者不知道添加的用户信息和组合规则,因此破解者无法计算出密钥结果;本方案将解密过程封装在动态库中,由Java启动程序调用动态库执行源文件解密过程,由于动态库是二进制文件,所以很难通过反编译得到解密算法。Java运行时装入字节码的机制隐含地意味着可以对字节码进行修改。在这里,它的用途是在类文件装入之时进行解密,因此可以看成是一种即时解密器。由于解密后的字节码文件永远不会保存到文件系统,所以窃密者很难得到解密后的代码。因此,依赖本方案中加密解密密钥的这些特点,可以有效地保证源文件的安全性。

⑵ 软件授权

本方案将密钥的计算和软件运行机器的CPU序列号、硬盘序列号进行了绑定,由于每台机器的密钥都是不一样,保证了只有在指定的机器上运行软件才能计算获得正确的解密密钥,从而解密源文件使Web工程正常运行。由于密钥的计算中加入了用户信息和组合规则,确保破解者无法计算出密钥。而且一旦软件被人破解,开发者无需对软件做过多改动,只需更改获取密钥中的用户信息和组合规则,再变换加密算法,即可应对破解,这对于投入高、用户群小、专业性强的软件是最优选择。

3 具体实现

3.1 密钥生成

⑴ 获取软件运行机器的惟一标识

在给软件加序列号时,CPU序列号、硬盘序列号这两个参数是最有用的,可以实现序列号和机器绑定。这些信息可以在.NET下通过WMI非常方便的查询得到。WMI非常强大,可以查询电脑中各种信息。查询的方式是WQL,和SQL的语句差不多。采用C#语言编程实现此功能,并保存在文件中。

//获取CPU序列号代码

string cpuInfo="";

ManagementClass mc=new ManagementClass

("Win32_Processor");

ManagementObjectCollection moc=mc.GetInstances();

foreach(ManagementObject mo in moc)

{cpuInfo=mo.Properties["ProcessorId"].Value.ToString();

}

//获取硬盘ID

string HDid="";

ManagementClass mc=new ManagementClass

("Win32_DiskDrive");

ManagementObjectCollection moc=mc.GetInstances();

foreach(ManagementObject mo in moc)

{HDid=(string)mo.Properties["Model"].Value;

}

⑵ 生成密钥

本方案使用Crypto++库来实现密钥生成功能。首先在文件头添加#pragma comment(lib,"cryptlib.lib")加载lib文件和头文件md5.h。代码如下,其中message为原始信息,len为message的长度。

MD5 md5;

byte digest[16];

md5.CalculateDigest(digest,message,len);

3.2 加密解密

⑴加密

源文件加密是一个独立的可执行程序。在应用加密方法时要加四个头文件 aes.h、hex.h、files.h和modes.h。加密函数void AESEncryptFile(const char*in,const char*out,const char*passPhrase)中参数in是要加密的文件名,out是要解密的文件名,passPhrase是密钥。这个函数的基本流程为:用增量值iv和密钥passPhrase创建一个AES加密的CFB工作模式对象aesEncryption。

void AESEncryptFile(const char *in,const char *out,

const char *passPhrase)//加密文件

{byte iv[AES::BLOCKSIZE]="123456";

AES::Encryption aesEncryption((byte*)passPhrase,

AES::DEFAULT_KEYLENGTH);

CFB_Mode_ExternalCipher::Encryption cfbEncryption

(aesEncryption,iv);

FileSource f(in,true,new StreamTransformationFilter

(cfbEncryption,new FileSink(out)));

}

⑵解密

解密算法被封装在C++动态库中,方便Java程序在启动时调用。源文件解密同样需要在应用解密函数时要加四个头文件 aes.h、hex.h、files.h和modes.h,实现过程和加密类似,实现代码如下:

//解密文件

void AESDecryptFile(const char *in,const char *out,

const char *passPhrase)//解密文件

{byte iv[AES::BLOCKSIZE]="123456";

CFB_Mode<AES >::Decryption cfbDecryption((byte *)

passPhrase,16,iv);

FileSource f(in,true,new StreamTransformationFilter

(cfbDecryption,new FileSink(out)));

}

3.3 程序启动

按照规则编写自定义的ClassLoader进行解密,这样我们就可以在程序中加载特定的类,并且这个类只能被我们自定义的加载器进行加载。通过定制的loadClass,实现运行时动态解密并执行程序,一般实现这个方法的步骤是执行findLoadedClass(String)去检测这个class是不是已经加载过了。执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。如果向上委托父加载器没有加载成功,则通过findClass(String)查找。如果class在上面的步骤中找到了,参数resolve又是true的话,那么loadClass()又会调用resolveClass(Class)这个方法来生成最终的Class对象。我们可以从源代码看出这个步骤。

public Class loadClass(String name,boolean resolve)

throws ClassNotFoundException

{Class clasz=null;

clasz=findLoadedClass(name);

if(clasz!=null)

return clasz;

byte classData[]=Util.readFile(name+".class");

if(classData!=null){

byte decryptedClassData[]=cipher.doFinal

(classData);//解密

clasz=defineClass(name,decryptedClassData,0,

decryptedClassData.length);//再把它转换成一个类

}

if(clasz==null)

clasz=findSystemClass(name);

if(resolve&&clasz!=null)

resolveClass(clasz);

return clasz;//把类返回给调用者

}

4 结束语

本文对现有的Java工程软件保护机制进行了深入研究,分析了不同机制的优缺点及其适用场景,提出了一种新的Java软件保护方案,并给出部分实现过程。该方案基于软件运行机器的惟一标识码,采用AES对称加密算法对核心源文件加密,在实现对源文件保护的同时,也确保了软件和运行机器的绑定,使其具有较高的防破解能力。即使软件被破解,也可以通过更改获取密钥中的用户信息和组合规则,再变换加密算法,轻松应对破解。现有Java Web工程大多基于开源框架,因此基于Spring框架如何实现ClassLoader的加载还需要进一步深入研究。

参考文献(References):

[1]周建林.软件保护技术研究与设计[D].华中科技大学硕士学位论文,2009.

[2]宋扬,李立新,周雁舟等.软件防篡改技术研究[J].计算机安全,2009.1:34-37

[3]李勇,左志宏.目标代码混淆技术综述[J].计算机技术与发展,2007.17(4):68-71

[4]尹浩,林闯等.数字水印技术综述[J].计算机技术与发展,2005.42(7):1093-1099

[5]王琴琴,郭师虹.软件授权技术的研究[J].计算机技术与发展,2012.22(9):235-238

[6]张福泰,李继国,王晓明等.密码学教程[M].武汉大学出版社,2006.

[7]王银江,凌力.JNI在安全加密系统效率改进中的应用[J].计算机工程,2004.30(12):99-100

[8]章敦华,刘建.Java动态类加载机制及其应用[J].计算机工程与设计,2004.25(3):432-435

猜你喜欢

源文件序列号加密算法
一种离线电子钱包交易的双向容错控制方法
网络社区划分在软件质量问题分析中的应用
基于源文件可疑度的软件缺陷定位方法研究
recALL
LKJ基础数据源文件自动编制系统的研究
基于小波变换和混沌映射的图像加密算法
Hill加密算法的改进
对称加密算法RC5的架构设计与电路实现
误写C源文件扩展名为CPP的危害
基于Arnold变换和Lorenz混沌系统的彩色图像加密算法