Hex & Base64 encoding

Choise.o·2024년 8월 11일
0

java.security.Key 타입의 두 데이터를 비교하는 코드를 작성했다. Key 타입은 getEncoded() 호출 시 바이트 배열(byte[])로 데이터를 반환해준다. byte 배열의 데이터를 문자열로 변환해준 뒤 문자열을 비교하는 방식으로 로직을 구현했다.

바이트 배열을 문자열로 변경하기 위해 Base64 인코딩 방식을 사용했다. 그런데 코드리뷰에서 Base64보다는 Hex로 인코딩하는게 좋겠다는 피드백을 받았다. 왜 그런 피드백을 줬는지 이해하려고 퇴근 길에 GPT랑 공부한 내용을 정리해본다.

byte[]

우선, getEncoded()가 반환한 바이트 배열의 데이터는 뭘까?

java.security.Key 타입의 데이터는 메모리에 저장된 객체 데이터이다. 이 객체 데이터는 메모리의 특정 위치를 참조할 뿐, 저장하거나 직렬화하여 바이트스트림으로 전송할 수 없다.
그렇다면 저장과 전송을 위해서는 어떻게 해야하는가. 원본 데이터를 바이트 배열로 변환해주면 된다. 바이트 배열은 다시 원본 객체(Key 같은)로 변환해줄 수 있다. 즉 바이트 배열은 원본 데이터 그 자체이다.(바이트는 컴퓨터가 해석할 수 있는 최소단위 데이터이다. 1byte = 8bit)

GPT에게 '바이트 배열이 뭐야?'라고 물어봤다.

바이트 배열(byte array)은 8비트 단위의 데이터로 이루어진 배열을 말합니다. 컴퓨터에서는 데이터를 처리할 때 가장 작은 단위인 바이트(byte)로 데이터를 저장하거나 전송합니다. 바이트는 8비트로 구성되며, 각각의 비트는 0 또는 1의 값을 가질 수 있습니다. 따라서 바이트는 256개의 서로 다른 값을 표현할 수 있습니다(0에서 255까지).

[바이트 배열의 의미]

  • 메모리 저장
    바이트 배열은 메모리에 연속된 바이트로 저장된 데이터를 나타냅니다.
    예를 들어, 문자열, 파일 데이터, 이미지 데이터 등 다양한 형식의 데이터를 바이트 배열로 표현할 수 있습니다.
  • 이진 데이터
    바이트 배열은 주로 이진 데이터를 저장하거나 전송할 때 사용됩니다. 예를 들어, 이미지 파일, 비디오 파일, 암호화된 데이터, 네트워크 패킷 등은 바이트 배열로 표현됩니다.
  • 변환
    다른 데이터 형식(예: 문자열, 정수, 파일 등)을 바이트 배열로 변환할 수 있습니다. 이 변환은 데이터를 파일에 저장하거나 네트워크를 통해 전송할 때 필수적입니다.

Hex 인코딩

바이트 배열을 System.out.println() 등을 사용해서 출력해보면 객체의 참조값이 출력된다. 즉 어떤 데이터인지 눈으로 확인할 수 없다.

(예시)
byte[] byteArray = { 65, 66, 67, 68 }; // A, B, C, D를 바이트 배열로 변환

// 바이트 배열을 직접 출력
System.out.println(byteArray);   // [B@1b6d3586
// ==> 주소값이 출력되어 어떤 데이터인지 알 수 없다.

바이트 배열 데이터를 확인하려면 어떻게 해야할까?

이럴 때 사용하는게 Hex 인코딩이다. Hex 인코딩을 통해 바이트 배열을 사람이 이해할 수 있는 형태로 변환할 수 있다. Hex 로 인코딩하면 각 바이트를 두자리 16진수로 표현해준다. 16진수는 0-9와 A-F의 문자를 사용한다.

import org.apache.commons.codec.binary.Hex;

public class ByteArrayToHexExample {
    public static void main(String[] args) {
        byte[] byteArray = { 65, 66, 67, 68 }; // 'A', 'B', 'C', 'D'에 해당하는 ASCII 값

        // 바이트 배열을 16진수 문자열로 인코딩
        String hexString = Hex.encodeHexString(byteArray);

        // 16진수 문자열 출력
        System.out.println(hexString);  // 출력: 41424344
        // 16진수 값 : A - 41, B-42, C-43, D-44
    }
}

