zoukankan      html  css  js  c++  java
  • Java类加载器( 死磕7)

    【正文】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群


    无编程不创客,无案例不学习。 一定记得去跑一跑案例哦


    类加载器系列全目录

    1.导入

    2. JAVA类加载器分类

    3. 揭秘ClassLoader抽象基类

    4. 神秘的双亲委托机制

    5. 入门案例:自定义一个文件系统的classLoader

    6. 基础案例:自定义一个网络类加载器

    7. 中级案例:设计一个加密的自定义网络加载器

    8. 高级案例1:使用ASM技术,结合类加载器,解密AOP原理

    9. 高级案例2:上下文加载器原理和案例

  • 相关阅读:
    win7-64系统下安装nodejs
    Vue项目使用npm run dev出现npm ERR! missing script: dev
    本地环境时覆盖Apollo配置
    金蝶K3序时簿页面增加物料即时库存显示功能
    LeetCode——开篇
    url 与 params 参数的常见操作归纳汇总(含精心准备的注释)
    如何让 arcgis require 里定义的方法可以在全局访问?
    字体图标库 iconfont、iconmoon 的维护管理与使用探索
    【转载】ES5-ES12 常用语法特性
    一次 outline 去除经验(非继承属性,看着像继承)
  • 原文地址:https://www.cnblogs.com/crazymakercircle/p/9824139.html
Copyright © 2011-2022 走看看