티스토리 뷰
7~8 년쯤 반도체 부품 제조 업체 프로젝트를 했었다. 개발 중에 레거시 시스템과 보안에 민감한 데이터를 주고 받을 일이 있었다. 그래서 암호화가 필요했는데 당시 업체의 SM 전산실 현업쪽에서 암호화 처리를 위해 아래와 같은 샘플 코드를 받았다.
byte[] bytes = Base64.encodeBase64(inputString.getBytes());
...이하 생략
당시 라이브러리를 뭘 사용했는지 모르겠지만 Base64 인코딩 처리를 하는 코드였다. 그래서 "Base64 인코딩은 암호화 방법이 아니다." 라고 말했다가 오히려 성화를 입은 적이 있다. 요즘도 폐쇠적인 곳의 전산팀과 일을 하다 보면 암호나 기타 민감 정보를 주고 받을 때 Base64 인코딩을 암호화 방식이라면 사용하는 곳을 보게 된다. 이걸 어떻게 받아 들여야할지 난감하다. Base64 인코딩에 다른 이야기를 하면 언제가 기술 면접에서 이런 질문을 받은적 있었다.
"Base64 인코딩에 대해서 설명해 주세요? ", "Base64 인코딩은 왜 사용하나요?.."
암호화가 아니라면 잘난척 하던 나였지만 막상 면접에서 해당 질문을 받아보니 뭐 문자 64개 인코딩 어쩌구 저쩌구 횡설수설 했던 것 같다.앞으로는 그런 실수를 하지 않기 위해서 Base64 인코딩에 대해서 한번 정리를 해봐야겠다. (물론 또 잊어 버리겠지만.....)
Base64 인코딩이란??
결론부터 말하면 "이진(Binary)데이터를 6byte 씩 잘라서 64개의 문자로 맵핑 하는것" 이다.
컴퓨터는 모든 데이터를 0 또는 1 로 저장하게 된다. 그렇기 때문에 예를들어 "apple" 라는 단어도 결국에는 이진 데이터로 저장하게 된다. 아래 그림은 아스키코드표의 일부이다.(UTF-8, ISO-8859-1, EUC-KR 등도 일치한다.)
문자 | a | p | p | l | e |
십진수 | 97 | 112 | 112 | 108 | 101 |
이진수 | 01100001 | 01110000 | 01110000 | 01101100 | 01100101 |
Base64 인코딩하는 방법은 6bit 단위로 Base64 CharsetMap 과 문자열 맵핑을 한다. 아래 Base64 Charset Map 과 맵핑하는 과정의 예시이다.
이진 데이터를 일렬로 붙히고 아래와 같고 다시 6bit 로 나누고 해당 문자열을 Base64 코드에 맵핑 하는 과정을 거친다. 이 과정에서 남는 비트(2bit당)에 대해서는 "=" 문자열 만큼 패딩을 표시해준다.
이진수 | 0110000101110000011100000110110001100101 | |||||||
이진수(6bit) | 011000 | 010111 | 000001 | 110000 | 011011 | 000110 | 010100 | |
십진수 | 24 | 23 | 1 | 48 | 27 | 6 | 20 | |
문자 | Y | X | B | w | b | G | U | = |
아래는 Base64 코드표이다.
결국 위와 같은 과정을 거치어 apple -> YXBwbGU= 문자열로 변경된다. 이렇듯 원 문자열이 알아 보기 힘들게 난독화 됐지만 분명 암호화라 할 수 없는 것이다. 위에 인코딩 규칙만 알면 누구나 디코딩을 할 수 있기 때문이다. 또한 Base64 인코딩은 인코딩후 문자열의 길이가 늘어나는 특징이 있는데(위 예시 같은 경우 5자에서 -> 8자로) 이로인해 30% 이상의 오버헤드가 발생한다.
Base64 Encoding In Java
Base64 인코딩 처리는 대부분 언어에서 여러가지 방법으로 지원을 한다. 뭐 직접 구현을 할 수도 있다. 자바에서도 apache.common 패키지의 라이브러리를 사용할 수 도 있지만 자바8부터는 자바 표준 API 자체에서 지원을 한다. 위에서 사용 예제를 직접 자바 8의 표준 API 를 통해서 구현해 보겠다.
String text = "apple";
final byte[] asciiByte = text.getBytes(StandardCharsets.US_ASCII);
final byte[] base64Byte = Base64.getEncoder().encode(asciiByte);
for (byte b : asciiByte) { //origin
System.out.println((char) b + "--> (십진수:" + b + " 이진수:" + String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0')+")");
}
System.out.println("------------------------------------");
for (byte b : base64Byte) { //base65
System.out.println((char) b + "--> (십진수:" + b + " 이진수:" + String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0')+")");
}
아래 코드는 apple 이라는 단어의 byte[] 배열을 얻어 Base64 인코딩 된 byte[] 배열을 얻는 과정이다.
final byte[] asciiByte = text.getBytes(StandardCharsets.US_ASCII);
final byte[] base64Byte = Base64.getEncoder().encode(asciiByte);
아래 코드는 -128 ~ 127의 범위를 가지는 byte 를 Unsigned 한 0 ~ 255 형태로 변경한 이후 2진수 형태로 표시하기 위한 코드이다.
String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0')+")");
결과
Base64 인코딩은 왜 사용하는가?
위에서 언급했듯이 Base64 인코딩은 오버헤드가 발생한다. 그럼에도 불구하고 Base64 인코딩을 사용하는 이유는 무엇일까? XML 이나 HTML 같은 문자를 전송하는 MEDIA 타입에 우리는 멀티미디어 파일 (오디오, 이미지, 영상) 들을 같이 전송하고 싶을 때가 있다. 이 경우 해당 바이너리 데이터를 문자열로 인코딩이 필요한데 그때 사용할 수 있다. 또다른 예를들면 MIME(Multipurpose Internet Mail Extensions) 등 메일 표준 포맷에서는 7bit 형태의 아스키 문자로 전송한다. 그렇기 때문에 그 이상의 이진데이터는 문자열 인코딩이 필요한데 그때 여러 인코딩 방법중 base64가 사용된다.
그렇다면 꼭 Base64여만 하는 이유는 뭘까? 아스키코드에 포함된 126개의 문자와 제어문자 중 일부는 하드웨어나 운영체제에 따라 인코딩 형태가 다르거나 존재하지 않을 수도 있다. 하지만 영대,소문자 숫자 + / 는 같은 인코딩을 보장한다. 이러한 문자를 안전한 문자(secure string)라 한다. 이러한 안전한 문자만을 사용하는 Base64 인코딩은 데이터를 외부에 송 수신시에 Client 나 Server의 스펙에 의존없이 안전하게 처리할 수 있다.
'Web Development' 카테고리의 다른 글
STOMP(Simple/Stream Text Oriented Message Protocol) (0) | 2020.09.28 |
---|---|
Build Docker Image with Maven (java) (0) | 2020.06.25 |
Jenkins 계정 권한 관리 (Role-based Authorization Strategy Plugin) (0) | 2020.03.26 |
Shiro JdbcRealm(Postgresql) (0) | 2020.03.20 |
OSGI for Java Bundle Programing (0) | 2020.03.16 |
- Total
- Today
- Yesterday