内容:
、为什么要加密?
2、定制类装入器
3、加密、解密
4、应用例子
5、注意事项
参考资源
作者介绍
俞良松([email protected])
软件Software工程师独立顾问和自由撰稿人
2001年10月
Java源代码很容易被别人偷看只要有个反编译器任何人都可以分析别人代码本文讨论如何在不修改原有情况下通过加密技术保护源代码
、为什么要加密?
对于传统C或C的类语言来说要在Web上保护源代码是很容易只要不发布它就可以遗憾是Java源代码很容易被别人偷看只要有个反编译器任何人都可以分析别人代码Java灵活性使得源代码很容易被窃取但和此同时它也使通过加密保护代码变得相对容易我们唯需要了解就是JavaClassLoader对象当然在加密过程中有关JavaCryptographyExtension(JCE)知识也是必不可少
有几种技术可以“模糊”Java类文件使得反编译器处理类文件效果大打折扣然而修改反编译器使的能够处理这些经过模糊处理类文件并不是什么难事所以不能简单地依赖模糊技术来保证源代码安全
我们可以用流行加密工具加密应用比如PGP(PrettyGoodPrivacy)或GPG(GNUPrivacyGuard)这时最终用户在运行应用的前必须先进行解密但解密的后最终用户就有了份不加密类文件这和事先不进行加密没有什么差别
Java运行时装入字节码机制隐含地意味着可以对字节码进行修改JVM每次装入类文件时都需要个称为ClassLoader对象这个对象负责把新类装入正在运行JVMJVM给ClassLoader个包含了待装入类(比如java.lang.Object)名字串然后由ClassLoader负责找到类文件装入原始数据并把它转换成个Class对象
我们可以通过定制ClassLoader在类文件执行的前修改它这种技术应用非常广泛——在这里它用途是在类文件装入的时进行解密因此可以看成是种即时解密器由于解密后字节码文件永远不会保存到文件系统所以窃密者很难得到解密后代码
由于把原始字节码转换成Class对象过程完全由系统负责所以创建定制ClassLoader对象其实并不困难只需先获得原始数据接着就可以进行包含解密在内任何转换
Java2在定程度上简化了定制ClassLoader构建在Java2中loadClass缺省实现仍旧负责处理所有必需步骤但为了顾及各种定制类装入过程它还个新findClass思路方法
这为我们编写定制ClassLoader提供了条捷径减少了麻烦:只需覆盖findClass而不是覆盖loadClass这种思路方法避免了重复所有装入器必需执行公共步骤这切由loadClass负责
不过本文定制ClassLoader并不使用这种思路方法原因很简单如果由默认ClassLoader先寻找经过加密类文件它可以找到;但由于类文件已经加密所以它不会认可这个类文件装入过程将失败因此我们必须自己实现loadClass稍微增加了些工作量
2、定制类装入器
每个运行着JVM已经拥有个ClassLoader这个默认ClassLoader根据CLASSPATH环境变量值在本地文件系统中寻找合适字节码文件
应用定制ClassLoader要求对这个过程有较为深入认识我们首先必须创建个定制ClassLoader类例子然后显式地要求它装入另外个类这就强制JVM把该类以及所有它所需要类关联到定制ClassLoaderListing1显示了如何用定制ClassLoader装入类文件
【Listing1:利用定制ClassLoader装入类文件】
//首先创建个ClassLoader对象
ClassLoadermyClassLoader=myClassLoader;
//利用定制ClassLoader对象装入类文件
//并把它转换成Class对象
ClassmyClass=myClassLoader.loadClass(\"mypackage.MyClass\");
//最后创建该类个例子
ObjectInstance=myClass.Instance;
//注意MyClass所需要所有其他类都将通过
//定制ClassLoader自动装入
如前所述定制ClassLoader只需先获取类文件数据然后把字节码传递给运行时系统由后者完成余下任务
ClassLoader有几个重要思路方法创建定制ClassLoader时我们只需覆盖其中个即loadClass提供获取原始类文件数据代码这个思路方法有两个参数:类名字以及个表示JVM是否要求解析类名字标记(即是否同时装入有依赖关系类)如果这个标记是true我们只需在返回JVM的前resolveClass
【Listing2:ClassLoader.loadClass个简单实现】
publicClassloadClass(Stringname,booleanresolve)
throwsClassNotFoundException{
try{
//我们要创建Class对象
Classclasz=null;
//必需步骤1:如果类已经在系统缓冲的中
//我们不必再次装入它
clasz=findLoadedClass(name);
(clasz!=null)
clasz;
//下面是定制部分
Data=/*通过某种思路方法获取字节码数据*/;
(Data!=null){
//成功读取字节码数据现在把它转换成个Class对象
clasz=Class(name,Data,0,Data.length);
}
//必需步骤2:如果上面没有成功
//我们尝试用默认ClassLoader装入它
(clasznull)
clasz=findClass(name);
//必需步骤3:如有必要则装入相关类
(resolve&&clasz!=null)
resolveClass(clasz);
//把类返回给者
clasz;
}catch(IOExceptionie){
throwClassNotFoundException(ie.toString);
}catch(GeneralSecurityExceptiongse){
throwClassNotFoundException(gse.toString);
}
}
Listing2显示了个简单loadClass实现代码中大部分对所有ClassLoader对象来说都样但有小部分(已通过注释标记)是特有在处理过程中ClassLoader对象要用到其他几个辅助思路方法:
findLoadedClass:用来进行检查以便确认被请求类当前还不存在loadClass思路方法应该首先它
Class:获得原始类文件字节码数据的后Class把它转换成个Class对象任何loadClass实现都必须这个思路方法
findClass:提供默认ClassLoader支持如果用来寻找类定制思路方法不能找到指定类(或者有意地不用定制思路方法)则可以该思路方法尝试默认装入方式这是很有用特别是从普通JAR文件装入标准Java类时
resolveClass:当JVM想要装入不仅包括指定类而且还包括该类引用所有其他类时它会把loadClassresolve参数设置成true这时我们必须在返回刚刚装入Class对象给者的前resolveClass
3、加密、解密
Java加密扩展即JavaCryptographyExtension简称JCE它是Sun加密服务软件Software包含了加密和密匙生成功能JCE是JCA(JavaCryptographyArchitecture)种扩展
JCE没有规定具体加密算法但提供了个框架加密算法具体实现可以作为服务提供者加入除了JCE框架的外JCE软件Software包还包含了SunJCE服务提供者其中包括许多有用加密算法比如DES(DataEncryptionStandard)和Blowfish
为简单计在本文中我们将用DES算法加密和解密字节码下面是用JCE加密和解密数据必须遵循基本步骤:
步骤1:生成个安全密匙在加密或解密任何数据的前需要有个密匙密匙是随同被加密应用起发布小段数据Listing3显示了如何生成个密匙【Listing3:生成个密匙】
//DES算法要求有个可信任随机数源
SecureRandomsr=SecureRandom;
//为我们选择DES算法生成个KeyGenerator对象
KeyGeneratorkg=KeyGenerator.getInstance(\"DES\");
kg.init(sr);
//生成密匙
SecretKeykey=kg.generateKey;
//获取密匙数据
rawKeyData=key.getEncoded;
/*接下来就可以用密匙进行加密或解密或者把它保存
为文件供以后使用*/
doSomething(rawKeyData);
步骤2:加密数据得到密匙的后接下来就可以用它加密数据除了解密ClassLoader的外般还要有个加密待发布应用独立(见Listing4)【Listing4:用密匙加密原始数据】
//DES算法要求有个可信任随机数源
SecureRandomsr=SecureRandom;
rawKeyData=/*用某种思路方法获得密匙数据*/;
//从原始密匙数据创建DESKeySpec对象
DESKeySpecdks=DESKeySpec(rawKeyData);
//创建个密匙工厂然后用它把DESKeySpec转换成
//个SecretKey对象
SecretKeyFactorykeyFactory=SecretKeyFactory.getInstance(\"DES\");
SecretKeykey=keyFactory.generateSecret(dks);
//Cipher对象实际完成加密操作
Ciphercipher=Cipher.getInstance(\"DES\");
//用密匙化Cipher对象
cipher.init(Cipher.ENCRYPT_MODE,key,sr);
//现在获取数据并加密
data=/*用某种思路方法获取数据*/
//正式执行加密操作
encryptedData=cipher.doFinal(data);
//进步处理加密后数据
doSomething(encryptedData);
步骤3:解密数据运行经过加密应用时ClassLoader分析并解密类文件操作步骤如Listing5所示【Listing5:用密匙解密数据】
//DES算法要求有个可信任随机数源
SecureRandomsr=SecureRandom;
rawKeyData=/*用某种思路方法获取原始密匙数据*/;
//从原始密匙数据创建个DESKeySpec对象
DESKeySpecdks=DESKeySpec(rawKeyData);
//创建个密匙工厂然后用它把DESKeySpec对象转换成
//个SecretKey对象
SecretKeyFactorykeyFactory=SecretKeyFactory.getInstance(\"DES\");
SecretKeykey=keyFactory.generateSecret(dks);
//Cipher对象实际完成解密操作
Ciphercipher=Cipher.getInstance(\"DES\");
//用密匙化Cipher对象
cipher.init(Cipher.DECRYPT_MODE,key,sr);
//现在获取数据并解密
encryptedData=/*获得经过加密数据*/
//正式执行解密操作
decryptedData=cipher.doFinal(encryptedData);
//进步处理解密后数据
doSomething(decryptedData);
4、应用例子
前面介绍了如何加密和解密数据要部署个经过加密应用步骤如下:
步骤1:创建应用我们例子包含个App主类两个辅助类(分别称为Foo和Bar)这个应用没有什么实际功用但只要我们能够加密这个应用加密其他应用也就不在话下
步骤2:生成个安全密匙在命令行利用GenerateKey工具(参见GenerateKey.java)把密匙写入个文件:%javaGenerateKeykey.data
步骤3:加密应用在命令行利用EncryptClasses工具(参见EncryptClasses.java)加密应用类:%javaEncryptClasseskey.dataApp.Foo.Bar.
该命令把每个.文件替换成它们各自加密版本
步骤4:运行经过加密应用用户通过个DecryptStart运行经过加密应用DecryptStart如Listing6所示【Listing6:DecryptStart.java启动被加密应用】
importjava.io.*;
importjava.security.*;
importjava.lang.reflect.*;
importjavax.crypto.*;
importjavax.crypto.spec.*;
publicDecryptStartextendsClassLoader
{
//这些对象在构造中设置
//以后loadClass思路方法将利用它们解密类
privateSecretKeykey;
privateCiphercipher;
//构造:设置解密所需要对象
publicDecryptStart(SecretKeykey)throwsGeneralSecurityException,
IOException{
this.key=key;
Stringalgorithm=\"DES\";
SecureRandomsr=SecureRandom;
.err.prln(\"[DecryptStart:creatingcipher]\");
cipher=Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE,key,sr);
}
//过程:我们要在这里读入密匙创建DecryptStart
//例子它就是我们定制ClassLoader
//设置好ClassLoader以后我们用它装入应用例子
//最后我们通过JavaReflectionAPI应用例子思路方法
publicvoid(Stringargs)throwsException{
StringkeyFilename=args[0];
StringappName=args[1];
//这些是传递给应用本身参数
StringrealArgs=String[args.length-2];
..gif' />copy(args,2,realArgs,0,args.length-2);
//读取密匙
.err.prln(\"[DecryptStart:readingkey]\");
rawKey=Util.readFile(keyFilename);
DESKeySpecdks=DESKeySpec(rawKey);
SecretKeyFactorykeyFactory=SecretKeyFactory.getInstance(\"DES\");
SecretKeykey=keyFactory.generateSecret(dks);
//创建解密ClassLoader
DecryptStartdr=DecryptStart(key);
//创建应用主类个例子
//通过ClassLoader装入它
.err.prln(\"[DecryptStart:loading\"+appName+\"]\");
Classclasz=dr.loadClass(appName);
//最后通过ReflectionAPI应用例子
//思路方法
//获取个对引用
Stringproto=String[1];
ClassArgs={(String[1]).getClass};
Method=clasz.getMethod(\"\",Args);
//创建个包含思路方法参数
ObjectargsArray={realArgs};
.err.prln(\"[DecryptStart:running\"+appName+\".]\");
//
.invoke(null,argsArray);
}
publicClassloadClass(Stringname,booleanresolve)
throwsClassNotFoundException{
try{
//我们要创建Class对象
Classclasz=null;
//必需步骤1:如果类已经在系统缓冲的中
//我们不必再次装入它
clasz=findLoadedClass(name);
(clasz!=null)
clasz;
//下面是定制部分
try{
//读取经过加密类文件
Data=Util.readFile(name+\".\");
(Data!=null){
//解密...
decryptedClassData=cipher.doFinal(Data);
//...再把它转换成个类
clasz=Class(name,decryptedClassData,
0,decryptedClassData.length);
.err.prln(\"[DecryptStart:decrypting\"+name+\"]\");
}
}catch(FileNotFoundExceptionfnfe){
}
//必需步骤2:如果上面没有成功
//我们尝试用默认ClassLoader装入它
(clasznull)
clasz=findClass(name);
//必需步骤3:如有必要则装入相关类
(resolve&&clasz!=null)
resolveClass(clasz);
//把类返回给者
clasz;
}catch(IOExceptionie){
throwClassNotFoundException(ie.toString
);
}catch(GeneralSecurityExceptiongse){
throwClassNotFoundException(gse.toString
);
}
}
}
对于未经加密应用正常执行方式如下:%javaApparg0arg1arg2
对于经过加密应用则相应运行方式为:%javaDecryptStartkey.dataApparg0arg1arg2
DecryptStart有两个目个DecryptStart例子就是个实施即时解密操作定制ClassLoader;同时DecryptStart还包含个过程它创建解密器例子并用它装入和运行应用举例应用App代码包含在App.java、Foo.java和Bar.java内Util.java是个文件I/O工具本文举例多处用到了它完整代码请从本文最后下载
5、注意事项
我们看到要在不修改源代码情况下加密个Java应用是很容易不过世上没有完全安全系统本文加密方式提供了定程度源代码保护但对某些攻击来说它是脆弱
虽然应用本身经过了加密但启动DecryptStart没有加密攻击者可以反编译启动并修改它把解密后类文件保存到磁盘降低这种风险办法的是对启动进行高质量模糊处理或者启动也可以采用直接编译成机器语言代码使得启动具有传统执行文件格式安全性
另外还要记住是大多数JVM本身并不安全狡猾黑客可能会修改JVM从ClassLoader的外获取解密后代码并保存到磁盘从而绕过本文加密技术Java没有为此提供真正有效补救措施
不过应该指出是所有这些可能攻击都有个前提这就是攻击者可以得到密匙如果没有密匙应用安全性就完全取决于加密算法安全性虽然这种保护代码思路方法称不上十全十美但它仍不失为种保护知识产权和敏感用户数据有效方案
参考资源
在运行时刻更新功能模块介绍了个利用类库加载器ClassLoader实现在运行时刻更新部分功能模块Java并将其和C/C中实现同样功能动态链接库方案进行了比较
Java窍门技巧105:利用JWhich掌握类路径展示个简单工具它可以清楚地确定类装载器从类路径中载入了什么Java类
要了解更多Java安全信息请阅读java.sun.comJavaSecurityAPI页
如何封锁您(或打开别人)Java代码Java代码反编译和模糊处理指南
使您软件Software运行起来:摆弄数字真正安全软件Software需要精确随机数生成器
下载本文代码:EncryptedJavaClass_code.zip
有关作者
俞良松软件Software工程师独立顾问和自由撰稿人最初从事PB和Oracle开发现主要兴趣在于Internet开发您可以通过[email protected]和我联系
最新评论