如何理解Java通过加密技术保护源代码的方法

技术如何理解Java通过加密技术保护源代码的方法这篇文章主要讲解了“如何理解Java通过加密技术保护源代码的方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何理解Ja

本文主要讲解“如何理解Java通过加密技术保护源代码的方法”。本文内容简单明了,易学易懂。请跟随边肖的思路,一起学习学习“如何理解Java通过加密技术保护源代码的方法”。

一、为什么要加密?

对于C或C等传统语言来说,只要不发布,就很容易保护Web上的源代码。遗憾的是,Java程序的源代码很容易被别人偷看。只要有反编译程序,任何人都可以分析别人的代码。Java的灵活性使得源代码容易被窃取,但同时也使得通过加密保护代码相对容易。我们唯一需要知道的是Java的ClassLoader对象。当然,在加密过程中,关于Java密码扩展(JCE)的知识也是必不可少的。

有几种技术可以“模糊”Java类文件,这大大降低了反编译程序处理类文件的效果。然而,修改反编译程序来处理这些模糊类文件并不难,所以依靠模糊技术来保证源代码的安全性并不容易。

我们可以用流行的加密工具加密应用程序,比如PGP(相当好的隐私)或GPG(GNU隐私卫士)。在这种情况下,最终用户必须在运行应用程序之前对其进行解密。但解密后,最终用户会有一个未加密的类文件,这和不提前加密没什么区别。

Java运行时加载字节码的机制意味着字节码可以被修改。每次JVM加载一个类文件时,它都需要一个名为ClassLoader的对象,该对象负责将新类加载到正在运行的JVM中。给JVM一个包含要加载的类的名称的字符串(比如java.lang.Object),然后由ClassLoader负责查找类文件,加载原始数据并转换成类对象。

我们可以在执行类文件之前通过定制来修改类加载器。该技术被广泛应用于mdashmdash这里,它的目的是在加载类文件时对其进行解密,因此可以将其视为即时解密器。因为解密的字节码文件永远不会保存到文件系统中,所以小偷很难得到解密的代码。

由于将原始字节码转换为Class对象的过程完全由系统负责,所以创建一个自定义的ClassLoader对象并不难,只需先获取原始数据,然后进行任何转换,包括解密。

Java 2在一定程度上简化了定制类加载器的构造。在Java 2中,loadClass的默认实现仍然负责处理所有必要的步骤,但是它也调用了一个新的findClass方法,以便考虑各种定制的类加载过程。

这为我们编写定制的类加载器提供了快捷方式,减少了麻烦:只需覆盖findClass而不是loadClass。此方法避免重复所有加载器必须执行的常见步骤,因为loadClass负责所有这些。

但是,本文中的自定义类加载器不使用这种方法。原因很简单。如果默认的ClassLoader首先查找加密的类文件,它可以找到它;但是,由于类文件是加密的,它将无法识别该类文件,加载过程将失败。因此,我们必须自己实现loadClass,这稍微增加了工作量。

二、定制类装入器

每个运行的JVM都已经有了一个类加载器。根据CLASSPATH环境变量的值,默认的ClassLoader在本地文件系统中搜索适当的字节码文件。

定制类加载器的应用需要对这个过程有深刻的理解。我们必须首先创建一个自定义类加载器类的实例,然后明确要求它加载另一个类。这迫使JVM将这个类和它需要的所有类与自定义类加载器相关联。清单1展示了如何用自定义类加载器加载类文件。

[清单1:使用自定义类加载器加载类文件]

//首先创建一个ClassLoader对象classloadermycyclassloader=new myclassloader();//使用自定义的classLoader对象加载Class文件//,并将其转换为Class对象Class my Class=my Class loader . load Class(' my package . my Class ');//最后,创建这个类的一个实例,object new instance=my class . new instance();//请注意,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()  );  }  }  }

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

五、注意事项 

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

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

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

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

感谢各位的阅读,以上就是“如何理解Java通过加密技术保护源代码的方法”的内容了,经过本文的学习后,相信大家对如何理解Java通过加密技术保护源代码的方法这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/46796.html

(0)

相关推荐

  • 如何安装logstash

    技术如何安装logstash这篇文章主要为大家展示了“如何安装logstash”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何安装logstash”这篇文章吧。安装logs

    攻略 2021年11月26日
  • 抖音刷点赞,很多人都是刷出来的?

    技术抖音刷点赞,很多人都是刷出来的?拍出高质量的作品,抖音官方会主动为你推荐。“内容为王”,我们在拍摄作品时,一定要考虑能引起影迷共鸣的元素。只要产品质量好,就能迅速上热的涨粉。
    但我们都知道,想要上热搜,并不容易,那么

    测评 2021年10月22日
  • ssh整合教程(ssh服务器是怎么搭建)

    技术如何整合SSH-DWR等技术这篇文章主要为大家展示了“如何整合SSH-DWR等技术”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何整合SSH-DWR等技术”这篇文章吧。

    攻略 2021年12月24日
  • Eos离线密钥生成的PHP代码怎么写

    技术Eos离线密钥生成的PHP代码怎么写Eos离线密钥生成的PHP代码怎么写,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。虽然EOS的密钥算法类似于比特

    攻略 2021年10月23日
  • QGIS如何连接Arcgis Server发布数据

    技术QGIS如何连接Arcgis Server发布数据这篇文章主要介绍了QGIS如何连接Arcgis Server发布数据,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家

    攻略 2021年11月28日
  • hbase学习 rowKey的设计-4

    技术hbase学习 rowKey的设计-4 hbase学习 rowKey的设计-4hbase学习 rowKey的设计-4访问hbase table中的行,只有三种方式:
    1 通过单个row key访问

    礼包 2021年12月22日