如何理解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)

相关推荐

  • 如何在添加新板时配置u-boot-env

    技术openwrt19.07添加新板时u-boot-env如何配置这篇文章主要介绍openwrt19.07添加新板时u-boot-env如何配置,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1.在

    攻略 2021年12月17日
  • 分布式机器学习中的模型聚合

    技术分布式机器学习中的模型聚合 分布式机器学习中的模型聚合我follow的这篇论文在联邦学习(分布式)的情景下引入了多任务学习,其采用的手段是使每个client/task节点的训练数据分布不同,从而使各

    礼包 2021年12月3日
  • Maven简介与Maven相关概念

    技术Maven简介与Maven相关概念 Maven简介与Maven相关概念目录Maven第一部分 Maven简介1 项目开发中的问题2 Maven概述Maven定义Maven的作用第二部分 Maven相

    礼包 2021年11月27日
  • 如何快速搭建实用的爬虫管理平台

    技术如何快速搭建实用的爬虫管理平台这篇文章主要讲解了“如何快速搭建实用的爬虫管理平台”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何快速搭建实用的爬虫管理平台”吧!爬虫

    攻略 2021年10月23日
  • 如何使用Eviews做辅助回归来检验模型是否存在多重共线性

    技术如何使用Eviews做辅助回归来检验模型是否存在多重共线性本篇文章为大家展示了如何使用Eviews做辅助回归来检验模型是否存在多重共线性,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有

    攻略 2021年11月9日
  • 抖音刷赞免费,抖音刷赞推广网站

    技术抖音刷赞免费,抖音刷赞推广网站抖音刷赞免费,抖音刷赞推广网站
    情绪不稳定哪只我一个,经常刷到“某某某崩溃大哭”的文章,就知道现代人的生活情绪有多不稳定。
    抖音刷赞神器免费版app
    他哑口无言,默默离开了办公室,然后

    测评 2021年11月13日