직렬화 / 역직렬화

niireymik·2024년 6월 3일


🔄️ 데이터 직렬화 / 역직렬화

기본적으로 데이터 직렬화와 역직렬화는 메모리를 디스크에 저장하거나 네트워크 통신에 사용하기 위한 형식으로 변환하는 것과 그 반대의 작업을 말한다.

직렬화가 필요한 이유

우리가 개발 언어로 무엇을 사용하든(C++, C, C#, Java 등,,) 사용하는 데이터들의 메모리 구조는 크게 2가지로 나뉜다.

  1. 값 형식 데이터(Value Type) : int, char 등의 데이터가 스택에 쌓이고 직접 접근 가능
  2. 참조 형식 데이터(Reference Type) : 힙에 메모리가 할당되고 스택에서 이 힙 메모리를 참조

문제는, 참조 형식 데이터는 실제 데이터 값이 아닌 힙에 할당되어 잇는 메모리 번지 주소를 가지고 있기 때문에 저장, 통신에 사용할 수 없다.

동적 할당된 데이터의 경우 주소값을 파일에 저장해두었다가 프로그램을 종료 및 재시작 후 접근해도 기존에 할당되었던 메모리는 해제되고 없어지며, 네트워크 통신 또한 각 PC마다 사용하고 있는 메모리 공간 주소가 당연하게도 다르기 때문이다.

이러한 이유 때문에, 데이터 직렬화를 통해서 텍스트 또는 바이너리의 형태로 기존 데이터를 저장 또는 통신해서 유의미한 데이터가 되도록 하는 것이다!



💡
데이터 직렬화 / 역직렬화의 개념을 이해했다면, 본격적으로 Java의 직렬화 / 역직렬화에 관해 알아보자!

자바에서 직렬화(serialize)란 자바 언어에서 사용되는 Object 또는 Data를 다른 컴퓨터의 자바 시스템에서도 사용 할수 있도록 바이트 스트림(stream of bytes) 형태로 연속적인(serial) 데이터로 변환하는 포맷 변환 기술을 말한다. 그 반대 개념인 역직렬화는(Deserialize)는 바이트로 변환된 데이터를 원래대로 자바 시스템의 Object 또는 Data로 변환하는 기술이다.

(말이 어렵긴 하지만 이는 자바 직렬화와 역직렬화의 정석적인 개념이다. 아래에서 쉽게 풀어 정리한 글로 확실히 알아보도록 하자! 🤗)



♒ 자바 직렬화 Java Serialization

자바 직렬화란 서두에서 말했 듯, 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터 변환하는 기술이다.

Java에서 입출력은 Stream이라는 데이터 통로를 통해서 이동한다. 다만 객체는 바이트형이 아니기 때문에, 스트림을 통해서 데이터를 파일 저장이나 네트워크 전송에 이용되는 것은 기본적으로 불가능하다.

그래서 객체를 스트림을 통해 주고받으려면, 바이트를 배열로 변환하는 작업이 필요하다! 이를 직렬화라고 한다.

간단히 정리하자면,

객체를 스트림이라는 데이터 통로를 통해 이동시키기 위해, 바이트 배열로 변환하는 작업

이 바로 직렬화이다! 🤗

이를 시스템적으로 살펴보면, JVM의 힙이나 스택 메모리에 있는 데이터에 대한 직렬화를 통해 바이트 형태로 변환하여 데이터베이스나 파일과 같은 외부 저장소에 저장해두고, 다른 컴퓨터에서 이 파일을 가져와 역직렬화를 통해 자바 객체로 변환해서 JVM 메모리에 적재하는 것이다.



⚛️ 자바 역직렬화 Java Deserialization

직렬화의 개념을 이해했다면 역직렬화의 개념은 자연스레 따라온다.
이는 다음과 같이 정리할 수 있겠다.

스트림을 통해 받은 바이트 배열을, 원래의 객체로 변환하는 작업

당연하겠지만, 역직렬화는 이미 직렬화된 데이터에 관해서만 적용된다.

아래는 자바 직렬화와 역직렬화의 흐름을 나타낸 그림이다.



직렬화 vs JSON

자바 직렬화는 외부 파일이나 네트워크를 통해 클라이언트 간에 객체 데이터를 주고 받을 때 사용된다.

그런데 여기서 드는 의문점은, "대표적으로 JOSN이라는 훌륭한 데이터 포맷이 있는데 왜 굳이 직렬화가 필요한가?"라는 부분이다.

실제로 JSON은 웹(Web) 뿐만 아니라 게임 쪽에서도 설정 파일로 쓰이거나 데이터를 교환할때 범용적으로 사용된다. 그리고 직렬화는 오로지 자바 프로그램에서만 사용이 가능하지만, JSON 형태로 객체 데이터를 저장해두면 파이썬, 자바스크립트에서도 범용적으로 사용이 가능하다.

앞에서 짚은 의문에 대해, 먼저 자바 직렬화의 장점에 대해 알아보자.

자바 직렬화의 장점

  1. 자바 직렬화는 자바의 고유 기술인 만큼 당연히 자바 시스템에서 개발에 최적화되어 있다.
  2. 자바의 광활한 레퍼런스 타입에 대해 제약 없이 외부에 내보낼 수 있다.

기본형(int, double, string) 타입이나 배열(array)과 같은 타입들은 웬만한 프로그래밍 언어가 공통적으로 사용하는 타입이기 때문에, 이러한 값들을 JSON으로도 충분히 커버할 수 있다.

하지만, 자바의 온갖 컬렉션이나 클래스, 인터페이스 타입들은 어떨까? 혹은 사용자가 커스텀으로 자료형 타입을 만들어 사용하는 경우는 어떨까? 이떄는 단순 파일 포맷만으로는 한계가 있고, 이들을 외부에 내보내기 위해선 각 데이터를 매칭시키는 별도의 파싱(parsing)이 필요하다.

👉 그에 반해, 직렬화를 이용한다면 비록 파이썬이나 자바스크립트와 같은 다른 시스템에서는 사용하지는 못할지라도, 직렬화 기본 조건만 지킨다면 하드한 작업없이 그냥 바로 외부에 보낼수가 있다. 그리고 역직렬화를 통해 읽어들이면 데이터 타입이 자동으로 맞춰지기 때문에 자바 클래스의 기능들을 파싱 없이 곧바로 다시 이용할 수 있는 것이다.

이것이 자바 직렬화를 사용하는 이유가 되는 장점들이다!

그러나 직렬화의 고유한 장점들에도 불구하고, 요즘은 범용적인 JSON을 이용하는 추세가 점점 늘고 있다. 따라서 JSON 이냐 직렬화 이냐 에 대한 명확한 정답은 없고 '목적에 따라 적절히 써야 한다' 정도로 정리할 수 있을 것 같다.


자바 직렬화의 단점

"직렬화의 고유한 장점들에도 불고하고, 요즘은 범용적인 JSON을 이용하는 추세가 점점 늘고 있다." -> 이는 직렬화가 보유하는 치명적인 단점의 영향이 있다. (사실 직렬화는 장점보다 단점이 극명하게 많다...)

1. 직렬화는 용량이 크다.
직렬화는 객체에 저장된 데이터값 뿐만 아니라 타입이나 클래스에 관한 메타 데이터를 가지고 있으므로 용량을 꽤 차지한다. 그래서 같은 정보를 저장해도 직렬화와 JSON은 파일의 용량 크기가 거의 2배 이상 난다.

따라서 장기간 저장하는 정보는 직렬화가 부적합하다.

2. 역직렬화는 위험하다.
결론부터 말하자면 직렬화 설정 자체는 문제는 없지만, 남이 만든 것을 역직렬화 과정에서 나도 모르게 공격당할 위험성이 있다.

역직렬화는 생성자 없이 인스턴스화가 가능하기 때문에 보이지 않는 생성자라고도 불린다. 객체를 직렬화하고 역직렬화하는 과정에서 중간에 누군가 가로채 파일 바이트 내용을 조작한다면, 이는 큰 문제가 될 수 있다.

따라서 신뢰할 수 없는 데이터는 절대 역직렬화하면 안 되며, (극단적으로 말해서) 직렬화의 잠재적인 위험성을 회피하는 가장 좋은 방법은 아무것도 역직렬화하지 않는 것이다.

3. 직렬화를 대체할 수 없다면
만일 Serializable을 구현한 클래스만 지원하는 프레임워크를 사용해야 하거나 어쩔 수 없이 클래스에 직렬화를 설정해야 할 경우가 있을 수 있다. 이때에는 역직렬화 필터링(ObjectInputFilter)와 같은 역직렬화 방어 기법들을 사용하면 된다. 이는 데이터 스트림이 역직렬화되기 전에 필터 조건문을 수행하여 특정 클래스만 허용하거나 제외하도록 한다.

그러나 이렇게 공격에 대비한다 해도 여전히 공격에 취약하기에 역시나 가장 확실한 방법은 역직렬화 자체를 피하는 것이다.

4. 릴리즈 후에 수정이 어렵다.
클래스가 Serializable을 구현하게 되면 직렬화된 바이트 스트림 인코딩도 하나의 공개 API가 되는 것이다. 그래서 직렬화을 구현한 클래스가 널리 퍼지면 그 직렬화 형태도 영원히 지원해야한다. 클래스의 내부 구현을 수정한다면 원래의 직렬화 형태와 달라지게 되기 때문이다.

즉, Serializable을 구현한 순간부터 해당 객체의 유지보수는 직렬화에 묶이게 되는 것이다. 

5. 클래스 캡슐화가 깨진다.
만일 직렬화할 클래스에 private 멤버가 있어도 직렬화를 하게 되면 그대로 외부로 노출되게 된다. (직렬화를 제외하려면 별도로 transient 설정해야 된다)

따라서 Serializable을 구현하면 직렬화 형태가 하나의 공개 API가 되어 캡슐화가 깨지게 된다.

6. 버그와 보안에 취약하다.
위에서 언급한 보이지 않는 생성자문제와 이어진다. 역직렬화는 언어의 기본 메커니즘을 우회하여 객체를 바로 생성하도록 한다.

만일 어느 객체가 생성자를 통해 인스턴스화할 때 불변식이나 허가되지 않은 접근을 설정해두더라도, 역직렬화를 통하면 이를 무시하고 생성된다는 점에서 문제가 되는 것이다.


💡
(+) 위에서 확인할 수 있듯이, 자바 직렬화는 수많은 단점과 위험 요소가 존재한다. 실제로 Java의 직렬화 및 역직렬화는 꼭 필요한 상황이 아니면 피할 것이 권고된다.



📝 정리하기

0개의 댓글