您的位置:首页 > 编程语言 > Java开发

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

2016-05-31 11:58 579 查看
本文演示利用自定义的 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目录, 其它包与本文无关。


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息