利用自定义的 ClassLoader 加密 Java Class 文件

本文演示利用自定义的 ClassLoader 加密 Java Class 文件


首先,我们定义一个需要被加密的java Class: classload.MyClassBase。 为了让客户端使用,需要定义一个 MyClassInterface, 这样客户端就不会直接引用 MyClassBase了,发布到客户端的class文件中是不存在 MyClassBase这个类的。


MyClassBase定义:

[java] view plain copy
  1. package classload;  
  2.   
  3. public class MyClassBase implements MyClassInterface {  
  4.     public void say() {  
  5.         System.out.append("Hello World!");  
  6.     }   
  7. }  

MyClassInteface 的定义:

[java] view plain copy
  1. package classload;  
  2.   
  3.   
  4. public interface MyClassInterface {  
  5.     public void say();  
  6. }  


我们把 classload/MyClassBase.class 这个文件进行加密处理, 变成另外一个文件,加密后的文件名可以放到任意位置, 这里我们把它放到 cipher/CipherMyClassBase.class。(CipherMyClassBase.class 就是加密后的文件)

如何加密后面再说, 先看看,客户端是如何使用的:

[java] view plain copy
  1. Class<?> clz = loader.loadClass("classload.MyClassBase");  
  2. System.out.println("loaded class:" + clz.getName() + " by " + clz.getClassLoader());  
  3. MyClassInterface obj = (MyClassInterface) clz.newInstance();  
  4. obj.say();  

这里客户端通过自定义的 ClassLoader 变量名loader, load一个名字叫 "classload.MyClassBase" 的 Class, 通过 newInstance()方法 new 出它的一个obj, 并强制转换成上面定义的接口类型 MyClassInterface。 注意:在客户端代码中是没有 MyClassBase 这个类。自定义的 loader 会 通过指定的 name  参数 “classload.MyClassBase”, 去找到加密后的文件 cipher/CipherMyClassBase.class, 并把它解密,返回 MyClassBase 的class实例。


现在看看 class文件加密、解密的代码:

[java] view plain copy
  1. package classload;  
  2.   
  3. import java.io.BufferedInputStream;  
  4. import java.io.BufferedOutputStream;  
  5. import java.io.ByteArrayOutputStream;  
  6. import java.io.File;  
  7. import java.io.FileInputStream;  
  8. import java.io.FileOutputStream;  
  9. import java.io.IOException;  
  10.   
  11. public class MyCipher {  
  12.   
  13.     public static void main(String[] args) {  
  14.   
  15.         String[] srcFileElement = { System.getProperty("user.dir"), "bin""classload""MyClassBase.class" };  
  16.         enCipherClass(String.join(File.separator, srcFileElement));  
  17.     }  
  18.   
  19.     public static String enCipherClass(String path) {  
  20.         File classFile = new File(path);  
  21.         if (!classFile.exists()) {  
  22.             System.out.println("File does not exist!");  
  23.             return null;  
  24.         }  
  25.   
  26.         String cipheredClass = classFile.getParent() + File.separator + "Cipher" + classFile.getName();  
  27.         System.out.println("enCipherClass() cipheredClass=" + cipheredClass);  
  28.   
  29.         try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(classFile));  
  30.                 BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(cipheredClass));) {  
  31.   
  32.             int data = 0;  
  33.             while ((data = is.read()) != -1) {  
  34.                 out.write(data ^ 0xFF);  
  35.             }  
  36.   
  37.             out.flush();  
  38.             is.close();  
  39.             out.close();  
  40.         } catch (IOException e) {  
  41.   
  42.             e.printStackTrace();  
  43.         }  
  44.         return cipheredClass;  
  45.     }  
  46.   
  47.     public static byte[] deCihperClass(String path) {  
  48.         File file = new File(path);  
  49.         if (!file.exists()) {  
  50.             System.out.println("deCihperClass() File:" + path + " not found!");  
  51.             return null;  
  52.         }  
  53.   
  54.         System.out.println("deCihperClass() path=" + path);  
  55.   
  56.         try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));) {  
  57.             ByteArrayOutputStream out = new ByteArrayOutputStream();  
  58.             int data = 0;  
  59.             while ((data = in.read()) != -1) {  
  60.                 out.write(data ^ 0xFF);  
  61.             }  
  62.             in.close();  
  63.             out.flush();  
  64.             out.close();  
  65.   
  66.             return out.toByteArray();  
  67.   
  68.         } catch (IOException e) {  
  69.             e.printStackTrace();  
  70.         }  
  71.   
  72.         return null;  
  73.     }  
  74.   
  75. }  