Base64 인코딩

바이트 배열을 문자열로 변환하기 위해 Base64로 인코딩하는 방식도 있다.
Base64는 바이너리 데이터를 텍스트로 변환하는 인코딩 방식 중 하나이다. Base64 인코딩은 주로 이메일, URL, 데이터 저장 등에 사용되며, 3바이트(24비트)의 데이터를 4개의 ASCII 문자로 변환한다.

바이너리 데이터?

  • 바이너리 데이터는 이진수로 표현되는 데이터를 의미하며, 컴퓨터 시스템에서 모든 데이터는 바이너리로 표현된다.
  • 바이트 배열(byte[])은 바이너리 데이터를 저장하거나 전송하기 위한 기본 데이터 구조이다.
import org.apache.commons.codec.binary.Base64;

public class Base64Example {
    public static void main(String[] args) {
        byte[] byteArray = { 65, 66, 67, 68 }; // 'A', 'B', 'C', 'D'에 해당하는 ASCII 값

        // 바이트 배열을 Base64로 인코딩
        String encodedString = Base64.encodeBase64String(byteArray);

        // Base64로 인코딩된 문자열 출력
        System.out.println("Base64 인코딩: " + encodedString); // 출력: QUJDRA==
    }
}

Hex와 Base64 비교

Hex 와 Base64 둘 다 바이너리 데이터(바이트 배열)을 문자열로 바꿔주는데 왜 Hex로 인코딩하라는 피드백을 받았을까?
결론부터 말하자면 Base64는 CharacterSet의 영향을 받을 수도 있고 Hex는 받지 않는다.

  • Hex와 Base64 비교
특징Hex(16진수 인코딩)Base64
목적주로 바이너리 데이터를 사람이 읽기쉬운 16진수로 표현바이너리 데이터를 더 짧고 효율적인 텍스트로 표현
인코딩된 크기원본 데이터의 2배 (1바이트 = 2개의 16진수 문자)원본 데이터의 약 1.33배 (3바이트 = 4개의 Base64 문자)
사용 문자 집합0-9, A-F (총 16개 문자)A-Z, a-z, 0-9, +, / (총 64개 문자)
패딩없음'=' 문자로 패딩 처리 가능
사용 사례디버깅, 로깅, 해시 출력, 바이너리 파일 저장이메일 인코딩, 데이터 URI, 웹 전송, JWT 토큰
가독성가독성 좋음길이가 짧지만 가독성 측면에서는 덜 직관적
효율성크기 면에서 비효율적 (2배로 늘어남)크기 면에서 더 효율적 (약 33% 증가)
플랫폼 독립성플랫폼 독립적(OS, 하드웨어 종속성 낮음)플랫폼 독립적

패딩 : 데이터의 크기가 3의 배수가 아닐 때 '=' 문자로 패딩을 추가하여 데이터의 길이를 조정.

  • Base64에서의 CharacterSet 영향
    • 텍스트 데이터를 Base64로 인코딩하기 전에, 데이터를 바이트 배열로 변환해야 한다.
    • 이때 어떤 Character Set(예: UTF-8, ISO-8859-1, UTF-16 등)을 사용하는지에 따라 바이트 배열의 내용이 달라질 수 있다. (동일한 텍스트라도 UTF-8과 UTF-16으로 인코딩했을 때 생성되는 바이트 배열이 다름)
    • ex) UTF-8에서 'A'는 1바이트로 표현되지만, UTF-16에서는 2바이트로 표현됨.

⇒ 즉 원본 문자열 데이터를 바이트 배열로 변환하는 과정에서 CharacterSet의 영향을 받으면 Base64 인코딩 결과가 달라질 수 있다.

결론

사실 내가 구현하려는 기능은 문자열을 바이트 배열로 변환하는건 아니라서 CharacterSet의 영향을 받지는 않을 것 같은데 그래도 어쨋든 가장 안전한 방법을 택하고자 Hex로 인코딩했다. 데이터 크기 면에서는 비효율적이지만 원본 데이터 자체가 크지 않다.)
끝_!

출처

  • Chat GPT

0개의 댓글