本文演示利用自定义的 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");
运行代码看输出:
- 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目录, 其它包与本文无关。
本文介绍了一种利用自定义ClassLoader加密Java Class文件的方法。通过加密Class文件并在客户端解密,确保了代码的安全性。客户端通过自定义的ClassLoader加载类,并实现类的实例化。
1394





