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:上下文加載器原理和案例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章