Java安全基礎之Java序列化與反序列化

smileleooo發表於2024-05-03

目錄
  • ObjectInputStream 和 ObjectOutputStream
  • java.io.Serializable
  • 自定義序列化和反序列化

Java 的序列化(Serialization)是指將物件轉換為位元組序列的過程,而反序列化(Deserialization)則是將位元組序列轉換回物件的過程。

序列化和反序列化通常用於在網路上傳輸物件或者將物件持久化到檔案系統中。

Java 提供了 java.io.Serializable 介面來支援物件的序列化和反序列化。如果一個類實現了 Serializable 介面,那麼該類的物件就可以被序列化和反序列化。

ObjectInputStream 和 ObjectOutputStream

ObjectInputStream 和 ObjectOutputStream 是 Java 中用於序列化和反序列化物件的類,它們提供了將物件轉換為位元組流並將位元組流轉換回物件的功能。

  • ObjectOutputStream:用於將物件序列化為位元組流,依賴於 writeObject() 方法。

  • ObjectInputStream:用於從位元組流反序列化物件,依賴於 readObject() 方法。

try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.ser"))) {
    // 寫入物件到檔案
    out.writeObject(myObject);
} catch (IOException e) {
    e.printStackTrace();
}

try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.ser"))) {
    // 從檔案中讀取物件
    MyObject myObject = (MyObject) in.readObject();
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
}

java.io.Serializable

Serializable 這個介面本身並不包含任何方法,它只是一個空介面,其作用是告訴 Java 編譯器,這個類的物件可以被序列化成位元組流,也可以從位元組流中反序列化成物件。

使用檔案流進行序列化: 將物件序列化到檔案中,使用 FileOutputStream 和 FileInputStream 進行讀寫操作。

import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        try {
            String obj = "serialization example";

            // 序列化物件到檔案
            FileOutputStream fos = new FileOutputStream("object.ser");
            ObjectOutputStream os = new ObjectOutputStream(fos);
            os.writeObject(obj);
            os.close();

            //反序列化物件到檔案
            FileInputStream fis = new FileInputStream("object.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            String dobj = (String)ois.readObject();
            System.out.print(dobj);
            ois.close();

        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

程式執行過後會在當前目錄下生成一個 object.ser 檔案,這個檔案裡存放的就是反序列化過後的物件。使用十六進位制顯示,前 32 為 ACED 0005,這是反序列化很明顯的一個特徵。

image

Java 反序列化中的 ACED0005 是一個序列化流的頭部識別符號,它代表 Java 序列化協議的魔數,用於檢測資料流是否為一個有效的 Java 序列化資料流。

具體來說 ACED0005 由兩部分組成:

  • ACED 是 Java 序列化資料流的魔數,用於標識序列化資料的開始

  • 0005 代表了序列化資料流的版本號,0005表示版本5

除了使用檔案流進行序列化,還可以使用位元組陣列進行序列化,使用 ByteArrayOutputStream 將物件序列化到位元組陣列中。

import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        try {
            String obj = "serialization example";

            // 序列化物件到位元組陣列
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream os = new ObjectOutputStream(byteArrayOutputStream);
            os.writeObject(obj);
            os.close();

            // 從位元組陣列反序列化物件
            byte[] byteArray = byteArrayOutputStream.toByteArray();
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
            ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
            String dobj = (String) ois.readObject();
            System.out.print(dobj);
            ois.close();

        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

除了可以將物件序列化到檔案流和位元組陣列,還可以將物件序列化到網路流、快取流等。除了 Java 原生的序列化方式外,還有一些第三方庫可以進行物件的序列化,例如 JSON 序列化或者 XML 序列。

自定義序列化和反序列化

在 Java 中,可以透過實現 writeObject() 和 readObject() 方法來自定義序列化和反序列化過程,可以更靈活地控制物件在序列化和反序列化過程中的行為。

  • 自定義序列化:private void writeObject(ObjectOutputStream oos)

  • 自定義反序列化:private void readObject(ObjectInputStream ois)

例如:Person 類實現了 Serializable 介面

class Person implements Serializable {
    private String name;
    private transient int age; // transient 表示該欄位不參與序列化

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 自定義序列化方法
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject(); // 預設序列化其他欄位
        // 手動序列化 age 欄位
        out.writeInt(age);
    }

    // 自定義反序列化方法
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject(); // 預設反序列化其他欄位
        // 手動反序列化 age 欄位
        age = in.readInt();
    }
}

在待序列化或反序列化的類中定義 readObject 和 writeObject 方法,就來實現了自定義的序列化和反序列化操作。

參考文章:
《Java程式碼審計入門篇》


若有錯誤,歡迎指正!o( ̄▽ ̄)ブ

相關文章