这里,仅仅是示例,加解密的算法非常简单, 加密算法是把原.class 文件的每一个字节和 0xFF 异或, 对应的解密方法就是 加密后的字节和 0xFF异或,数学原理为: A^B^B = A。 

上面代码中,加密方法enCipherClass 根据输入的XXX.class文件名, 产生一个加密后的CipherXXX.class 文件, 这里把classload\MyClassBase.class  变为 classload\CipherMyClassBase.class。 

解密方法 deCihperClass 把输入的 class 文件, 变为 byte [] 返回。


运行加密算法后,需要手工把  classload\CipherMyClassBase.class 复制到目录 cipher/, 并删除目录 classload\下的 CipherMyClassBase.class  和 MyClassBase.class。

 

自定义ClassLoader的代码:

[java] view plain copy
  1. ClassLoader loader = new ClassLoader() {  
  2.     @Override  
  3.     public Class<?> findClass(String name) {  
  4.   
  5.         System.out.println("findClass() name = " + name);  
  6.   
  7.         String baseName = name.substring(name.lastIndexOf('.') + 1);  
  8.   
  9.         String[] fileNameElements = { System.getProperty("user.dir"), "cipher",  
  10.                 "Cipher" + baseName + ".class" };  
  11.         byte[] data = MyCipher.deCihperClass(String.join(File.separator, fileNameElements));  
  12.   
  13.         Class<?> clz = defineClass(name, data, 0, data.length);  
  14.         return clz;  
  15.     }  
  16. };  


这里采用匿名内部类的方式定义自己的 ClassLoader, 定义自己的ClassLoader,只需要继承 ClassLoader, 并覆写方法 findClass 即可。


完整的 客户端代码:

[java] view plain copy
  1. package classload;  
  2. import java.io.File;  
  3. public class ClassLoadTest {  
  4.     public static void main(String[] args) throws Exception {  
  5.         ClassLoader loader = new ClassLoader() {  
  6.             @Override  
  7.             public Class<?> findClass(String name) {  
  8.   
  9.                 System.out.println("findClass() name = " + name);  
  10.   
  11.                 String baseName = name.substring(name.lastIndexOf('.') + 1);  
  12.   
  13.                 String[] fileNameElements = { System.getProperty("user.dir"), "cipher",  
  14.                         "Cipher" + baseName + ".class" };  
  15.                 byte[] data = MyCipher.deCihperClass(String.join(File.separator, fileNameElements));  
  16.   
  17.                 Class<?> clz = defineClass(name, data, 0, data.length);  
  18.                 return clz;  
  19.             }  
  20.         };  
  21.         Class<?> clz = loader.loadClass("classload.MyClassBase");  
  22.         System.out.println("loaded class:" + clz.getName() + " by " + clz.getClassLoader());  
  23.         MyClassInterface obj = (MyClassInterface) clz.newInstance();  
  24.         obj.say();  
  25.     }  
  26. }  


最后,需要注意的一点,客户端通过自定义的ClassLoader  像如下代码加载类:

[java] view plain copy
  1. loader.loadClass("classload.MyClassBase");  

自定义的 ClassLoader 会先把 类加载操作先委派给它的parent, 也就是系统默认的类加载器, 如果系统默认的类加载器,找不到  classload.MyClassBase 这个类,才会调用自己的类加载器,如果classpath下有classload.MyClassBase 这个类,系统默认的类加载器就会找到这个类, 那么自己的类加载器是不会调用的,所以前面说过,需要把classload/MyClassBase.class这个文件删除,自己的类加载器才会起作用。

运行代码看输出:

[plain] view plain copy
  1. findClass() name = classload.MyClassBase  
  2. deCihperClass() path=C:\Users\myname\myworkspace\Demo\cipher\CipherMyClassBase.class  
  3. loaded class:classload.MyClassBase by classload.ClassLoadTest$1@6d06d69c  
  4. Hello World!  

从上面的输出看,使用的类加载器为我们自定的那个:

[plain] view plain copy
  1. classload.ClassLoadTest$1@6d06d69c  

如果,把MyClassBase.class 放回到 bin/classload/MyClassBase.class, 输出就变了:


[plain] view plain copy
  1. loaded class:classload.MyClassBase by sun.misc.Launcher$AppClassLoader@73d16e93  
  2. Hello World!  

这个时候,使用的类加载器为:

[plain] view plain copy
  1. sun.misc.Launcher$AppClassLoader@73d16e93  


我的 eclipse目录结构:


只需关注 classload这个package和cipher目录, 其它包与本文无关。

发布了515 篇原创文章 · 获赞 1217 · 访问量 464万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 1024 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览