运⽤加密技术保护Java源代码
为什么要加密?
对于传统的C或C++之类的语⾔来说,要在Web上保护源代码是很容易的,只要不发布它就可以。遗憾的是,Java程序的源代码很容易被别⼈偷看。只要有⼀个反编译器,任何⼈都可以分析别⼈的代码。Java的灵活性使得源代码很容易被窃取,但与此同时,它也使通过加密保护代码变得相对容易,我们唯⼀需要了解的就是Java的ClassLoader对象。当然,在加密过程中,有关Java Cryptography
Extension(JCE)的知识也是必不可少的。
有⼏种技术可以“模糊”Java类⽂件,使得反编译器处理类⽂件的效果⼤打折扣。然⽽,修改反编译器使之能够处理这些经过模糊处理的类⽂件并不是什么难事,所以不能简单地依赖模糊技术来保证源代码的安全。
我们可以⽤流⾏的加密⼯具加密应⽤,⽐如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard)。这时,最终⽤户在运⾏应⽤之前必须先进⾏解密。但解密之后,最终⽤户就有了⼀份不加密的类⽂件,这和事先不进⾏加密没有什么差别。
Java运⾏时装⼊字节码的机制隐含地意味着可以对字节码进⾏修改。JVM每次装⼊类⽂件时都需要⼀个
称为ClassLoader的对象,这个对象负责把新的类装⼊正在运⾏的JVM。JVM给ClassLoader⼀个包含了待装⼊类(⽐如java.lang.Object)名字的字符串,然后由ClassLoader负责到类⽂件,装⼊原始数据,并把它转换成⼀个Class对象。
我们可以通过定制ClassLoader,在类⽂件执⾏之前修改它。这种技术的应⽤⾮常⼴泛――在这⾥,它的⽤途是在类⽂件装⼊之时进⾏解密,因此可以看成是⼀种即时解密器。由于解密后的字节码⽂件永远不会保存到⽂件系统,所以窃密者很难得到解密后的代码。
由于把原始字节码转换成Class对象的过程完全由系统负责,所以创建定制ClassLoader对象其实并不困难,只需先获得原始数据,接着就可以进⾏包含解密在内的任何转换。
Java 2在⼀定程度上简化了定制ClassLoader的构建。在Java 2中,loadClass的缺省实现仍旧负责处理所有必需的步骤,但为了顾及各种定制的类装⼊过程,它还调⽤⼀个新的findClass⽅法。
这为我们编写定制的ClassLoader提供了⼀条捷径,减少了⿇烦:只需覆盖findClass,⽽不是覆盖loadClass。这种⽅法避免了重复所有装⼊器必需执⾏的公共步骤,因为这⼀切由loadClass负责。
不过,本⽂的定制ClassLoader并不使⽤这种⽅法。原因很简单。如果由默认的ClassLoader先寻经过加密的类⽂件,它可以到;但由于类⽂件已经加密,所以它不会认可这个类⽂件,装⼊过程将失败。因此,我们必须⾃⼰实现loadClass,稍微增加了⼀些⼯作量。
定制类装⼊器
每⼀个运⾏着的JVM已经拥有⼀个ClassLoader。这个默认的ClassLoader根据CLASSPATH环境变量的值,在本地⽂件系统中寻合适的字节码⽂件。
应⽤定制ClassLoader要求对这个过程有较为深⼊的认识。我们⾸先必须创建⼀个定制ClassLoader类的实例,然后显式地要求它装⼊另外⼀个类。这就强制JVM把该类以及所有它所需要的类关联到定制的ClassLoader。Listing 1显⽰了如何⽤定制ClassLoader装⼊类⽂件。
1 2 3 4 5 6 7 8 9 10【Listing 1:利⽤定制的ClassLoader装⼊类⽂件】
// ⾸先创建⼀个ClassLoader对象
ClassLoader myClassLoader = new myClassLoader();
// 利⽤定制ClassLoader对象装⼊类⽂件
// 并把它转换成Class对象
Class myClass = myClassLoader.loadClass( "mypackage.MyClass" ); // 最后,创建该类的⼀个实例
Object newInstance = wInstance();
// 注意,MyClass所需要的所有其他类,都将通过
// 定制的ClassLoader⾃动装⼊
如前所述,定制ClassLoader只需先获取类⽂件的数据,然后把字节码传递给运⾏时系统,由后者完成余下的任务。
ClassLoader有⼏个重要的⽅法。创建定制的ClassLoader时,我们只需覆盖其中的⼀个,即loadClass,提供获取原始类⽂件数据的代码。这个⽅法有两个参数:类的名字,以及⼀个表⽰JVM是否要求解析类名字的标记(即是否同时装⼊有依赖关系的类)。如果这个标记是true,我们只需在返回JVM之前调⽤resolveClass。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32【Listing 2:ClassLoader.loadClass()的⼀个简单实现】
public Class loadClass( String name, boolean resolve )
throws ClassNotFoundException {
try {
// 我们要创建的Class对象
Class clasz = null;
// 必需的步骤1:如果类已经在系统缓冲之中,
// 我们不必再次装⼊它
clasz = findLoadedClass( name );
if (clasz != null)
return clasz;
// 下⾯是定制部分
byte classData[] = /* 通过某种⽅法获取字节码数据 */;
if (classData != null) {
/
/ 成功读取字节码数据,现在把它转换成⼀个Class对象
clasz = defineClass( name, classData, 0, classData.length ); }
// 必需的步骤2:如果上⾯没有成功,
// 我们尝试⽤默认的ClassLoader装⼊它
if (clasz == null)
clasz = findSystemClass( name );
// 必需的步骤3:如有必要,则装⼊相关的类
if (resolve && clasz != null)
resolveClass( clasz );
// 把类返回给调⽤者
return clasz;
} catch( IOException ie ) {
throw new ClassNotFoundException( ie.toString() );
} catch( GeneralSecurityException gse ) {
throw new ClassNotFoundException( String() );
}
}
Listing 2显⽰了⼀个简单的loadClass实现。代码中的⼤部分对所有ClassLoader对象来说都⼀样,但有⼀⼩部分(已通过注释标记)是特有的。在处理过程中,ClassLoader对象要⽤到其他⼏个辅助⽅法:
findLoadedClass:⽤来进⾏检查,以便确认被请求的类当前还不存在。loadClass⽅法应该⾸先调⽤它。
defineClass:获得原始类⽂件字节码数据之后,调⽤defineClass把它转换成⼀个Class对象。任何loadClass实现都必须调⽤这个⽅法。
findSystemClass:提供默认ClassLoader的⽀持。如果⽤来寻类的定制⽅法不能到指定的类(或者有意地不⽤定制⽅法),则可以调⽤该⽅法尝试默认的装⼊⽅式。这是很有⽤的,特别是从普通的JAR⽂件装⼊标准Java类时。
resolveClass:当JVM想要装⼊的不仅包括指定的类,⽽且还包括该类引⽤的所有其他类时,它会把loadClass的resolve参数设置成true。这时,我们必须在返回刚刚装⼊的Class对象给调⽤者之前调⽤resolveClass。
加密、解密
Java加密扩展即Java Cryptography Extension,简称JCE。它是Sun的加密服务软件,包含了加密和密匙⽣成功能。JCE是JCA(Java Cryptography Architecture)的⼀种扩展。
JCE没有规定具体的加密算法,但提供了⼀个框架,加密算法的具体实现可以作为服务提供者加⼊。除了JCE框架之外,JCE软件包还包含了SunJCE服务提供者,其中包括许多有⽤的加密算法,⽐如DES(Data Encryption Standard)和Blowfish。
为简单计,在本⽂中我们将⽤DES算法加密和解密字节码。下⾯是⽤JCE加密和解密数据必须遵循的基本步骤:
步骤1:⽣成⼀个安全密匙。在加密或解密任何数据之前需要有⼀个密匙。密匙是随同被加密的应⽤⼀起发布的⼀⼩段数据,Listing 3显⽰了如何⽣成⼀个密匙。
1 2 3 4 5 6 7 8 9 10 11 12 13【Listing 3:⽣成⼀个密匙】
// DES算法要求有⼀个可信任的随机数源
SecureRandom sr = new SecureRandom();
// 为我们选择的DES算法⽣成⼀个KeyGenerator对象 KeyGenerator kg = Instance( "DES" ); kg.init( sr );
// ⽣成密匙
SecretKey key = kg.generateKey();
// 获取密匙数据
byte rawKeyData[] = Encoded();
/* 接下来就可以⽤密匙进⾏加密或解密,或者把它保存为⽂件供以后使⽤ */
doSomething( rawKeyData );
步骤2:加密数据。得到密匙之后,接下来就可以⽤它加密数据。除了解密的ClassLoader之外,⼀般还要有⼀个加密待发布应⽤的独⽴程序(见Listing 4)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20【Listing 4:⽤密匙加密原始数据】
// DES算法要求有⼀个可信任的随机数源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* ⽤某种⽅法获得密匙数据 */;
// 从原始密匙数据创建DESKeySpec对象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 创建⼀个密匙⼯⼚,然后⽤它把DESKeySpec转换成
// ⼀个SecretKey对象
SecretKeyFactory keyFactory = Instance( "DES" ); SecretKey key = ateSecret( dks );
// Cipher对象实际完成加密操作
Cipher cipher = Instance( "DES" );
// ⽤密匙初始化Cipher对象
cipher.init( Cipher.ENCRYPT_MODE, key, sr );
// 现在,获取数据并加密
byte data[] = /* ⽤某种⽅法获取数据 */
// 正式执⾏加密操作
byte encryptedData[] = cipher.doFinal( data );
// 进⼀步处理加密后的数据
doSomething( encryptedData );
步骤3:解密数据。运⾏经过加密的应⽤时,ClassLoader分析并解密类⽂件。操作步骤如Listing 5所⽰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14【Listing 5:⽤密匙解密数据】
// DES算法要求有⼀个可信任的随机数源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* ⽤某种⽅法获取原始密匙数据 */;
// 从原始密匙数据创建⼀个DESKeySpec对象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 创建⼀个密匙⼯⼚,然后⽤它把DESKeySpec对象转换成
// ⼀个SecretKey对象
SecretKeyFactory keyFactory = Instance( "DES" ); SecretKey key = ateSecret( dks );
// Cipher对象实际完成解密操作
Cipher cipher = Instance( "DES" );
// ⽤密匙初始化Cipher对象
cipher.init( Cipher.DECRYPT_MODE, key, sr );
15 16 17 18 19 20 // 现在,获取数据并解密
byte encryptedData[] = /* 获得经过加密的数据 */
// 正式执⾏解密操作
byte decryptedData[] = cipher.doFinal( encryptedData ); // 进⼀步处理解密后的数据
doSomething( decryptedData );
应⽤实例
前⾯介绍了如何加密和解密数据。要部署⼀个经过加密的应⽤,步骤如下:
1. 步骤1:创建应⽤。我们的例⼦包含⼀个App主类,两个辅助类(分别称为Foo和Bar)。这个应⽤没有什么实际功⽤,但只要我们能
够加密这个应⽤,加密其他应⽤也就不在话下。
2. 步骤2:⽣成⼀个安全密匙。在命令⾏,利⽤GenerateKey⼯具(参见GenerateKey.java)把密匙写⼊⼀个⽂件:
1% java GenerateKey key.data
3. 步骤3:加密应⽤。在命令⾏,利⽤EncryptClasses⼯具(参见EncryptClasses.java)加密应⽤的类:
1% java EncryptClasses key.data App.class Foo.class Bar.class
该命令把每⼀个.class⽂件替换成它们各⾃的加密版本。
4. 步骤4:运⾏经过加密的应⽤。⽤户通过⼀个DecryptStart程序运⾏经过加密的应⽤。DecryptStart程序如Listing 6所⽰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31【Listing 6:DecryptStart.java,启动被加密应⽤的程序】
import java.io.*;
import java.security.*;
import flect.*;
pto.*;
pto.spec.*;
public class DecryptStart extends ClassLoader
{
// 这些对象在构造函数中设置,
// 以后loadClass()⽅法将利⽤它们解密类
private SecretKey key;
private Cipher cipher;
// 构造函数:设置解密所需要的对象
public DecryptStart( SecretKey key ) throws GeneralSecurityException, IOException {
this.key = key;
String algorithm = "DES";
SecureRandom sr = new SecureRandom();
cipher = Instance( algorithm );
cipher.init( Cipher.DECRYPT_MODE, key, sr );
}
// main过程:我们要在这⾥读⼊密匙,创建DecryptStart的
/
/ 实例,它就是我们的定制ClassLoader。
// 设置好ClassLoader以后,我们⽤它装⼊应⽤实例,
// 最后,我们通过Java Reflection API调⽤应⽤实例的main⽅法
static public void main( String args[] ) throws Exception {
String keyFilename = args[0];
String appName = args[1];
// 这些是传递给应⽤本⾝的参数
String realArgs[] = new String[args.length-2];
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 System.arraycopy( args, 2, realArgs, 0, args.length-2 );
// 读取密匙
byte rawKey[] = adFile( keyFilename );
DESKeySpec dks = new DESKeySpec( rawKey );
SecretKeyFactory keyFactory = Instance( "DES" ); SecretKey key = ateSecret( dks );
// 创建解密的ClassLoader
DecryptStart dr = new DecryptStart( key );
// 创建应⽤主类的⼀个实例
// 通过ClassLoader装⼊它
java源代码加密Class clasz = dr.loadClass( appName );
// 最后,通过Reflection API调⽤应⽤实例
// 的main()⽅法
// 获取⼀个对main()的引⽤
String proto[] = new String[1];
Class mainArgs[] = { (new String[1]).getClass() };
Method main = Method( "main", mainArgs );
// 创建⼀个包含main()⽅法参数的数组
Object argsArray[] = { realArgs };
// 调⽤main()
main.invoke( null, argsArray );
}
public Class loadClass( String name, boolean resolve )
throws ClassNotFoundException {
try {
// 我们要创建的Class对象
Class clasz = null;
// 必需的步骤1:如果类已经在系统缓冲之中
// 我们不必再次装⼊它
clasz = findLoadedClass( name );
if (clasz != null)
return clasz;
// 下⾯是定制部分
try {
// 读取经过加密的类⽂件
byte classData[] = adFile( name+".class" );
if (classData != null) {
// 解密...
byte decryptedClassData[] = cipher.doFinal( classData );
// ... 再把它转换成⼀个类
clasz = defineClass( name, decryptedClassData,
0, decryptedClassData.length );
}
} catch( FileNotFoundException fnfe ) {
}
// 必需的步骤2:如果上⾯没有成功
// 我们尝试⽤默认的ClassLoader装⼊它
if (clasz == null)
clasz = findSystemClass( name );
// 必需的步骤3:如有必要,则装⼊相关的类
if (resolve && clasz != null)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论