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

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


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


MyClassBase定义:

package classload;

public class MyClassBase implements MyClassInterface {
	public void say() {
		System.out.append("Hello World!");
	} 
}

MyClassInteface 的定义:

package classload;


public interface MyClassInterface {
	public void say();
}


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

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

		Class<?> clz = loader.loadClass("classload.MyClassBase");
		System.out.println("loaded class:" + clz.getName() + " by " + clz.getClassLoader());
		MyClassInterface obj = (MyClassInterface) clz.newInstance();
		obj.say();

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


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

package classload;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class MyCipher {

	public static void main(String[] args) {

		String[] srcFileElement = { System.getProperty("user.dir"), "bin", "classload", "MyClassBase.class" };
		enCipherClass(String.join(File.separator, srcFileElement));
	}

	public static String enCipherClass(String path) {
		File classFile = new File(path);
		if (!classFile.exists()) {
			System.out.println("File does not exist!");
			return null;
		}

		String cipheredClass = classFile.getParent() + File.separator + "Cipher" + classFile.getName();
		System.out.println("enCipherClass() cipheredClass=" + cipheredClass);

		try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(classFile));
				BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(cipheredClass));) {

			int data = 0;
			while ((data = is.read()) != -1) {
				out.write(data ^ 0xFF);
			}

			out.flush();
			is.close();
			out.close();
		} catch (IOException e) {

			e.printStackTrace();
		}
		return cipheredClass;
	}

	public static byte[] deCihperClass(String path) {
		File file = new File(path);
		if (!file.exists()) {
			System.out.println("deCihperClass() File:" + path + " not found!");
			return null;
		}

		System.out.println("deCihperClass() path=" + path);

		try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));) {
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			int data = 0;
			while ((data = in.read()) != -1) {
				out.write(data ^ 0xFF);
			}
			in.close();
			out.flush();
			out.close();

			return out.toByteArray();

		} catch (IOException e) {
			e.printStackTrace();
		}

		return null;
	}

}

这里,仅仅是示例,加解密的算法非常简单, 加密算法是把原.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的代码:

		ClassLoader loader = new ClassLoader() {
			@Override
			public Class<?> findClass(String name) {

				System.out.println("findClass() name = " + name);

				String baseName = name.substring(name.lastIndexOf('.') + 1);

				String[] fileNameElements = { System.getProperty("user.dir"), "cipher",
						"Cipher" + baseName + ".class" };
				byte[] data = MyCipher.deCihperClass(String.join(File.separator, fileNameElements));

				Class<?> clz = defineClass(name, data, 0, data.length);
				return clz;
			}
		};


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


完整的 客户端代码:

package classload;
import java.io.File;
public class ClassLoadTest {
	public static void main(String[] args) throws Exception {
		ClassLoader loader = new ClassLoader() {
			@Override
			public Class<?> findClass(String name) {

				System.out.println("findClass() name = " + name);

				String baseName = name.substring(name.lastIndexOf('.') + 1);

				String[] fileNameElements = { System.getProperty("user.dir"), "cipher",
						"Cipher" + baseName + ".class" };
				byte[] data = MyCipher.deCihperClass(String.join(File.separator, fileNameElements));

				Class<?> clz = defineClass(name, data, 0, data.length);
				return clz;
			}
		};
		Class<?> clz = loader.loadClass("classload.MyClassBase");
		System.out.println("loaded class:" + clz.getName() + " by " + clz.getClassLoader());
		MyClassInterface obj = (MyClassInterface) clz.newInstance();
		obj.say();
	}
}


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

 loader.loadClass("classload.MyClassBase");

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

运行代码看输出:

findClass() name = classload.MyClassBase
deCihperClass() path=C:\Users\myname\myworkspace\Demo\cipher\CipherMyClassBase.class
loaded class:classload.MyClassBase by classload.ClassLoadTest$1@6d06d69c
Hello World!

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

classload.ClassLoadTest$1@6d06d69c

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


loaded class:classload.MyClassBase by sun.misc.Launcher$AppClassLoader@73d16e93
Hello World!

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

sun.misc.Launcher$AppClassLoader@73d16e93


我的 eclipse目录结构:


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





                
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值