`
djsl6071
  • 浏览: 593627 次
  • 性别: Icon_minigender_1
  • 来自: 厦门
社区版块
存档分类
最新评论

使用技巧:运用加密技术保护Java源代码

阅读更多
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:创建应用。我们的例子包含一个App主类,两个辅助类(分别称为Foo和Bar)。这个应用没有什么实际功用,但只要我们能够加密这个应用,加密其他应用也就不在话下。

  步骤2:生成一个安全密匙。在命令行,利用GenerateKey工具(参见GenerateKey.java)把密匙写入一个文件: % java GenerateKey key.data

  步骤3:加密应用。在命令行,利用EncryptClasses工具(参见EncryptClasses.java)加密应用的类: % java EncryptClasses key.data App.class Foo.class Bar.class

  该命令把每一个.class文件替换成它们各自的加密版本。

  步骤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()
);
}
}
}


  对于未经加密的应用,正常执行方式如下: % java App arg0 arg1 arg2

  对于经过加密的应用,则相应的运行方式为: % java DecryptStart key.data App arg0 arg1 arg2

  DecryptStart有两个目的。一个DecryptStart的实例就是一个实施即时解密操作的定制ClassLoader;同时,DecryptStart还包含一个main过程,它创建解密器实例并用它装入和运行应用。示例应用App的代码包含在App.java、Foo.java和Bar.java内。Util.java是一个文件I/O工具,本文示例多处用到了它。完整的代码请从本文最后下载。

  五、注意事项

  我们看到,要在不修改源代码的情况下加密一个Java应用是很容易的。不过,世上没有完全安全的系统。本文的加密方式提供了一定程度的源代码保护,但对某些攻击来说它是脆弱的。

  虽然应用本身经过了加密,但启动程序DecryptStart没有加密。攻击者可以反编译启动程序并修改它,把解密后的类文件保存到磁盘。降低这种风险的办法之一是对启动程序进行高质量的模糊处理。或者,启动程序也可以采用直接编译成机器语言的代码,使得启动程序具有传统执行文件格式的安全性。

  另外还要记住的是,大多数JVM本身并不安全。狡猾的黑客可能会修改JVM,从ClassLoader之外获取解密后的代码并保存到磁盘,从而绕过本文的加密技术。Java没有为此提供真正有效的补救措施。

  不过应该指出的是,所有这些可能的攻击都有一个前提,这就是攻击者可以得到密匙。如果没有密匙,应用的安全性就完全取决于加密算法的安全性。虽然这种保护代码的方法称不上十全十美,但它仍不失为一种保护知识产权和敏感用户数据的有效方案。 
分享到:
评论

相关推荐

    飞机订票系统java源代码

    《飞机订票系统Java源代码解析与探讨》 在信息技术高速发展的今天,各种软件系统已经深入到我们生活的各个角落,其中,飞机订票系统作为出行服务的重要组成部分,扮演着不可或缺的角色。本文将以“飞机订票系统Java...

    JAVA上百实例源码以及开源项目源代码

    Java源代码实现部分,比较有意思,也具参考性。像坐标控制、旋转矩阵、定时器、生成图像、数据初始化、矩阵乘法、坐标旋转、判断是否是顺时针方向排列、鼠标按下、放开时的动作等,都可在本源码中得以体现。 Java...

    java源代码反编译专家v1.4终极版

    此外,压缩包内的"精品源代码100例.mht"文件可能包含了一些精选的Java源代码示例,这些示例可能是为了帮助用户更好地理解和应用反编译知识,涵盖了各种编程场景和技巧。 总的来说,Java源代码反编译是一个复杂而有...

    Java聊天软件源代码

    8. **数据加密**:为了保护用户的隐私,聊天软件可能会使用加密技术如SSL/TLS对通信内容进行加密,确保数据安全。 9. **数据库交互**:如果软件有存储历史记录的功能,可能会涉及数据库操作,如使用JDBC(Java ...

    java源码包---java 源码 大量 实例

    Java源代码实现部分,比较有意思,也具参考性。像坐标控制、旋转矩阵、定时器、生成图像、数据初始化、矩阵乘法、坐标旋转、判断是否是顺时针方向排列、鼠标按下、放开时的动作等,都可在本源码中得以体现。 Java...

    医药打卡挂号系统JAVA源代码

    "医药打卡挂号系统JAVA源代码"是一个基于Java技术开发的软件项目,主要服务于医疗机构的日常运营,方便患者进行在线预约挂号和签到。这个系统的核心是利用Java的强大功能和灵活性来构建一个稳定、高效的平台,使得...

    公司人事管理系统 Java源代码

    在这个系统中,Java源代码扮演了至关重要的角色,因为它是一种广泛应用的编程语言,特别适合开发企业级应用。 首先,让我们深入了解一下Java在人事管理系统中的应用。Java以其“一次编写,到处运行”的特性,保证了...

    JAVA源代码关于宾客管理系统

    总之,JAVA源代码的宾客管理系统是一个综合性的应用,涉及到众多的JAVA技术和编程技巧,从数据库操作到用户交互,从数据结构到系统架构,都是开发者需要考虑和掌握的。PHONE22.java文件作为系统的一部分,揭示了这个...

    常规Java工具,算法,加密,数据库,面试题,源代码分析,解决方案.zip

    代码和项目实例:提供了多个Java项目的源代码,方便学习者参考和实践。 学习笔记和心得:记录了学习过程中的重点难点和心得体会,有助于学习者更好地理解和掌握知识。 二、适用人群 本资源适用于即将毕业或已经毕业...

    多人命题系统JAVA源代码

    【多人命题系统JAVA源代码】是一个专为教育和考试领域设计的软件系统,它采用Java编程语言编写,具有高可扩展性和灵活性。该系统的核心功能是支持多人协作完成命题工作,便于教师、命题专家进行试题的创建、编辑和...

    基于JAVA的RSA文件加密软件的设计与实现(源代码+文档)

    本项目提供了基于Java的RSA文件加密软件的源代码和相关文档,旨在帮助开发者了解和实践RSA加密技术。 **RSA加密原理** RSA算法基于数论中的两个大素数乘积难以分解的原理。它包括一对密钥:公钥和私钥。公钥可以...

    java企业通讯QQ源代码

    在Java企业通讯领域,开发一款类似QQ...通过研究这个源代码,你可以了解到一个实际的Java企业通讯软件是如何从零开始构建的,同时也能提升你的编程技巧和解决问题的能力。对于初学者而言,这是一次难得的实践学习经历。

    java源码:HAHA CHAT Java仿QQ聊天程序源代码.rar

    【标题】"HAHA CHAT Java仿QQ聊天程序源代码"是一个基于Java编程语言实现的聊天应用程序,旨在模仿流行的QQ即时通讯软件的功能。这个项目对于Java初学者和想要深入理解网络编程、多线程以及GUI设计的开发者来说,是...

    基于Misty1算法的加密软件(JAVA)的实现(源代码+LW).zip

    本文将深入探讨基于Misty1算法的加密软件在JAVA环境中的实现,以及如何通过源代码进行理解与应用。Misty1算法是一种非对称加密算法,源自日本,具有较高的安全性和效率,广泛应用于各种信息安全领域。 首先,我们来...

    JAVA网络编程经典源代码

    通过分析和学习这些源代码,开发者不仅可以掌握Java网络编程的基本技巧,还能了解到如何在实际项目中应用这些知识,提升解决问题的能力。此外,源代码中可能还包括对错误处理、性能优化、网络通信协议解析等方面的...

    么把.class文件反编译成.java 经修改后再编译成.class

    * 使用加密技术:可以使用加密技术来保护源代码的安全。 反编译技术是 Java 开发中的一个重要技术,可以帮助开发者快速地-debug 和修改代码。但是,也需要注意安全性问题,防止泄露源代码的内容。

    编写的职工工资管理系统源代码.rar

    总结来说,职工工资管理系统源代码是实现企业薪资管理自动化的重要工具,其涉及的C++编程、数据结构设计、面向对象编程等知识点,是软件工程师必备的技术基础。通过深入学习和实践,我们可以构建出更高效、更安全的...

    基于Misty1算法的加密软件(JAVA)的实现(源代码+论文).rar

    《基于Misty1算法的加密软件JAVA...通过源代码和论文,我们可以学习到如何在实际项目中运用这种高级加密算法,同时也加深了对密码学原理的理解。这不仅有助于提升个人技能,也为未来在信息安全领域的实践打下坚实基础。

    常规Java工具,算法,加密,数据库,面试题,源代码分析,解决方案

    本资源包涵盖了Java领域的多个重要知识点,包括常规Java工具、算法、加密技术、数据库管理和面试题,同时提供了源代码分析和解决方案,旨在帮助开发者提升技能,解决实际问题。 1. **常规Java工具**:这部分可能...

    CRM系统 源代码

    6. 移动适配:随着移动设备的普及,CRM系统通常需要支持手机和平板等设备,源代码中会有相应的响应式设计或原生应用的开发部分。 在使用或研究CRM系统源代码时,需要注意以下几点: 1. 学习最佳实践:源代码可以...

Global site tag (gtag.js) - Google Analytics