Java诞生于年是一门较年轻的语言它以平台无关性安全性面向对象分布式键壮性等特点赢得了众多程序员的青睐特别是它简洁的面向对象的语言风格更让许多人对它爱不释手但人们在使用Java的过程中会发现它有几个致命的弱点运行速度慢用户使用不便源代码保护机制不够安全特别是在保护源代码方面Java是基于解释一种叫Java字节码的中间代码来运行其程序的而且Jvm比计算机的微处理器要简单的多文档也很齐全结果造成其目标程序很容易被反编译而且所得代码和其原始代码十分相似甚至可以一模一样可读性相当好这就给Java的代码保护带来了不利但要实现Java程序的保护也不是不可能的经研究和总结至少有三种实现方式混淆器网络加载重要类加密重要类
混淆器
目前开发人员使用的比较多的保护代码的方法是用混淆器混淆器是采用一些方法将类变量方法包的名字改为无意义的字符串使用非法的字符代替符号贴加一些代码使反编译软件崩溃贴加一些无关的指令或永远执行不到的指令等使反编译无法成功或所得的代码可读性很差这样就实现了反反编译的目的我们来做个演示原始代码如下
import Javaio*import Javasecurity*public class sKey_kb{public static void main(String args[]) throws Exception{FileInputStream f=new FileInputStream(keydat)ObJectInputStream b=new ObJectInputStream(f)Key k=(Key)breadObJect()byte[] kb=kgetEncoded()FileOutputStream f=new FileOutputStream(keykbdat)fwrite(kb)for(int i=iSystemoutprint(kb[i]+)} } }
使用混淆器后再用Jad反编译得代码如下
import Javaio*import JavasecurityKeypublic class sKey_kb{public skey() {}public static void main(String args[]) {FileInputStream fileinputstream=new FileInputStream(ma)ObJectInputStream obJectinputstream=new ObJectInputStream(filein
putstream)Key key=(Key)breadObJect()byte abyte[]=keygetEncoded()FileOutputStream fileoutputstream=new FileOutputStream(na)fileoutputstreamwrite(abyte)for(int i=iSystemoutprint(abyte[i]+oa)}private static String a(String s){int i=slength()char ac[]=new char[i]for(int J=Jreturn new String(ac)}private static String ma=uAAuAAFuAFuAFBuAEuAAEuAABuABEprivate static String na=
uAAuAAFuABuAAuAAuAFBuAEuAAEuAABuABEprivate static String oa=uAEpublic static{ma=a(ma)na=a(ma)oa=a(oa)} }
混淆后再反编译所仍然能得到源代码但显然所得代码与原始代码比变得难以读懂代码中多了其他的方法文件名等信息也被打乱了并且把以上代码写进sKey_kbJava中无法通过编译
但是如果在编写软件时在软件中写入某些注册信息或一些简单的算法通过反编译还是有可能得到这些信息的从而未能达到保护软件的目的反编译器与混淆器之间的斗争是永无止尽的所以从其他角度去保护Java的源代码是很有必要
网络加载重要类
在Java中提供了一个ClassLoader类这个类可以让我们使用类加载器将所需要的Java字节码文件加载到Jvm中我们通过重写这个类可以实现从网络通过url加载Java字节码文件这样我们就可以把一些重要的隐秘的class放在网络服务器上通过口令去检验是否有权限下载该类从而实现Java代码保护的目的其次在Java中正好提供了URLClassLoader这个类通过此类正好可以实现我们的目的URLClassLoader类的基本使用方法是通过一个URL类型的数组告诉URLClassLoader类的对象是从什么地方加载类然后使用loadclass()方法从给定的URL中加载字节码文件获得它的方法然后再执行
具体步骤如下
创建URL
URL url[]={new URL(file:///c/classloader/web)new URL(/Javaclass/)}
创建URLClassLoader对象
URLClassLoader cl=new URLClassLoader(url)
使用URLClassLoader对象加载字节码文件
Class class=clloadClass(class)
执行静态方法
Class getarg[]={(new String [])getClass() }Method m=classgetMethod(maingetarg)String[] myl={arg passedarg passed)ObJect myarg[]={myl}minvole(nullmyarg)
加密重要类
使用网络加载重要类的方法固然有一定的用处但是在遇到无网络的情况时还是无法解决我们的问题对于这种情况我们只能把所有文件放在本地计算机上那么对此我们该怎么做才能保护好Java代码呢?
其实要实现这一点并不难只需要对一些重要的类实行加密就可以了当然在装载时加密的类是需要解密才能被ClassLoader识别的所以我们必须自己创建ClassLoader类在标准Java api中ClassLoader有几个重要的方法创建定制ClassLoader时我们只需覆盖其中的一个即loadClass添加获取原始类文件数据的代码这个方法有两个参数类的名字以及一个表示JVM是否要求解析类名字的标记(即是否同时装入有依赖关系的类)如果这个标记为true我们只需在返回JVM之前调用resolveClass
原代码如下
public Class loadClass( String name boolean resolve )throws ClassNotFoundException {try {Class clasz = null//步骤如果类已经在系统缓沖之中我们就不需要再次装入它clasz = findLoadedClass( name )if (clasz != null)return claszbyte classData[] = /* 通过某种方法获取字节码数据 */if (classData != null) {clasz = defineClass( name classData classDatalength )}//步骤如果上面没有成功if (clasz == null)clasz = findSystemClass( name )//步骤如有必要则装入相关的类if (resolve && clasz != null)resolveClass( clasz )return clasz} catch( IOException ie ) {throw new ClassNotFoundException( ietoString() )} catch( GeneralSecurityException gse ) {throw new ClassNotFoundException( gsetoString() )} }
代码中的大部分对所有ClassLoader对象来说都一样但有一小部分是特有的在处理过程中ClassLoader对象要用到其他几个辅助方法findLoadedClass用来进行检查以便确认被请求的类当前是否存在loadClass方法应该首先调用它defineClass获得原始类文件字节码数据之后调用defineClass把它转换成对象任何loadClass实现都必须调用这个方法findSystemClass提供默认ClassLoader的支持如果用来寻找类的定制方法不能找到指定的类则可以调用该方法尝试默认的装入方式resolveClass当JVM想要装入的不仅包括指定的类而且还包括该类引用的所有其他类时它会把loadClass的resolve参数设置成true这时我们必须在返回刚刚装入的Class对象给调用者之前调用resolveClass
接下来就是加密解密部分Java加密扩展即Java Cryptography Extension简称JCE是Sun的加密服务软件包含了加密和密匙生成功能我们可以用DES算法加密和解密字节码用JCE加密和解密数据是要遵循一些基本步骤的(可以参考<>这里就不祥述了)
加密完成后就是通过解密来获取原始类的Java字节码可以通过一个DecryptStart程序运行经过加密的应用
具体方法如下
public class DecryptStart extends ClassLoader{private SecretKey keyprivate Cipher cipherpublic DecryptStart( SecretKey key ) throws GeneralSecurityException
IOException {thiskey = keyString algorithm = DESSecureRandom sr = new SecureRandom()Systemerrprintln( [DecryptStart creating cipher] )cipher = CiphergetInstance( algorithm )cipherinit( CipherDECRYPT_MODE key sr )}// main过程我们要在这里读入密匙创建DecryptStart的static public void main( String args[] ) throws Exception {String keyFilename = args[]String appName = args[]String realArgs[] = new String[argslength]Systemarraycopy( args realArgs argslength )Systemerrprintln( [DecryptStart reading key] )byte rawKey[] = UtilreadFile( keyFilename )DESKeySpec dks = new DESKeySpec( rawKey )SecretKeyFactory keyFactory = SecretKeyFactorygetInstance( DES )SecretKey key = keyFactorygenerateSecret( dks )DecryptStart dr = new DecryptStart( key )Systemerrprintln( [DecryptStart loading +appName+] )Class clasz = drloadClass( appName )String proto[] = new String[]Class mainArgs[] = { (new String[])getClass() }Method main = claszgetMethod( main mainArgs )ObJect argsArray[] = { realArgs }Systemerrprintln( [DecryptStart running +appName+main()] )maininvoke( null argsArray )}
虽然应用本身经过了加密但启动程序DecryptStart没有加密攻击者可以反编译启动程序并修改它把解密后的类文件保存到磁盘降低这种风险的办法之一是对启动程序进行高质量的模糊处理或者启动程序也可以采用直接编译成机器语言的代码使得启动程序具有传统执行文件格式的安全性比如使用Java的Jini技术来实现解密部分就可以作到当然这是需要付出一定的代价的就是丧失了Java的最大特点——平台无关性不过Jni技术可以用c语言在多种平台实现我们可以在不同的平台编写不同的启动程序
综合实例
对于一些需要网络支持的软件来说可以建立一个Web站点在站点上存放该软件的关键类并且建立用户管理机制用户直接登陆网站进行确认是许可用户则发放解密key文件让其下载关键类在本地解密运行这样作的优点是建立的Web站点可以有效的管理密钥以及用户资料从而起到加强保护软件源代码的作用并方便软件升级用C/S结构是不错的选择