下面有关java代码安全性的叙述哪些是对的
发布时间:2025-05-20 09:32:09 发布人:远客网络
一、下面有关java代码安全性的叙述哪些是对的
1、下面有关Java代码安全性的叙述,()是对的。Ⅰ:字节码校验器加载查询执行需要的所有类。Ⅱ:运行时解释器执行代码。Ⅲ:在运行时,字节码被加载,验证后在解释器里面运行。Ⅳ:类加载器通过分离本机文件系统的类和从网络导入的类增加安全性。
2、请帮忙给出正确答案和分析,谢谢!
3、解析:该题考查考生对Java程序代码安全性的掌握。Java程序运行的过程是这样的:类加载器加载程序运行所需要的所有类,它通过区分本机文件系统的类和网络系统导入的类增加安全性,这可以限制任何的特洛伊木马程序,因为本机类总是先被加载,一旦所有的类被加载完,执行文件的内存划分就固定了,在这个时候,特定的内存地址被分配给对应的符号引用,查找表也被建立,由于内存划分发生在运行时,解释器在受限制的代码区增加保护,防止未授权的访问;然后字节码校验器进行校验,主要执行下面的检查,类是否符合JVM规范的类文件格式,有没有违反访问限制,代码有没有造成堆栈的上溢或者下溢,所有操作代码的参数类型是否都是正确的,有没有非法的数据类型转换(例如将整型数转换成对象类型)发生;校验通过的字节码被解释器执行,解释器在必要时通过运行时系统执行对底层硬件的相应调用。
二、Java中解决安全问题的技术有哪些
请遵循下列建议以避免高严重性静态安全性暴露:
让每个类和方法都成为 final,除非有足够的理由不这样做
如果将变量声明为 public,那么外部代码就可以操作该变量。这可能会导致安全性暴露。
如果实例变量为 public,那么就可以在类实例上直接访问和操作该实例变量。将实例变量声明为 protected并不一定能解决这一问题:虽然不可能直接在类实例基础上访问这样的变量,但仍然可以从派生类访问这个变量。
清单 1演示了带有 public变量的代码,因为变量为 public的,所以它暴露了。
public class MyClass extends Test{
public void methodIllegalSet(String name){
this.name= name;// this should not be allowed
public static void main(String[] args){
obj.id= 123;// this should not be allowed
mc.methodIllegalSet("Illegal Set Value");
一般来说,应该使用取值方法而不是 public变量。按照具体问题具体对待的原则,在确定哪些变量特别重要因而应该声明为 private时,请将编码的方便程度及成本同安全性需要加以比较。清单 2演示了以下列方式来使之安全的代码:
清单 2.不带有 public变量的代码
public void setName(String name){
不允许扩展的类和方法应该声明为 final。这样做防止了系统外的代码扩展类并修改类的行为。
仅仅将类声明为非 public并不能防止攻击者扩展类,因为仍然可以从它自己的包内访问该类。
让每个类和方法都成为 final,除非有足够的理由不这样做。按此建议,我们要求您放弃可扩展性,虽然它是使用诸如 Java语言之类的面向对象语言的主要优点之一。在试图提供安全性时,可扩展性却成了您的敌人;可扩展性只会为攻击者提供更多给您带来麻烦的方法。
没有显式地标注为 public、private或 protected的类、方法和变量在它们自己的包内是可访问的。
如果 Java包不是封闭的,那么攻击者就可以向包内引入新类并使用该新类来访问您想保护的内容。诸如 java.lang之类的一些包缺省是封闭的,一些 JVM也让您封闭自己的包。然而,您最好假定包是不封闭的。
从软件工程观点来看,包作用域具有重要意义,因为它可以阻止对您想隐藏的内容进行偶然的、无意中的访问。但不要依靠它来获取安全性。应该将类、方法和变量显式标注为 public、private或 protected中适合您特定需求的那种。
克隆允许绕过构造器而轻易地复制类实例。
即使您没有有意使类可克隆,外部源仍然可以定义您的类的子类,并使该子类实现 java.lang.Cloneable。这就让攻击者创建了您的类的新实例。拷贝现有对象的内存映象生成了新的实例;虽然这样做有时候是生成新对象的可接受方法,但是大多数时候是不可接受的。清单 3说明了因为可克隆而暴露的代码:
public MyClass(int id,String name){
System.out.println("Id="+id+"
// hackers code to clone the user class
public class Hacker extends MyClass implements Cloneable{
public static void main(String[] args){
MyClass o=(MyClass)hack.clone();
catch(CloneNotSupportedException e){
要防止类被克隆,可以将清单 4中所示的方法添加到您的类中:
throws java.lang.CloneNotSupportedException{
throw new java.lang.CloneNotSupportedException();
如果想让您的类可克隆并且您已经考虑了这一选择的后果,那么您仍然可以保护您的类。要做到这一点,请在您的类中定义一个为 final的克隆方法,并让它依赖于您的一个超类中的一个非 final克隆方法,如清单 5中所示:
清单 5.以安全的方式使您的代码可克隆
throws java.lang.CloneNotSupportedException{
类中出现 clone()方法防止攻击者重新定义您的 clone方法。
序列化允许将类实例中的数据保存在外部文件中。闯入代码可以克隆或复制实例,然后对它进行序列化。
序列化是令人担忧的,因为它允许外部源获取对您的对象的内部状态的控制。这一外部源可以将您的对象之一序列化成攻击者随后可以读取的字节数组,这使得攻击者可以完全审查您的对象的内部状态,包括您标记为 private的任何字段。它也允许攻击者访问您引用的任何对象的内部状态。
要防止类中的对象被序列化,请在类中定义清单 6中的 writeObject()方法:
private final void writeObject(ObjectOutputStream out)
throws java.io.NotSerializableException{
throw new java.io.NotSerializableException("This object cannot
通过将 writeObject()方法声明为 final,防止了攻击者覆盖该方法。
通过使用逆序列化,攻击者可以用外部数据或字节流来实例化类。
不管类是否可以序列化,都可以对它进行逆序列化。外部源可以创建逆序列化成类实例的字节序列。这种可能为您带来了大量风险,因为您不能控制逆序列化对象的状态。请将逆序列化作为您的对象的另一种公共构造器?一种您无法控制的构造器。
要防止对对象的逆序列化,应该在您的类中定义清单 7中的 readObject()方法:
private final void readObject(ObjectInputStream in)
throws java.io.NotSerializableException{
throw new java.io.NotSerializableException("This object cannot
通过将该方法声明为 final,防止了攻击者覆盖该方法。
您可能会尝试将诸如加密密钥之类的秘密存放在您的应用程序或库的代码。对于你们开发人员来说,这样做通常会把事情变得更简单。
任何运行您的代码的人都可以完全访问以这种方法存储的秘密。没有什么东西可以防止心怀叵测的程序员或虚拟机窥探您的代码并了解其秘密。
可以以一种只可被您解密的方式将秘密存储在您代码中。在这种情形下,秘密只在于您的代码所使用的算法。这样做没有多大坏处,但不要洋洋得意,认为这样做提供了牢固的保护。您可以遮掩您的源代码或字节码?也就是,以一种为了解密必须知道加密格式的方法对源代码或字节码进行加密?但攻击者极有可能能够推断出加密格式,对遮掩的代码进行逆向工程从而揭露其秘密。
这一问题的一种可能解决方案是:将敏感数据保存在属性文件中,无论什么时候需要这些数据,都可以从该文件读取。如果数据极其敏感,那么在访问属性文件时,您的应用程序应该使用一些加密/解密技术。
从事某个项目的某个心怀叵测的开发人员可能故意引入易受攻击的代码,打算日后利用它。这样的代码在初始化时可能会启动一个后台进程,该进程可以为闯入者开后门。它也可以更改一些敏感数据。
入口点程序可能很危险而且有恶意。通常,Java开发人员往往在其类中编写 main()方法,这有助于测试单个类的功能。当类从测试转移到生产环境时,带有 main()方法的类就成为了对应用程序的潜在威胁,因为闯入者将它们用作入口点。
请检查代码中是否有未使用的方法出现。这些方法在测试期间将会通过所有的安全检查,因为在代码中不调用它们?但它们可能含有硬编码在它们内部的敏感数据(虽然是测试数据)。引入一小段代码的攻击者随后可能调用这样的方法。
避免最终应用程序中的死代码(注释内的代码)。如果闯入者去掉了对这样的代码的注释,那么代码可能会影响系统的功能性。
可以在清单 8中看到所有三种类型的恶意代码的示例:
// code written to harm the system
//unusedMethod();//code in comment put with bad intentions,
//might affect the system if uncommented
// x=x+10;//Code in comment, might affect the
//functionality of the system if uncommented
应该将(除启动应用程序的 main()方法之外的)main()方法、未使用的方法以及死代码从应用程序代码中除去。在软件交付使用之前,主要开发人员应该对敏感应用程序进行一次全面的代码评审。应该使用“Stub”或“dummy”类代替 main()方法以测试应用程序的功能。
请遵循下列建议以避免中等严重性静态安全性暴露:
您可以不运行构造器而分配对象。这些对象使用起来不安全,因为它们不是通过构造器初始化的。
在初始化时验证对象确保了数据的完整性。
例如,请想象为客户创建新帐户的 Account对象。只有在 Account期初余额大于 0时,才可以开设新帐户。可以在构造器里执行这样的验证。有些人未执行构造器而创建 Account对象,他可能创建了一个具有一些负值的新帐户,这样会使系统不一致,容易受到进一步的干预。
在使用对象之前,请检查对象的初始化过程。要做到这一点,每个类都应该有一个在构造器中设置的私有布尔标志,如清单 9中的类所示。在每个非 static方法中,代码在任何进一步执行之前都应该检查该标志的值。如果该标志的值为 true,那么控制应该进一步继续;否则,控制应该抛出一个例外并停止执行。那些从构造器调用的方法将不会检查初始化的变量,因为在调用方法时没有设置标志。因为这些方法并不检查标志,所以应该将它们声明为 private以防止用户直接访问它们。
清单 9.使用布尔标志以检查初始化过程
private boolean initialized= false;
private void method1(){//no need to check for initialization variable
//proceed with the business logic
throw new Exception("Illegal State Of the object");
如果对象由逆序列化进行初始化,那么上面讨论的验证机制将难以奏效,因为在该过程中并不调用构造器。在这种情况下,类应该实现 ObjectInputValidation接口:
清单 10.实现 ObjectInputValidation
interface java.io.ObjectInputValidation{
public void validateObject() throws InvalidObjectException;
所有验证都应该在 validateObject()方法中执行。对象还必须调用 ObjectInputStream.RegisterValidation()方法以为逆序列化对象之后的验证进行注册。 RegisterValidation()的第一个参数是实现 validateObject()的对象,通常是对对象自身的引用。注:任何实现 validateObject()的对象都可能充当对象验证器,但对象通常验证它自己对其它对象的引用。RegisterValidation()的第二个参数是一个确定回调顺序的整数优先级,优先级数字大的比优先级数字小的先回调。同一优先级内的回调顺序则不确定。
当对象已逆序列化时,ObjectInputStream按照从高到低的优先级顺序调用每个已注册对象上的 validateObject()。
有时候,您可能需要比较两个对象的类,以确定它们是否相同;或者,您可能想看看某个对象是否是某个特定类的实例。因为 JVM可能包括多个具有相同名称的类(具有相同名称但却在不同包内的类),所以您不应该根据名称来比较类。
如果根据名称来比较类,您可能无意中将您不希望授予别人的权利授予了闯入者的类,因为闯入者可以定义与您的类同名的类。
例如,请假设您想确定某个对象是否是类 com.bar.Foo的实例。清单 11演示了完成这一任务的错误方法:
if(obj.getClass().getName().equals("Foo"))// Wrong!
// object's class has some other name
在那些非得根据名称来比较类的情况下,您必须格外小心,必须确保使用了当前类的 ClassLoader的当前名称空间,如清单 12中所示:
if(obj.getClass()== this.getClassLoader().loadClass("com.bar.Foo")){
// object's class is equal to
//the class that this class calls"com.bar.Foo"
// object's class is not equal to the class that
// this class calls"com.bar.Foo"
然而,比较类的更好方法是直接比较类对象看它们是否相等。例如,如果您想确定两个对象 a和 b是否属同一个类,那么您就应该使用清单 13中的代码:
清单 13.直接比较对象来看它们是否相等
if(a.getClass()== b.getClass()){
// objects have the same class
// objects have different classes
Java字节码没有内部类的概念,因为编译器将内部类转换成了普通类,而如果没有将内部类声明为 private,则同一个包内的任何代码恰好能访问该普通类。
因为有这一特性,所以包内的恶意代码可以访问这些内部类。如果内部类能够访问括起外部类的字段,那么情况会变得更糟。可能已经将这些字段声明为 private,这样内部类就被转换成了独立类,但当内部类访问外部类的字段时,编译器就将这些字段从专用(private)的变为在包(package)的作用域内有效的。内部类暴露了已经够糟糕的了,但更糟糕的是编译器使您将某些字段成为 private的举动成为徒劳。
建议如果能够不使用内部类就不要使用内部类。
请遵循下列建议以避免低严重性静态安全性暴露:
Java方法返回对象引用的副本。如果实际对象是可改变的,那么使用这样一个引用调用程序可能会改变它的内容,通常这是我们所不希望见到的。
请考虑这个示例:某个方法返回一个对敏感对象的内部数组的引用,假定该方法的调用程序不改变这些对象。即使数组对象本身是不可改变的,也可以在数组对象以外操作数组的内容,这种操作将反映在返回该数组的对象中。如果该方法返回可改变的对象,那么事情会变得更糟;外部实体可以改变在那个类中声明的 public变量,这种改变将反映在实际对象中。
清单 14演示了脆弱性。getExposedObj()方法返回了 Exposed对象的引用副本,该对象是可变的:
清单 14.返回可变对象的引用副本
public Exposed(int id, String name){
public void setName(String name){
System.out.println("Id="+ id+" Name="+ name);
private Exposed exposedObj= new Exposed(1,"Harry Porter");
public Exposed getExposedObj(){
return exposedObj;//returns a reference to the object.
public static void main(String[] args){
exp12.getExposedObj().display();
Exposed exposed= exp12.getExposedObj();
exp12.getExposedObj().display();
如果方法返回可改变的对象,但又不希望调用程序改变该对象,请修改该方法使之不返回实际对象而是返回它的副本或克隆。要改正清单 14中的代码,请让它返回 Exposed对象的副本,如清单 15中所示:
public Exposed getExposedObj(){
return new Exposed(exposedObj.getId(),exposedObj.getName());
或者,您的代码也可以返回 Exposed对象的克隆。
本机方法是一种 Java方法,其实现是用另一种编程语言编写的,如 C或 C++。有些开发人员实现本机方法,这是因为 Java语言即使使用即时(just-in-time)编译器也比许多编译过的语言要慢。其它人需要使用本机代码是为了在 JVM以外实现特定于平台的功能。
使用本机代码时,请小心,因为对这些代码进行验证是不可能的,而且本机代码可能潜在地允许 applet绕过通常的安全性管理器(Security Manager)和 Java对设备访问的控制。
如果非得使用本机方法,那么请检查这些方法以确定:
它们是否是 public、private等等
它们是否含有绕过包边界从而绕过包保护的方法调用
编写安全 Java代码是十分困难的,但本文描述了一些可行的实践来帮您编写安全 Java代码。这些建议并不能解决您的所有安全性问题,但它们将减少暴露数目。最佳软件安全性实践可以帮助确保软件正常运行。安全至关重要和高可靠系统设计者总是花费大量精力来分析和跟踪软件行为。只有通过将安全性作为至关紧要的系统特性来对待?并且从一开始就将它构建到应用程序中,我们才可以避免亡羊补牢似的、修修补补的安全性方法。
三、如何有效防止Java程序源码被人偷窥
1、 Java程序的源代码很容易被别人偷看只要有一个反编译器任何人都可以分析别人的代码本文讨论如何在不修改原有程序的情况下通过加密技术保护源代码
一为什么要加密?
2、对于传统的C或C++之类的语言来说要在Web上保护源代码是很容易的只要不发布它就可以遗憾的是 Java程序的源代码很容易被别人偷看只要有一个反编译器任何人都可以分析别人的代码 Java的灵活性使得源代码很容易被窃取但与此同时它也使通过加密保护代码变得相对容易我们唯一需要了解的就是Java的ClassLoader对象当然在加密过程中有关Java Cryptography Extension(JCE)的知识也是必不可少的
3、有几种技术可以模糊 Java类文件使得反编译器处理类文件的效果大打折扣然而修改反编译器使之能够处理这些经过模糊处理的类文件并不是什么难事所以不能简单地依赖模糊技术来保证源代码的安全
4、我们可以用流行的加密工具加密应用比如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard)这时最终用户在运行应用之前必须先进行解密但解密之后最终用户就有了一份不加密的类文件这和事先不进行加密没有什么差别
5、 Java运行时装入字节码的机制隐含地意味着可以对字节码进行修改 JVM每次装入类文件时都需要一个称为ClassLoader的对象这个对象负责把新的类装入正在运行的JVM JVM给ClassLoader一个包含了待装入类(比如java lang Object)名字的字符串然后由ClassLoader负责找到类文件装入原始数据并把它转换成一个Class对象
6、我们可以通过定制ClassLoader在类文件执行之前修改它这种技术的应用非常广泛??在这里它的用途是在类文件装入之时进行解密因此可以看成是一种即时解密器由于解密后的字节码文件永远不会保存到文件系统所以窃密者很难得到解密后的代码
7、由于把原始字节码转换成Class对象的过程完全由系统负责所以创建定制ClassLoader对象其实并不困难只需先获得原始数据接着就可以进行包含解密在内的任何转换
8、 Java在一定程度上简化了定制ClassLoader的构建在Java中 loadClass的缺省实现仍旧负责处理所有必需的步骤但为了顾及各种定制的类装入过程它还调用一个新的findClass方法
9、这为我们编写定制的ClassLoader提供了一条捷径减少了麻烦只需覆盖findClass而不是覆盖loadClass这种方法避免了重复所有装入器必需执行的公共步骤因为这一切由loadClass负责
10、不过本文的定制ClassLoader并不使用这种方法原因很简单如果由默认的ClassLoader先寻找经过加密的类文件它可以找到;但由于类文件已经加密所以它不会认可这个类文件装入过程将失败因此我们必须自己实现loadClass稍微增加了一些工作量
11、每一个运行着的JVM已经拥有一个ClassLoader这个默认的ClassLoader根据CLASSPATH环境变量的值在本地文件系统中寻找合适的字节码文件
12、应用定制ClassLoader要求对这个过程有较为深入的认识我们首先必须创建一个定制ClassLoader类的实例然后显式地要求它装入另外一个类这就强制JVM把该类以及所有它所需要的类关联到定制的ClassLoader Listing显示了如何用定制ClassLoader装入类文件
13、【Listing利用定制的ClassLoader装入类文件】
14、//首先创建一个ClassLoader对象 ClassLoader myClassLoader= new myClassLoader();//利用定制ClassLoader对象装入类文件//并把它转换成Class对象 Class myClass= myClassLoader loadClass( mypackage MyClass);//最后创建该类的一个实例 Object newInstance= myClass newInstance();//注意 MyClass所需要的所有其他类都将通过//定制的ClassLoader自动装入
15、如前所述定制ClassLoader只需先获取类文件的数据然后把字节码传递给运行时系统由后者完成余下的任务
16、 ClassLoader有几个重要的方法创建定制的ClassLoader时我们只需覆盖其中的一个即loadClass提供获取原始类文件数据的代码这个方法有两个参数类的名字以及一个表示JVM是否要求解析类名字的标记(即是否同时装入有依赖关系的类)如果这个标记是true我们只需在返回JVM之前调用resolveClass
17、【Listing ClassLoader loadClass()的一个简单实现】
18、 public Class loadClass( String name boolean resolve) throws ClassNotFoundException{ try{//我们要创建的Class对象 Class clasz= null;//必需的步骤如果类已经在系统缓冲之中//我们不必再次装入它 clasz= findLoadedClass( name); if(clasz!= null) return clasz;//下面是定制部分 byte classData[]=/*通过某种方法获取字节码数据*/; if(classData!= null){//成功读取字节码数据现在把它转换成一个Class对象 clasz= defineClass( name classData classData length);}//必需的步骤如果上面没有成功//我们尝试用默认的ClassLoader装入它 if(clasz== null) clasz= findSystemClass( name);//必需的步骤如有必要则装入相关的类 if(resolve&& clasz!= null) resolveClass( clasz);//把类返回给调用者 return clasz;} catch( IOException ie){ throw new ClassNotFoundException( ie toString());} catch( GeneralSecurityException gse){ throw new ClassNotFoundException( gse toString());}}
19、 Listing显示了一个简单的loadClass实现代码中的大部分对所有ClassLoader对象来说都一样但有一小部分(已通过注释标记)是特有的在处理过程中 ClassLoader对象要用到其他几个辅助方法
20、 findLoadedClass用来进行检查以便确认被请求的类当前还不存在 loadClass方法应该首先调用它
21、 defineClass获得原始类文件字节码数据之后调用defineClass把它转换成一个Class对象任何loadClass实现都必须调用这个方法
22、 findSystemClass提供默认ClassLoader的支持如果用来寻找类的定制方法不能找到指定的类(或者有意地不用定制方法)则可以调用该方法尝试默认的装入方式这是很有用的特别是从普通的JAR文件装入标准Java类时
23、 resolveClass当JVM想要装入的不仅包括指定的类而且还包括该类引用的所有其他类时它会把loadClass的resolve参数设置成true这时我们必须在返回刚刚装入的Class对象给调用者之前调用resolveClass
24、 Java加密扩展即Java Cryptography Extension简称JCE它是Sun的加密服务软件包含了加密和密匙生成功能 JCE是JCA(Java Cryptography Architecture)的一种扩展
25、 JCE没有规定具体的加密算法但提供了一个框架加密算法的具体实现可以作为服务提供者加入除了JCE框架之外 JCE软件包还包含了SunJCE服务提供者其中包括许多有用的加密算法比如DES(Data Encryption Standard)和Blowfish
26、为简单计在本文中我们将用DES算法加密和解密字节码下面是用JCE加密和解密数据必须遵循的基本步骤
27、步骤生成一个安全密匙在加密或解密任何数据之前需要有一个密匙密匙是随同被加密的应用一起发布的一小段数据 Listing显示了如何生成一个密匙【Listing生成一个密匙】
28、// 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);步骤加密数据得到密匙之后接下来就可以用它加密数据除了解密的ClassLoader之外一般还要有一个加密待发布应用的独立程序(见Listing)【Listing用密匙加密原始数据】
29、// 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);步骤解密数据运行经过加密的应用时 ClassLoader分析并解密类文件操作步骤如Listing所示【Listing用密匙解密数据】
30、// 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);
31、前面介绍了如何加密和解密数据要部署一个经过加密的应用步骤如下
32、步骤创建应用我们的例子包含一个App主类两个辅助类(分别称为Foo和Bar)这个应用没有什么实际功用但只要我们能够加密这个应用加密其他应用也就不在话下
33、步骤生成一个安全密匙在命令行利用GenerateKey工具(参见GenerateKey java)把密匙写入一个文件% java GenerateKey key data
34、步骤加密应用在命令行利用EncryptClasses工具(参见EncryptClasses java)加密应用的类% java EncryptClasses key data App class Foo class Bar class
35、该命令把每一个 class文件替换成它们各自的加密版本
36、步骤运行经过加密的应用用户通过一个DecryptStart程序运行经过加密的应用 DecryptStart程序如Listing所示【Listing DecryptStart java启动被加密应用的程序】
37、 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[ ]; String appName= args[ ];//这些是传递给应用本身的参数 String realArgs[]= new String[args length ]; System arraycopy( args realArgs args length);//读取密匙 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[ ]; Class mainArgs[]={(new String[ ]) 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;//必需的步骤如果类已经在系统缓冲之中//我们不必再次装入它 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 decryptedClassData length); System err println( [DecryptStart: decrypting class+name+ ]);}} catch( FileNotFoundException fnfe)//必需的步骤如果上面没有成功//我们尝试用默认的ClassLoader装入它 if(clasz== null) clasz= findSystemClass( name);//必需的步骤如有必要则装入相关的类 if(resolve&& clasz!= null) resolveClass( clasz);//把类返回给调用者 return clasz;} catch( IOException ie){ throw new ClassNotFoundException( ie toString());} catch( GeneralSecurityException gse){ throw new ClassNotFoundException( gse toString());}}}对于未经加密的应用正常执行方式如下% java App arg arg arg
38、对于经过加密的应用则相应的运行方式为% java DecryptStart key data App arg arg arg
39、 DecryptStart有两个目的一个DecryptStart的实例就是一个实施即时解密操作的定制ClassLoader;同时 DecryptStart还包含一个main过程它创建解密器实例并用它装入和运行应用示例应用App的代码包含在App java Foo java和Bar java内 Util java是一个文件I/O工具本文示例多处用到了它完整的代码请从本文最后下载
40、我们看到要在不修改源代码的情况下加密一个Java应用是很容易的不过世上没有完全安全的系统本文的加密方式提供了一定程度的源代码保护但对某些攻击来说它是脆弱的
41、虽然应用本身经过了加密但启动程序DecryptStart没有加密攻击者可以反编译启动程序并修改它把解密后的类文件保存到磁盘降低这种风险的办法之一是对启动程序进行高质量的模糊处理或者启动程序也可以采用直接编译成机器语言的代码使得启动程序具有传统执行文件格式的安全性
42、另外还要记住的是大多数JVM本身并不安全狡猾的黑客可能会修改JVM从ClassLoader之外获取解密后的代码并保存到磁盘从而绕过本文的加密技术 Java没有为此提供真正有效的补救措施