- 浏览: 863319 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
yukang1:
蚂蚁lovejing 写道我也出现与楼上相同的问题。查了一些资 ...
Spring中实现文件上传 -
史玉凤:
必须用ie浏览器
javascript获取客户端网卡MAC地址和IP地址和计算机名 -
蚂蚁lovejing:
我也出现与楼上相同的问题。查了一些资料,描述的跟楼主的博文差不 ...
Spring中实现文件上传 -
温柔一刀:
djlijian 写道最近也在研究redis,如何在项目中使用 ...
Redis 常见的性能问题和解决方法 -
djlijian:
最近也在研究redis,如何在项目中使用呢?感觉网上的资料太少 ...
Redis 常见的性能问题和解决方法
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装入类档。
【Listing 1:利用定制的ClassLoader装入类档】
// 首先创建一个ClassLoader对象
ClassLoader myClassLoader = new myClassLoader();
// 利用定制ClassLoader对象装入类文件
// 并把它转换成Class对象
Class myClass = myClassLoader.loadClass( "mypackage.MyClass" );
// 最后,创建该类的一个实例
Object newInstance = myClass.newInstance();
// 注意,MyClass所需要的所有其它类,都将通过
// 定制的ClassLoader自动装入
如前所述,定制ClassLoader只需先获取类文件的数据,然后把字节码传递给运行时系统,由后者完成余下的任务。
ClassLoader有几个重要的方法。创建定制的ClassLoader时,我们只需覆盖其中的一个,即loadClass,提供获取原始类文件数据的代码。这个方法有两个参数:类的名字,以及一个表示JVM是否要求解析类名字的标记(即是否同时装入有依赖关系的类)。如果这个标记是true,我们只需在返回JVM之前调用resolveClass。
【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( gse.toString() );
}
}
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显示了如何生成一个密匙。
【Listing 3:生成一个密匙】
// DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
// 为我们选择的DES算法生成一个KeyGenerator对象
KeyGenerator kg = KeyGenerator.getInstance( "DES" );
kg.init( sr );
// 生成密匙
SecretKey key = kg.generateKey();
// 获取密匙资料
byte rawKeyData[] = key.getEncoded();
/* 接下来就可以用密匙进行加密或解密,或者把它保存
为档供以后使用 */
doSomething( rawKeyData );
•
• 步骤2:加密数据。得到密匙之后,接下来就可以用它加密数据。除了解密的ClassLoader之外,一般还要有一个加密待发布应用的独立程序(见Listing
4)。
【Listing 4:用密匙加密原始资料】
// DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某种方法获得密匙数据 */;
// 从原始密匙数据创建DESKeySpec对象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 创建一个密匙工厂,然后用它把DESKeySpec转换成
// 一个SecretKey物件
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher对象
cipher.init( Cipher.ENCRYPT_MODE, key, sr );
// 现在,获取数据并加密
byte data[] = /* 用某种方法获取数据 */
// 正式执行加密操作
byte encryptedData[] = cipher.doFinal( data );
// 进一步处理加密后的数据
doSomething( encryptedData );
•
• 步骤3:解密数据。运行经过加密的应用时,ClassLoader分析并解密类档。操作步骤如Listing 5所示。
【Listing 5:用密匙解密数据】
// DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某种方法获取原始密匙数据 */;
// 从原始密匙数据创建一个DESKeySpec对象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 创建一个密匙工厂,然后用它把DESKeySpec对象转换成
// 一个SecretKey物件
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher对象
cipher.init( Cipher.DECRYPT_MODE, key, sr );
// 现在,获取数据并解密
byte encryptedData[] = /* 获得经过加密的资料 */
// 正式执行解密操作
byte decryptedData[] = cipher.doFinal( encryptedData );
// 进一步处理解密后的资料
doSomething( decryptedData );
•
四、应用实例
前面介绍了如何加密和解密数据。要部署一个经过加密的应用,步骤如下:
1. 步骤1:创建应用。我们的例子包含一个App主类,两个辅助类(分别称为Foo和Bar)。这个应用没有什么实际功用,但只要我们能够加密这个应用,加密其它应用也就不在话下。
2. 步骤2:生成一个安全密匙。在命令行,利用GenerateKey工具(参见GenerateKey.java)把密匙写入一个档:
% java GenerateKey key.data
3.
4. 步骤3:加密应用。在命令行,利用EncryptClasses工具(参见EncryptClasses.java)加密应用的类:
% java EncryptClasses key.data App.class Foo.class Bar.class
5.
该命令把每一个.class档替换成它们各自的加密版本。
6. 步骤4:运行经过加密的应用。用户通过一个DecryptStart程序运行经过加密的应用。DecryptStart程序如Listing
6所示。
【Listing 6:DecryptStart.java,启动被加密应用的程序】
import java.io.*;
import java.security.*;
import java.lang.reflect.*;
import javax.crypto.*;
import javax.crypto.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();
System.err.println( "[DecryptStart: creating cipher]" );
cipher = Cipher.getInstance( 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];
System.arraycopy( args, 2, realArgs, 0, args.length-2 );
// 读取密匙
System.err.println( "[DecryptStart: reading key]" );
byte rawKey[] = Util.readFile( keyFilename );
DESKeySpec dks = new DESKeySpec( rawKey );
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// 创建解密的ClassLoader
DecryptStart dr = new DecryptStart( key );
// 创建应用主类的一个实例
// 通过ClassLoader装入它
System.err.println( "[DecryptStart: loading "+appName+"]" );
Class clasz = dr.loadClass( appName );
// 最后,通过Reflection API调用应用实例
// 的main()方法
// 获取一个对main()的引用
String proto[] = new String[1];
Class mainArgs[] = { (new String[1]).getClass() };
Method main = clasz.getMethod( "main", mainArgs );
// 创建一个包含main()方法参数的数组
Object argsArray[] = { realArgs };
System.err.println( "[DecryptStart: running "+appName+".main()]" );
// 调用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[] = Util.readFile( name+".class" );
if (classData != null) {
// 解密...
byte decryptedClassData[] = cipher.doFinal( classData );
// ... 再把它转换成一个类
clasz = defineClass( name, decryptedClassData,
0, decryptedClassData.length );
System.err.println( "[DecryptStart: decrypting class "+name+"]" );
}
} catch( FileNotFoundException fnfe ) {
}
// 必需的步骤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( gse.toString()
);
}
}
}
7.
对于未经加密的应用,正常执行方式如下:
% java App arg0 arg1 arg2
8.
对于经过加密的应用,则相应的运行方式为:
% java DecryptStart key.data App arg0 arg1 arg2
9.
DecryptStart有两个目的。一个DecryptStart的实例就是一个实施实时解密操作的定制ClassLoader;同时,DecryptStart还包含一个main过程,它创建解密器实例并用它装入和运行应用。示例应用App的代码包含在App.java、Foo.java和Bar.java内。Util.java是一个档I/O工具,本文示例多处用到了它。完整的代码请从本文最后下载。
五、注意事项
我们看到,要在不修改源代码的情况下加密一个Java应用是很容易的。不过,世上没有完全安全的系统。本文的加密方式提供了一定程度的源代码保护,但对某些攻击来说它是脆弱的。
虽然应用本身经过了加密,但启动程序DecryptStart没有加密。攻击者可以反编译启动程序并修改它,把解密后的类文件保存到磁盘。降低这种风险的办法之一是对启动程序进行高质量的模糊处理。或者,启动程序也可以采用直接编译成机器语言的代码,使得启动程序具有传统执行文件格式的安全性。
另外还要记住的是,大多数JVM本身并不安全。狡猾的黑客可能会修改JVM,从ClassLoader之外获取解密后的代码并保存到磁盘,从而绕过本文的加密技术。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装入类档。
【Listing 1:利用定制的ClassLoader装入类档】
// 首先创建一个ClassLoader对象
ClassLoader myClassLoader = new myClassLoader();
// 利用定制ClassLoader对象装入类文件
// 并把它转换成Class对象
Class myClass = myClassLoader.loadClass( "mypackage.MyClass" );
// 最后,创建该类的一个实例
Object newInstance = myClass.newInstance();
// 注意,MyClass所需要的所有其它类,都将通过
// 定制的ClassLoader自动装入
如前所述,定制ClassLoader只需先获取类文件的数据,然后把字节码传递给运行时系统,由后者完成余下的任务。
ClassLoader有几个重要的方法。创建定制的ClassLoader时,我们只需覆盖其中的一个,即loadClass,提供获取原始类文件数据的代码。这个方法有两个参数:类的名字,以及一个表示JVM是否要求解析类名字的标记(即是否同时装入有依赖关系的类)。如果这个标记是true,我们只需在返回JVM之前调用resolveClass。
【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( gse.toString() );
}
}
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显示了如何生成一个密匙。
【Listing 3:生成一个密匙】
// DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
// 为我们选择的DES算法生成一个KeyGenerator对象
KeyGenerator kg = KeyGenerator.getInstance( "DES" );
kg.init( sr );
// 生成密匙
SecretKey key = kg.generateKey();
// 获取密匙资料
byte rawKeyData[] = key.getEncoded();
/* 接下来就可以用密匙进行加密或解密,或者把它保存
为档供以后使用 */
doSomething( rawKeyData );
•
• 步骤2:加密数据。得到密匙之后,接下来就可以用它加密数据。除了解密的ClassLoader之外,一般还要有一个加密待发布应用的独立程序(见Listing
4)。
【Listing 4:用密匙加密原始资料】
// DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某种方法获得密匙数据 */;
// 从原始密匙数据创建DESKeySpec对象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 创建一个密匙工厂,然后用它把DESKeySpec转换成
// 一个SecretKey物件
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher对象
cipher.init( Cipher.ENCRYPT_MODE, key, sr );
// 现在,获取数据并加密
byte data[] = /* 用某种方法获取数据 */
// 正式执行加密操作
byte encryptedData[] = cipher.doFinal( data );
// 进一步处理加密后的数据
doSomething( encryptedData );
•
• 步骤3:解密数据。运行经过加密的应用时,ClassLoader分析并解密类档。操作步骤如Listing 5所示。
【Listing 5:用密匙解密数据】
// DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某种方法获取原始密匙数据 */;
// 从原始密匙数据创建一个DESKeySpec对象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 创建一个密匙工厂,然后用它把DESKeySpec对象转换成
// 一个SecretKey物件
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher对象
cipher.init( Cipher.DECRYPT_MODE, key, sr );
// 现在,获取数据并解密
byte encryptedData[] = /* 获得经过加密的资料 */
// 正式执行解密操作
byte decryptedData[] = cipher.doFinal( encryptedData );
// 进一步处理解密后的资料
doSomething( decryptedData );
•
四、应用实例
前面介绍了如何加密和解密数据。要部署一个经过加密的应用,步骤如下:
1. 步骤1:创建应用。我们的例子包含一个App主类,两个辅助类(分别称为Foo和Bar)。这个应用没有什么实际功用,但只要我们能够加密这个应用,加密其它应用也就不在话下。
2. 步骤2:生成一个安全密匙。在命令行,利用GenerateKey工具(参见GenerateKey.java)把密匙写入一个档:
% java GenerateKey key.data
3.
4. 步骤3:加密应用。在命令行,利用EncryptClasses工具(参见EncryptClasses.java)加密应用的类:
% java EncryptClasses key.data App.class Foo.class Bar.class
5.
该命令把每一个.class档替换成它们各自的加密版本。
6. 步骤4:运行经过加密的应用。用户通过一个DecryptStart程序运行经过加密的应用。DecryptStart程序如Listing
6所示。
【Listing 6:DecryptStart.java,启动被加密应用的程序】
import java.io.*;
import java.security.*;
import java.lang.reflect.*;
import javax.crypto.*;
import javax.crypto.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();
System.err.println( "[DecryptStart: creating cipher]" );
cipher = Cipher.getInstance( 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];
System.arraycopy( args, 2, realArgs, 0, args.length-2 );
// 读取密匙
System.err.println( "[DecryptStart: reading key]" );
byte rawKey[] = Util.readFile( keyFilename );
DESKeySpec dks = new DESKeySpec( rawKey );
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// 创建解密的ClassLoader
DecryptStart dr = new DecryptStart( key );
// 创建应用主类的一个实例
// 通过ClassLoader装入它
System.err.println( "[DecryptStart: loading "+appName+"]" );
Class clasz = dr.loadClass( appName );
// 最后,通过Reflection API调用应用实例
// 的main()方法
// 获取一个对main()的引用
String proto[] = new String[1];
Class mainArgs[] = { (new String[1]).getClass() };
Method main = clasz.getMethod( "main", mainArgs );
// 创建一个包含main()方法参数的数组
Object argsArray[] = { realArgs };
System.err.println( "[DecryptStart: running "+appName+".main()]" );
// 调用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[] = Util.readFile( name+".class" );
if (classData != null) {
// 解密...
byte decryptedClassData[] = cipher.doFinal( classData );
// ... 再把它转换成一个类
clasz = defineClass( name, decryptedClassData,
0, decryptedClassData.length );
System.err.println( "[DecryptStart: decrypting class "+name+"]" );
}
} catch( FileNotFoundException fnfe ) {
}
// 必需的步骤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( gse.toString()
);
}
}
}
7.
对于未经加密的应用,正常执行方式如下:
% java App arg0 arg1 arg2
8.
对于经过加密的应用,则相应的运行方式为:
% java DecryptStart key.data App arg0 arg1 arg2
9.
DecryptStart有两个目的。一个DecryptStart的实例就是一个实施实时解密操作的定制ClassLoader;同时,DecryptStart还包含一个main过程,它创建解密器实例并用它装入和运行应用。示例应用App的代码包含在App.java、Foo.java和Bar.java内。Util.java是一个档I/O工具,本文示例多处用到了它。完整的代码请从本文最后下载。
五、注意事项
我们看到,要在不修改源代码的情况下加密一个Java应用是很容易的。不过,世上没有完全安全的系统。本文的加密方式提供了一定程度的源代码保护,但对某些攻击来说它是脆弱的。
虽然应用本身经过了加密,但启动程序DecryptStart没有加密。攻击者可以反编译启动程序并修改它,把解密后的类文件保存到磁盘。降低这种风险的办法之一是对启动程序进行高质量的模糊处理。或者,启动程序也可以采用直接编译成机器语言的代码,使得启动程序具有传统执行文件格式的安全性。
另外还要记住的是,大多数JVM本身并不安全。狡猾的黑客可能会修改JVM,从ClassLoader之外获取解密后的代码并保存到磁盘,从而绕过本文的加密技术。Java没有为此提供真正有效的补救措施。
不过应该指出的是,所有这些可能的攻击都有一个前提,这就是攻击者可以得到密匙。如果没有密匙,应用的安全性就完全取决于加密算法的安全性。虽然这种保护代码的方法称不上十全十美,但它仍不失为一种保护知识产权和敏感用户数据的有效方案。
发表评论
-
Spring Integration 1.0 发布
2008-11-20 14:34 2137终于迎来了spring integration的第一个fina ... -
When To Use Mock Objects?
2008-06-10 10:18 1614The real object has nondeterm ... -
JACOB发送传真
2008-03-21 19:46 3826JACOB一个JAva-COM中间件.通过这个组件你可以在Ja ... -
IDEA快捷键
2007-04-30 12:16 5583最常用快捷键 1.Ctrl+E,可以显示最近编辑的文件列表 2 ... -
Ant+Tomcat自动部署的小问题
2007-02-14 18:00 8445今天用Ant对dms系统进行Tomcat的自动部署。 通过a ... -
FCKeditor-2.3在线编辑器,可以直接用的(java版)
2007-02-12 12:11 39460初次使用FCKeditor编辑器, 在网上搜索了半天也没有找 ... -
Java是剑客-飘逸;.NET是刀客-霸道
2006-12-26 19:17 4966网上看到的一篇 ... -
常用到的Eclipse快捷键
2006-11-11 12:13 3402Ctrl+s 存盘 Ctrl+/ ... -
java操作Excel、PDF文件
2006-10-09 16:41 36299下面这些是在开发中用到的一些东西,有的代码贴的不是完整的,只是 ... -
通过SCJP考试,说点体会和感受
2006-04-25 18:58 4888我就随便说说哈,不认可的朋友就当没有看见哈! 1、首先声明下 ... -
使用smartupload进行文件的上传下载
2006-06-07 23:30 5309jspSmartUpload是由www.jspsmart.co ... -
log4j的配置文件例子
2006-07-09 04:27 3027log4j的配置文件例子,非常方便的一个开源日志软件,一直用它 ... -
javaBean数据库连接池
2006-07-09 04:35 2729实现简单的数据库连接池: java 代码 / ... -
用java实现浮点数的精确计算
2006-07-13 22:28 2919问题的提出: 如果我们编译运行下面这个程序会看到什么? j ... -
工厂模式
2006-07-13 22:30 5136简单工厂模式 1. 目的 工厂模式就是专门负 ... -
61条面向对象设计的经验原则
2006-07-13 22:51 1858(1)所有数据都应该隐藏在所在的类的内部。 (2)类的使用者必 ... -
追MM与java的32种模式
2006-07-13 22:55 1840创建型模式 1、FACTORY—追MM少不了请吃饭了,麦当 ... -
OO的五大原则
2006-07-14 19:25 4761OO的五大原则是指SRP ... -
JAVA系列之对象的序列化与反序列化
2006-07-17 09:09 178371 简介 对象序列化(Seria ... -
java获取服务器端网卡地址
2006-08-08 19:43 4304java 代码 package com.ctgu ...
相关推荐
《JAVA加密和解密的艺术(第二版)》是一本深入探讨Java平台上的加密与解密技术的专业书籍。这本书不仅提供了理论知识,还包含了丰富的实践示例,帮助读者理解和掌握加密技术在实际应用中的运用。其内容涵盖了从基本...
java加密和解密的方法,利用指定的密钥,可逆的。密钥必须16位。
《Java加密与解密的艺术(第2版)》由梁栋著,以Java中的加密API和加密算法为切入点,全面介绍了Java SE 7的特性,及其中与安全相关的各种API,详细讲解了各种流行的加密算法及其在实际中的应用,为Java开发工程师和...
全书包含3个部分,基础篇对Java企业级应用的安全知识、密码学核心知识、与Java加密相关的API和通过权限文件加强系统安全方面的知识进行了全面的介绍;实践篇不仅对电子邮件传输算法、消息摘要算法、对称加密算法、非...
若要构建安全坚固的Java企业级应用,不仅要深入了解每种算法的原理并将它们综合运用,而且还要悟透Java加密与解密技术的本质。全书包含3个部分,基础篇对Java企业级应用的安全知识、密码学核心知识、与Java加密相关...
Java加密扩展基础是Java开发工具包(Java SDK)中的一个重要组件,自1.4版本起,JCE(Java Cryptography Extension)被纳入核心库,为Java开发者提供了强大的安全功能。这个扩展提供了一套完整的框架,使得开发人员...
《Java加密与解密的艺术(第2版)》由梁栋著,以Java中的加密API和加密算法为切入点,全面介绍了Java SE 7的特性,及其中与安全相关的各种API,详细讲解了各种流行的加密算法及其在实际中的应用,为Java开发工程师和...
Java加密程序源代码是关于使用Java编程语言进行数据加密和解密的技术实现。在Java中,我们可以使用多种方法来实现加密,这些方法通常基于标准的加密算法,如对称加密和非对称加密。对称加密算法如DES(Data ...
Java加密程序源代码是用于保护数据安全的重要工具,它通过特定的算法将原始信息转换成不可读的形式,防止未经授权的访问或泄露。本程序可能涵盖了对称加密、非对称加密以及哈希函数等多种加密技术。 对称加密是最早...
Java加密算法是信息安全领域的重要组成部分,它涉及到数据的保护、隐私的维护以及网络通信的安全。在Java中,我们可以使用多种加密技术来实现数据的加密和解密,包括对称加密、非对称加密以及数字签名等。下面将详细...
Java加密通常使用Java Cryptography Extension (JCE) 提供的API。JCE包含各种加密算法,如AES(高级加密标准)和DES(数据加密标准)。在我们的项目中,可以使用AES来对ZIP文件内容进行加密,因为AES是一种强大且...
### Java加密网址分享知识点概述 #### 一、Java加密技术概览 在互联网时代,数据安全变得尤为重要。Java作为一种广泛使用的编程语言,在处理敏感信息时提供了多种加密手段。本篇文章将探讨如何使用Java进行网址...
, 全书包含3个部分,基础篇对Java企业级应用的安全知识、密码学核心知识、与Java加密相关的API和通过权限文件加强系统安全方面的知识进行了全面的介绍;实践篇不仅对电子邮件传输算法、消息摘要算法、对称加密算法、...
Java加密算法是信息安全领域的重要组成部分,它在保护数据隐私、验证数据完整性和确认发送者身份等方面发挥着关键作用。这个名为"Java加密算法学习笔记的源码包"的压缩文件包含了关于Java中几种主要加密算法的实践...
《EncryptChatRoom--Java加密聊天室》是一款基于DES(Data Encryption Standard)算法的Java实现的加密聊天室项目。该项目旨在提供一个安全的通信环境,确保用户间的聊天信息不被未经授权的第三方窃取或篡改。下面...
本书名为《Java加密与解密的艺术》,由梁栋撰写,ISBN为978-7-111-29762-8。书籍内容围绕Java开发中的加密与解密技术,覆盖了大部分加解密问题,旨在帮助企业应用提高安全性,并提供实用的解决策略。文中介绍了一...
Java加密技术是Java编程语言中用于保障数据安全的一系列加密方法和技术,它包括了单向加密算法、对称加密算法、非对称加密算法以及加密算法的高级应用如数字签名和数字证书等。本文将详细介绍这些加密技术,并结合...
Java加密器Applet小程序是一种基于Java编程语言的小型应用程序,它可以在Web浏览器环境中运行,用于对文件进行加密处理。Applet是Java平台早期的一种特性,允许开发者创建可以在客户端计算机上执行的互动内容,通常...
Java加密解密工具包,通常用于保护敏感数据的安全,防止未经授权的访问或篡改。这个名为"JCT"的工具包提供了丰富的功能,使得开发者在Java应用中集成加密和解密操作变得更加简单。下面我们将详细探讨Java加密的相关...
Java加密算法是信息安全领域中的重要组成部分,用于保护数据的隐私性和完整性。在Java平台上,有多种内置的加密库,如Java Cryptography Extension (JCE) 和 Java Cryptography Architecture (JCA),它们为开发者...