zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java类加载器( 死磕7)

JAVA 加载
2023-09-11 14:13:57 时间

文章很长,而且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :

免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:尼恩Java面试宝典 最新版 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取


【正文】Java类加载器( CLassLoader )死磕7:

基于加密的自定义网络加载器
7.1. 加密传输Server端的源码
7.2. 加密传输Client端的源码
7.3. 使用亦或实现简单加密和解密算法
7.4. 网络加密SafeClassLoader的源码
7.5. SafeSocketLoader的使用

众所周知,java代码很容易被反编译,如果你需要把自己的代码进行加密,可以先将编译后的代码用某种加密算法加密,然后结合自己的网络类加载器,进行加密后的安全传输。

客户端接收到加密后的字节码后,负责将这段加密后的代码还原。

说明:本文会以pdf格式持续更新,更多最新尼恩3高pdf笔记,请从下面的链接获取:语雀 或者 码云

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端
*/

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

案例提示:无编程不创客、无案例不学习。一定要跑案例哦

运行的结果是:

|> 开始加载配置文件到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;

/**
 * 构造函数
 * 与服务器建立连接
 *
 * @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<span style="color: rgb(0, 0, 255);">&lt;?</span>&gt; 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 =&gt;");

        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

下载地址:下面链接获取。

疯狂创客圈:如果说Java是一个武林,这里的聚集一群武痴, 交流编程体验心得

说明:本文会以pdf格式持续更新,更多最新尼恩3高pdf笔记,请从下面的链接获取:语雀 或者 码云