`
顽石OK07
  • 浏览: 83823 次
  • 性别: Icon_minigender_2
  • 来自: 上海
社区版块
存档分类
最新评论

如何有效防止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装入类文件】

 

Word-WRAP: break-word" bgColor=#f3f3f3>以下是引用片段:

  // 首先创建一个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没有为此提供真正有效的补救措施。

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

 

 

分享到:
评论
1 楼 lovekang89 2012-08-15  
  学习了,看起来不错

相关推荐

    如何有效防止Java程序源码被人偷窥.doc

    【如何有效防止Java程序源码被人偷窥】 Java程序的源代码保护是一个重要的议题,因为一旦源码被窃取,可能会导致知识产权的流失,甚至可能导致安全风险。在Java中,由于其字节码的可读性,源码很容易通过反编译器被...

    USB摄像头偷窥终结者(防止别人偷窥你

    标签“防止 USB摄像头 偷窥”进一步明确了这款软件的核心功能,即对USB摄像头进行安全防护,避免被不法分子用于非法监控。 从压缩包子文件的文件名称列表来看,有两个关键文件: 1. UsbCamSecurity.dll:这是一个...

    U盘偷窥者主程序

    对于企业而言,建立严格的外设访问策略,限制U盘的使用,也是防止U盘偷窥者入侵的有效措施。 总之,U盘偷窥者主程序是一种危险的恶意软件,它利用U盘作为传播媒介,通过隐藏代码逃避检测。深入理解和剖析这种程序,...

    视频加密码软件CamBlocker v1.1注册版.rar

    在网络安全越来越被重视的今天,如何保证摄像头不被偷窥?借助这个小工具可以帮我们给摄像头加把锁,这个软件可以给你的摄像头加上密码,不输入密码谁也无法启动摄像头,可以有效防止那些利用摄像头来偷窥的软件。这...

    偷窥者远程控制软件的VC源代码

    4. **编码解码**:为了隐藏通信内容和防止被拦截,源代码可能包括加密和解密算法,如AES、RSA等。 5. **事件驱动编程**:GUI(图形用户界面)部分可能基于事件驱动模型,如Windows的消息循环,使得软件能响应用户的...

    桌面偷窥器

    【标题】"桌面偷窥器"所提及的是一款软件,主要功能是远程监控或查看他人的桌面活动。这种类型的软件通常被用于企业管理员对员工的工作监控,或者在技术支持场景下远程协助用户解决问题。然而,未经同意的个人使用...

    360魔法摄像头V1.0.0.1007中文绿色免费版

    害怕视频被偷偷打开,于是只好拿一张纸贴上摄像头,防止被偷窥? 那就赶快使用360视频防窥贴吧,您可以使用一张图片来替换您的视频画面,达到视频隐身/防窥的效果。 想不想让对方看到趣味的图片呢?比如洗澡中?...

    偷窥各种桌面程序SPwdSpyDemo_Src

    标题 "偷窥各种桌面程序SPwdSpyDemo_Src" 提示我们这可能是一个关于桌面应用程序密码监测或安全漏洞的研究项目。这个项目的源代码可能包含了用于分析、监控或模拟桌面应用程序密码输入过程的代码。描述中同样重复了...

    易语言密码文本偷窥器

    而PostMessage函数则是异步的,消息会被放入消息队列,程序不会等待。在密码文本偷窥器中,可能通过这些函数来模拟用户操作,比如触发键盘事件,从而获取或修改密码文本框中的内容。 密码文本偷窥器的行为涉及到了...

    键盘鼠标1分钟没有动作自动锁微信,防止他人偷窥微信信息

    平时工作来回的跑微信登陆在电脑上同事来来回回 个人隐私很容易曝光 所以特别做了一个小软件防他人偷窥个人隐私 感觉还挺实用的 分享给有需要的人 使用说明:文件必须放在C盘------(在C盘文件中有)若是运行无效...

    CSS3可爱偷窥小狗动画代码.zip

    在本资源中,"CSS3可爱偷窥小狗动画代码.zip"是一个包含HTML和CSS源码的压缩包,用于创建一个动态的、具有趣味性的“可爱偷窥小狗”动画效果。这个动画可能展示了一只小狗从屏幕边缘探头探脑的形象,为网站增添互动...

    Mysql源码学习笔记 偷窥线程

    初学者可能会被源码的复杂性和多样性所震撼,特别是当打开Visual Studio(VS)查看MySQL源码时,可能会觉得代码风格各异,缺乏统一的规范。这其实反映了MySQL作为一款长期迭代的开源项目,由众多开发者共同贡献的...

    人类最后堡垒将被攻破!GPT读心术来了,能偷窥大脑

    人类最厉害的莫过于缜密的大脑,而现在,有研究表明,这一人类最后的堡垒或将被攻破。 研究人员已经开发出第一种非侵入性的方法来确定内在言语(inner speech)的要点,为不能说话的人提供了一个可能的沟通渠道。 ...

    java反编译工具jd-gui

    Java反编译工具JD-GUI是一款非常实用的Java开发者工具,它可以帮助我们查看Java字节码(.class文件)的源代码,即使原始的源代码不可用或丢失。这个工具对于学习、调试和理解第三方库的内部工作原理非常有用。在Java...

    鼠标键盘记录偷窥器鼠标键盘记录偷窥器

    鼠标键盘记录偷窥器鼠标键盘记录偷窥器鼠标键盘记录偷窥器鼠标键盘记录偷窥器鼠标键盘记录偷窥器鼠标键盘记录偷窥器鼠标键盘记录偷窥器

    跟我学Shiro教程及其课程分章节源码

    Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能: 认证 - 用户身份识别,常被称为用户“登录”; 授权 - 访问控制; 密码加密 - 保护或隐藏数据防止被偷窥; 会话管理 - ...

    手机解锁电脑(可用指纹解锁电脑很好玩)

    电脑开机输入一大串密码嫌麻烦?担心输入密码被有心之人偷窥?担心密码被熊孩子偷偷记住?只需这个软件,和密码说再见!电脑也能实现指纹解锁!(可以实现任何带指纹安卓手机瞬间解锁电脑!解锁只需1秒!)

    U盘偷窥者升级版加入隐藏模式

    【标题】"U盘偷窥者升级版加入隐藏模式"所涉及的知识点主要集中在恶意软件、U盘安全和程序隐蔽技术上。这个标题暗示了一种针对U盘的恶意软件已经进行了更新,增加了隐藏模式,这使得它在目标系统中更难以被检测和...

    密码偷窥器--可以显示密码框内容的软件

    "MimaShow.exe"很可能是一个可执行文件,即程序的主界面或控制模块,用户通过运行这个程序来启动密码偷窥的功能。 在深入理解这些知识点时,我们需要考虑以下几个方面: 1. **密码安全**:在互联网安全中,密码是...

Global site tag (gtag.js) - Google Analytics