java.security.Key 타입의 두 데이터를 비교하는 코드를 작성했다. Key 타입은 getEncoded() 호출 시 바이트 배열(byte[])로 데이터를 반환해준다. byte 배열의 데이터를 문자열로 변환해준 뒤 문자열을 비교하는 방식으로 로직을 구현했다.
바이트 배열을 문자열로 변경하기 위해 Base64 인코딩 방식을 사용했다. 그런데 코드리뷰에서 Base64보다는 Hex로 인코딩하는게 좋겠다는 피드백을 받았다. 왜 그런 피드백을 줬는지 이해하려고 퇴근 길에 GPT랑 공부한 내용을 정리해본다.
우선, getEncoded()가 반환한 바이트 배열의 데이터는 뭘까?
java.security.Key 타입의 데이터는 메모리에 저장된 객체 데이터이다. 이 객체 데이터는 메모리의 특정 위치를 참조할 뿐, 저장하거나 직렬화하여 바이트스트림으로 전송할 수 없다.
그렇다면 저장과 전송을 위해서는 어떻게 해야하는가. 원본 데이터를 바이트 배열로 변환해주면 된다. 바이트 배열은 다시 원본 객체(Key 같은)로 변환해줄 수 있다. 즉 바이트 배열은 원본 데이터 그 자체이다.(바이트는 컴퓨터가 해석할 수 있는 최소단위 데이터이다. 1byte = 8bit)
GPT에게 '바이트 배열이 뭐야?'라고 물어봤다.
바이트 배열(byte array)은 8비트 단위의 데이터로 이루어진 배열을 말합니다. 컴퓨터에서는 데이터를 처리할 때 가장 작은 단위인 바이트(byte)로 데이터를 저장하거나 전송합니다. 바이트는 8비트로 구성되며, 각각의 비트는 0 또는 1의 값을 가질 수 있습니다. 따라서 바이트는 256개의 서로 다른 값을 표현할 수 있습니다(0에서 255까지).
[바이트 배열의 의미]
- 메모리 저장
바이트 배열은 메모리에 연속된 바이트로 저장된 데이터를 나타냅니다.
예를 들어, 문자열, 파일 데이터, 이미지 데이터 등 다양한 형식의 데이터를 바이트 배열로 표현할 수 있습니다.- 이진 데이터
바이트 배열은 주로 이진 데이터를 저장하거나 전송할 때 사용됩니다. 예를 들어, 이미지 파일, 비디오 파일, 암호화된 데이터, 네트워크 패킷 등은 바이트 배열로 표현됩니다.- 변환
다른 데이터 형식(예: 문자열, 정수, 파일 등)을 바이트 배열로 변환할 수 있습니다. 이 변환은 데이터를 파일에 저장하거나 네트워크를 통해 전송할 때 필수적입니다.
바이트 배열을 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 인코딩은 주로 이메일, 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는 CharacterSet의 영향을 받을 수도 있고 Hex는 받지 않는다.
특징 | 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의 배수가 아닐 때 '=' 문자로 패딩을 추가하여 데이터의 길이를 조정.
⇒ 즉 원본 문자열 데이터를 바이트 배열로 변환하는 과정에서 CharacterSet의 영향을 받으면 Base64 인코딩 결과가 달라질 수 있다.
사실 내가 구현하려는 기능은 문자열을 바이트 배열로 변환하는건 아니라서 CharacterSet의 영향을 받지는 않을 것 같은데 그래도 어쨋든 가장 안전한 방법을 택하고자 Hex로 인코딩했다. 데이터 크기 면에서는 비효율적이지만 원본 데이터 자체가 크지 않다.)
끝_!