【正文】Java类加载器( CLassLoader )死磕7:
基于加密的自定义网络加载器
本小节目录
7.1. 加密传输Server端的源码
7.2. 加密传输Client端的源码
7.3. 使用亦或实现简单加密和解密算法
7. 网络加密SafeClassLoader的源码
7.5. SafeSocketLoader的使用
众所周知,java代码很容易被反编译,如果你需要把自己的代码进行加密,可以先将编译后的代码用某种加密算法加密,然后结合自己的网络类加载器,进行加密后的安全传输。
客户端接收到加密后的字节码后,负责将这段加密后的代码还原。
1.1.1. 加密传输Server端的源码
和文件传输Server端的源码基本一致,只有一行代码的差别。
简单粗暴,直接上码。
package com.crazymakercircle.classLoader; import com.crazymakercircle.config.SystemConfig; import com.crazymakercircle.util.DeEnCode; import com.crazymakercircle.util.IOUtil; import com.crazymakercircle.util.Logger; import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * 文件传输Server端<br> */ public class SafeSocketServer { ServerSocket serverSocket = null; static String filePath = null; public SafeSocketServer() throws Exception { serverSocket = new ServerSocket(SystemConfig.SOCKET_SERVER_PORT); this.filePath = SystemConfig.CLASS_SERVER_PATH; startServer(); } /** * 启动服务端 * 使用线程处理每个客户端传输的文件 * * @throws Exception */ public void startServer() { while (true) { // server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的 Logger.info("server listen at:" + SystemConfig.SOCKET_SERVER_PORT); Socket socket = null; try { socket = serverSocket.accept(); // 每接收到一个Socket就建立一个新的线程来处理它 new Thread(new Task(socket)).start(); } catch (Exception e) { e.printStackTrace(); } } } /** * 处理客户端传输过来的文件线程类 */ class Task implements Runnable { private Socket socket; private DataInputStream dis; private FileOutputStream fos; public Task(Socket socket) { this.socket = socket; } @Override public void run() { try { dis = new DataInputStream(socket.getInputStream()); // 文件名和长度 String fileName = dis.readUTF(); DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); sendFile(fileName, dos); } catch (Exception e) { e.printStackTrace(); } finally { IOUtil.closeQuietly(fos); IOUtil.closeQuietly(dis); IOUtil.closeQuietly(socket); } } private void sendFile(String fileName, DataOutputStream dos) throws Exception { fileName=classNameToPath(fileName); fileName = SafeSocketServer.filePath + File.separator + fileName; File file = new File(fileName); if (!file.exists()) { throw new Exception("file not found! :"+fileName); } long fileLen = file.length(); dos.writeLong(fileLen); dos.flush(); byte one= (byte) 0xff; FileInputStream fis = new FileInputStream(file); // 开始传输文件 Logger.info("======== 开始传输文件 ========"); byte[] bytes = new byte[1024]; int length = 0; long progress = 0; while ((length = fis.read(bytes, 0, bytes.length)) != -1) { DeEnCode.encode(bytes,length); dos.write(bytes, 0, length); dos.flush(); progress += length; Logger.info("| " + (100 * progress / fileLen) + "% |"); } Logger.info("======== 文件传输成功 ========"); } } private String classNameToPath(String className) { return className.replace('.', '/') + ".class"; } public static void main(String[] args) { try { SafeSocketServer socketServer = new SafeSocketServer(); socketServer.startServer(); } catch (Exception e) { e.printStackTrace(); } } }
和文件传输Server端的源码基本一致,只有一行代码的差别。这个类仅仅增加的一行是:
DeEnCode.encode(bytes,length);
其目的是,在发送字节码之前,使用定义的加密函数,进行字节码加密。
源码比较长,建议运行main函数,先将服务端的源码跑起来,然后再阅读代码,这样阅读起来更加容易懂。
另外,在使用基于网络的类加载器之前,一定要确保服务端的代码先执行。否则客户端会报错。
案例路径:com.crazymakercircle.classLoader.SafeSocketServer
案例提示:无编程不创客、无案例不学习。一定要跑案例哦
运行的结果是:
<clinit> |> 开始加载配置文件到SystemConfig loadFromFile |> load properties: /system.properties startServer |> server listen at:18899
看到以上结果,表示服务端开始启动。监听了18899端口,等待客户端的连接。
1.1.2. 加密传输Client端的源码
客户端的工作:
建立和服务器的TCP连接后,首先做的第一步工作,是发送文件名称给服务器端。
然后阻塞,直到服务器的数据过来。客户端开始接受服务器传输过来的数据。接受数据的工作由函数receivefile()完成。
整个的数据的读取工作分为两步,先读取文件的大小,然后读取传输过来的文件内容。
在传输文件内容的字节码时,需要对字节码进行解密。
简单粗暴,直接上源码。
/** * 文件传输Client端 */ public class SafeSocketClient { private Socket client; private FileInputStream fis; private DataOutputStream dos; /** * 构造函数<br/> * 与服务器建立连接 * * @throws Exception */ public SafeSocketClient() throws IOException { this.client = new Socket( SystemConfig.SOCKET_SERVER_IP, SystemConfig.SOCKET_SERVER_PORT ); Logger.info("Cliect[port:" + client.getLocalPort() + "] 成功连接服务端"); } /** *向服务端去取得文件 * * @throws Exception */ public byte[] getFile(String fileName) throws Exception { byte[] result = null; try { dos = new DataOutputStream(client.getOutputStream()); // 文件名和长度 dos.writeUTF(fileName); dos.flush(); DataInputStream dis = new DataInputStream(client.getInputStream()); result = receivefile(dis); Logger.info("文件接收成功,File Name:" + fileName); } catch (Exception e) { e.printStackTrace(); } finally { IOUtil.closeQuietly(fis); IOUtil.closeQuietly(dos); IOUtil.closeQuietly(client); } return result; } public byte[] receivefile(DataInputStream dis) throws Exception { int fileLength = (int) dis.readLong(); ByteArrayOutputStream bos = new ByteArrayOutputStream(fileLength); long startTime = System.currentTimeMillis(); Logger.info("block IO 传输开始:"); // 开始接收文件 byte[] bytes = new byte[1024]; int length = 0; while ((length = dis.read(bytes, 0, bytes.length)) != -1) { DeEnCode.decode(bytes,length); bos.write(bytes, 0, length); bos.flush(); } Logger.info(" Size:" + IOUtil.getFormatFileSize(fileLength)); long endTime = System.currentTimeMillis(); Logger.info("block IO 传输毫秒数:" + (endTime - startTime)); bos.flush(); byte[] result = bos.toByteArray(); IOUtil.closeQuietly(bos); return result; } }
与前面的基础案例SafeSocketClient 相比,只有一行代码的差别。这个类仅仅增加的一行是:
DeEnCode.decode(bytes,length);
其目的是,在接受字节码之后,使用定义的解密函数,进行字节码解密。
案例路径:com.crazymakercircle.classLoader.SafeSocketClient
此案例类没法独立运行,因为没有运行的入口。只能作为基础类,供其他类调用。
下面介绍一下加密和解密的算法。
1.1.3. 使用亦或实现简单加密和解密算法
加密和解密算法有很多,这里为为了演示方便,使用亦或的方式,实现简单加密和解密算法。
简单粗暴,直接上代码:
public class DeEnCode { private static final String key0 = "FECOI()*&<MNCXZPKL"; private static final Charset charset = Charset.forName("UTF-8"); private static byte[] keyBytes = key0.getBytes(charset); public static void encode(byte[] b, int length) { for (int i = 0, size =length; i < size; i++) { for (byte kb : keyBytes) { b[i] = (byte) (b[i] ^ kb); } } } public static void decode(byte[] dee, int length) { for (int i = 0, size =length; i < size; i++) { for (byte kb : keyBytes) { dee[i] = (byte) (dee[i] ^ kb); } } } public static void main(String[] args) { String s = "you are right ok 测试"; byte[] sb = s.getBytes(); encode(sb,sb.length); decode(sb, sb.length); Logger.info(new String(sb)); } }
案例路径:com.crazymakercircle.util.DeEnCode
无编程不创客、无案例不学习。一定要跑案例哦
运行上面的main方法,测试一下加密和解密。结果是:
main |> you are right ok 测试
从结果可以看到,解密后的数据和加密前的数据是一致的。说明这组算法是没有问题的。
1.1.4. 网络加密SafeClassLoader的源码
与前面的网络类加载器SocketClassLoader,只有一行之差。
源码如下:
public class SafeClassLoader extends ClassLoader {
public SafeClassLoader() {
}
protected Class<?> findClass(String name)
throws ClassNotFoundException {
Logger.info("findClass name = " + name);
byte[] classData = null;
try {
SafeSocketClient client = new SafeSocketClient();
classData = client.getFile(name);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
}
1.1.5. SafeSocketLoader的使用
简单粗暴,先上代码:
package com.crazymakercircle.classLoaderDemo.net; import com.crazymakercircle.classLoader.SafeClassLoader; import com.crazymakercircle.config.SystemConfig; import com.crazymakercircle.petStore.pet.IPet; import com.crazymakercircle.util.ClassLoaderUtil; import com.crazymakercircle.util.Logger; public class SafeNetDemo { public static void testLoader() { try { SafeClassLoader classLoader = new SafeClassLoader(); String className = SystemConfig.PET_DOG_CLASS; Class dogClass = classLoader.loadClass(className); Logger.info("显示dogClass的ClassLoader =>"); ClassLoaderUtil.showLoader4Class(dogClass); IPet pet = (IPet) dogClass.newInstance(); pet.sayHello(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } public static void main(String[] args) { testLoader(); } }
案例路径:com.crazymakercircle.classLoaderDemo.base.SafeSocketDemo
案例提示:无编程不创客、无案例不学习。一定要跑案例哦
运行的结果,与前面的SocketLoaderDemo结果是相同的,这里不在赘述。
源码:
代码工程: classLoaderDemo.zip
下载地址:在疯狂创客圈QQ群文件共享。
疯狂创客圈:如果说Java是一个武林,这里的聚集一群武痴, 交流编程体验心得
QQ群链接:疯狂创客圈QQ群
无编程不创客,无案例不学习。 一定记得去跑一跑案例哦
类加载器系列全目录