|
级别
:
初级
俞良松,
软件工程师,独立顾问和自由撰稿人
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
装入类文件。
如前所述,定制
ClassLoader
只需先获取类文件的数据,然后把字节码传递给运行时系统,由后者完成余下的任务。
ClassLoader
有几个重要的方法。创建定制的
ClassLoader
时,我们只需覆盖其中的一个,即
loadClass
,提供获取原始类文件数据的代码。这个方法有两个参数:类的名字,以及一个表示
JVM
是否要求解析类名字的标记(即是否同时装入有依赖关系的类)。如果这个标记是
true
,我们只需在返回
JVM
之前调用
resolveClass
。
Listing 2
显示了一个简单的
loadClass
实现。代码中的大部分对所有
ClassLoader
对象来说都一样,但有一小部分(已通过注释标记)是特有的。在处理过程中,
ClassLoader
对象要用到其他几个辅助方法:
Java
加密扩展即
Java Cryptography Extension
,简称
JCE
。它是
Sun
的加密服务软件,包含了加密和密匙生成功能。
JCE
是
JCA
(
Java Cryptography Architecture
)的一种扩展。
JCE
没有规定具体的加密算法,但提供了一个框架,加密算法的具体实现可以作为服务提供者加入。除了
JCE
框架之外,
JCE
软件包还包含了
SunJCE
服务提供者,其中包括许多有用的加密算法,比如
DES
(
Data Encryption Standard
)和
Blowfish
。
为简单计,在本文中我们将用
DES
算法加密和解密字节码。下面是用
JCE
加密和解密数据必须遵循的基本步骤:
前面介绍了如何加密和解密数据。要部署一个经过加密的应用,步骤如下:
1.
步骤
1
:创建应用。我们的例子包含一个
App
主类,两个辅助类(分别称为
Foo
和
Bar
)。这个应用没有什么实际功用,但只要我们能够加密这个应用,加密其他应用也就不在话下。
2.
步骤
2
:生成一个安全密匙。在命令行,利用
GenerateKey
工具(参见
GenerateKey.java
)把密匙写入一个文件:
3.
4.
步骤
3
:加密应用。在命令行,利用
EncryptClasses
工具(参见
EncryptClasses.java
)加密应用的类:
5.
该命令把每一个 .class 文件替换成它们各自的加密版本。
6.
步骤
4
:运行经过加密的应用。用户通过一个
DecryptStart
程序运行经过加密的应用。
DecryptStart
程序如
Listing 6
所示。
7.
对于未经加密的应用,正常执行方式如下:
8.
对于经过加密的应用,则相应的运行方式为:
9.
DecryptStart
有两个目的。一个
DecryptStart
的实例就是一个实施即时解密操作的定制
ClassLoader
;同时,
DecryptStart
还包含一个
main
过程,它创建解密器实例并用它装入和运行应用。示例应用
App
的代码包含在
App.java
、
Foo.java
和
Bar.java
内。
Util.java
是一个文件
I/O
工具,本文示例多处用到了它。完整的代码请从本文最后下载。
我们看到,要在不修改源代码的情况下加密一个
Java
应用是很容易的。不过,世上没有完全安全的系统。本文的加密方式提供了一定程度的源代码保护,但对某些攻击来说它是脆弱的。
虽然应用本身经过了加密,但启动程序
DecryptStart
没有加密。攻击者可以反编译启动程序并修改它,把解密后的类文件保存到磁盘。降低这种风险的办法之一是对启动程序进行高质量的模糊处理。或者,启动程序也可以采用直接编译成机器语言的代码,使得启动程序具有传统执行文件格式的安全性。
另外还要记住的是,大多数
JVM
本身并不安全。狡猾的黑客可能会修改
JVM
,从
ClassLoader
之外获取解密后的代码并保存到磁盘,从而绕过本文的加密技术。
Java
没有为此提供真正有效的补救措施。
不过应该指出的是,所有这些可能的攻击都有一个前提,这就是攻击者可以得到密匙。如果没有密匙,应用的安全性就完全取决于加密算法的安全性。虽然这种保护代码的方法称不上十全十美,但它仍不失为一种保护知识产权和敏感用户数据的有效方案。
|