들어가며
Base64 인코딩과 디코딩에 대한 이해 부족으로 인해 문제를 해결하는 과정에서 어려움을 겪었던 경험을 바탕으로 이 글을 작성하게 되었습니다.
Base64에 대해 저와 비슷한 고민을 하신 분들, 혹은 개념은 알고 있지만 막연하게 이해하고 계신 분들에게 도움이 되고자 글을 작성했습니다.
이번 글에서는 Base64가 무엇인지, 왜 사용하는지, 어떤 특징이 있는지를 살펴보고, 그와 관련된 몇 가지 오해와 사실에 대해 이야기해 보겠습니다.
Parameter 를 빼앗겼습니다.
기프티콘 API 발송 작업 진행 중 기프티콘 쿠폰 코드가 화면에서 넘어오지 않는 현상이 발생했습니다.
개발자 도구로 Network를 확인 한 결과 클라이언트 에서는 정상적으로 전달 한 것으로 나오는 상황이였습니다.
Payload : 전 분명 전달했습니다.

근데 빼앗겼습니다(?)
어디서 사라진거죠?
로컬에서 디버깅 해 본 결과 특정 모바일 쿠폰 코드가 Controller 쪽으로 넘어오지 않는 것을 확인 할 수 있었습니다.
어디서 사라진걸까요?
Controller 에 값이 넘어오지 않는다면 Spring Framework 를 쓰는 사용자 입장에서는
대표적으로 두가지를 먼저 확인하게 됩니다.
- Dispatcher Servlet 의 prehandler
- Filter Chain 의 Custom Filter
확인 결과 Custom Filter 에서문제가 발생했습니다.
- Custom Filter를 하나 생성하고 해당 Filter 에서
- HttpServletRequestWrapper 를 상속받은 Custom Request Wrapper 를 구현,
- 구현체에서 Paramter 를 가져오는 함수들을 overriding 하고
- 다음 Filter 를 호출 할 때 넘겨주는 request 를 Custom Request Wrapper 로 넘겨준 것
어떻게 사라진거죠?
overriding한 메서드에서 파라미터를 가져올 때 모든 파리미터 항목에 대해서
Base64 디코딩 후 복호화를 진행하는데
특이한 점은 디코딩 -> 복호화 를 진행하는 코드가 try 문으로 감싸져있었고 실패하는 순간
logging 만 처리하고 파라미터 값을 그대로 쓰던 상황이였습니다.
보안 상 회사 코드의 자세한 내부 로직이나 코드를 게시할 수는 없지만,,
결과적으로 base64 인코딩 하지 않은 내용을 디코딩한 후 복호화 하려고 했고
디코딩 된 바이트 배열을 복사하는 과정에서 특정 자릿수의 값이 문제가 생기는 상황이였습니다.
여기서 특정 자릿수에 문제가 있다는 것을 파악하기 위해 base64의 인코딩/디코딩 구조에 대해서 학습 할 필요가 생겼습니다.
base64.. 그냥 64진법인줄...
예전에 단축 URL 토이 프로젝트를 진행한적이 있었는데 해당 프로젝트에서 base64 인코딩 기법을 활용하여 단축 URL을 생성했었습니다.
그때 간단하게 DB에 auto increment 되는 ID 값을 두고 해당 ID 값을 base64로 인코딩하여 단축된 URL 을 사용했었죠
그렇다보니 저에게 있어 base64는 그냥 64진법이구나 긴 값을 짧게 줄여주는구나 정도로 인식을 했습니다.
그런데 base64의 실체
그런데 base64는 실제로는 문자를 각 8비트 단위(바이트) 배열로 만들고 그 비트들을 이어붙인 후 6비트 단위로 쪼개서 6비트 단위로 재구성하여 해당 비트별 base64의 문자에 대응하는 구조였습니다.
이해를 돕기위해 예시를 들어보겠습니다. LDK 라는 문자열에 대해서 각 문자를 바이트 배열로 생성합니다.
이 때, ASCII 코드표를 기반으로 변환됩니다.

L D K
- L : 76 -> 0100 1100
- D : 68 -> 0100 0100
- K : 75 -> 0100 1011
이렇게 변환 된 값을 이어붙입니다
0100 1100 0100 0100 0100 1011
이렇게 붙인 값에서 6자리씩 구분을 짓습니다.
0100 11/00 0100/ 0100 01/00 1011/
이렇게 구분된 6자리의 비트로 base64 인코딩을 진행합니다.

19 / 4 / 17 / 11 값은
결국 T E R L 로 치환됩니다.
따라서, LDK 를 base64로 인코딩하면 TERL 이라는 결과가 나오게 됩니다.
https://www.base64encode.org/
Base64 Encode and Decode - Online
Encode to Base64 format or decode from it with various advanced options. Our site has an easy to use online tool to convert your data.
www.base64encode.org
온라인 base64 사이트에서 간단하게 검증 할 수 있습니다.
첫번째 의문, 항상 6자리로 나눠지나요?
첫번째 변환을 보고나면 저와 같은 의문을 가지는 분이 있으실겁니다.
결론부터 말하자면 항상 6으로 나눠지지 않습니다. 당연한 결과죠 문자열 하나당 8비트씩 차지하는데 이게 6비트와 정확히 일치하려면
원본 문자열이 3의배수여야만 합니다.(최소공배수 24)
base64 기법에서는 이와같은 상황을 해결하기 위해 padding 기법을 사용합니다. 의미를 지니지 않는 약속된 문자("=")를 추가해서 = 문자 하나당 2비트씩 추가된 비트가 있다고 명시하는 것이죠.
이전 예시에서 마지막 문자에 L을 추가해서 LDKL 을 인코딩 한다고 해봅시다.
L D K
- L : 76 -> 0100 1100
- D : 68 -> 0100 0100
- K : 75 -> 0100 1011
- L : 76 -> 0100 1100
0100 11/00 0100/ 0100 01/00 1011/ 0100 11/00 + 0000
19 / 4 / 17 / 11 / 19 / 0 값은
T E R L T A 로 치환되고 추가로 붙은 4비트를 명시하기 위해 =을 두개 붙여
결과적으로 TERLTA== 으로 생성이 됩니다.
근데.. 패딩이 꼭 필요한가요?
그런데, 이 패딩이 왜 필요한가에 대한 의문이 들었습니다.
왜냐하면 실제로 패딩이 없는 데이터를 가지고도 충분히 디코딩을 할 수 있기 때문입니다.
각 문자열을 비트로 변환하고 8비트로 구분 했을 때 남는 값을 그냥 버리면 되는거 아닌가 라는 생각이 들었습니다.
이에 대한 답변은 "이러한 약속을 base64 인코딩의 표준으로 정했고, 좀 더 효율적인 계산을 위함"이라는 것이였습니다.
실제로 java 라이브러리의 Base64 클래스를 살펴보면

padding 값인 "=" 이 있다면 명시적으로 추가하고 없다면 직접 padding 값을 계산합니다.
두번째 의문, 인코딩 하면 데이터가 늘어나는거 아닌가요?
맞습니다. 그도 그런것이 한 문자당 8비트 길이를 6비트로 구분한다는 얘기는 당연히 결과 문자열이 늘어나는 것을 의미합니다.
일반적으로는 전송되는 데이터를 어떻게든 줄이기 위해 용을 쓰는데 base64는 그렇지 않습니다.
제가 오해했던 부분도 이 내용과 관련있습니다.
단축 URL에서 사용한 인코딩은 정수값 자체를 base64 인코딩 하면서 값이 짧아지는 효과를 가져왔었기 때문입니다.
왜 데이터를 늘리면서까지 base64 인코딩을 할까요?
여기서 base64를 쓰는 근본적인 이유가 밝혀집니다.
결과적으로 base64는 데이터의 안전한 전송과 저장을 목적으로 사용됩니다.
가장 주로 사용되는 목적은
바이너리 데이터를 텍스트로 표현하기 위함입니다.
다양한 시스템과 프로토콜(HTTP 통신, 이메일, HTML)들은 텍스트 데이터만 처리할 수 있는 경우가 많습니다.
이미지 같은 데이터를 안전하고 효율적으로 보내기 위해서 이렇게 base64로 문자열로 전부 치환하여 보내는 것입니다.
특수문자 같은 데이터들을 그대로 사용할 수 없는 URL 생성에서도 많이 사용됩니다.(단축URL과 다른 개념)
마치며...
이번 글에서는 사라진 파라미터를 찾기 위한 험난한 여정과 그 과정에서 저를 혼란스럽게 했던 base64에 대해 다루어 보았습니다.
평소 base64로 인코딩된 URL을 자주 사용하면서도 왜 사용하는지, 마지막에 붙는 패딩 문자열에 대해 깊이 고민해본 적이 없었지만, 이번 기회를 통해 그 의미와 필요성을 이해하게 되었습니다.
덕분에 앞으로 관련 장애나 오류가 발생했을 때 더욱 빠르게 문제를 해결할 수 있는 시야를 가지게 되었습니다.
긴 글 읽어주셔서 감사드리며, 이 글이 비슷한 상황에 있는 분들에게 조금이나마 도움이 되기를 바랍니다.
.
.
.
추가로 알아보면 좋은 자료들
- Base64 외에도 바이너리 데이터를 텍스트로 변환하는 다른 방법들
- Hexadecimal(Base16) 인코딩이나 Base85 인코딩
- JWT(JSON Web Token)에서의 Base64의 역할
- Base64 class 에도 나와있던MIME(Multipurpose Internet Mail Extensions)
'Java' 카테고리의 다른 글
[GC] 나야 메모리, 근데 이제 누수를 곁들인 (2) | 2024.10.22 |
---|---|
[Transactional] 거래가 왜 없었을까요? (0) | 2024.08.18 |
[Transactional] 거래가 있었는데요.. 없었습니다 (2) | 2024.08.17 |
[Java의 동시성] - Java에서 동시성 문제를 해결하는 방법 (1) | 2023.05.07 |
[동시성 문제] - 동시성 문제란 무엇이며 어떻게 해결해야할까? (0) | 2023.05.01 |
들어가며
Base64 인코딩과 디코딩에 대한 이해 부족으로 인해 문제를 해결하는 과정에서 어려움을 겪었던 경험을 바탕으로 이 글을 작성하게 되었습니다.
Base64에 대해 저와 비슷한 고민을 하신 분들, 혹은 개념은 알고 있지만 막연하게 이해하고 계신 분들에게 도움이 되고자 글을 작성했습니다.
이번 글에서는 Base64가 무엇인지, 왜 사용하는지, 어떤 특징이 있는지를 살펴보고, 그와 관련된 몇 가지 오해와 사실에 대해 이야기해 보겠습니다.
Parameter 를 빼앗겼습니다.
기프티콘 API 발송 작업 진행 중 기프티콘 쿠폰 코드가 화면에서 넘어오지 않는 현상이 발생했습니다.
개발자 도구로 Network를 확인 한 결과 클라이언트 에서는 정상적으로 전달 한 것으로 나오는 상황이였습니다.
Payload : 전 분명 전달했습니다.

근데 빼앗겼습니다(?)
어디서 사라진거죠?
로컬에서 디버깅 해 본 결과 특정 모바일 쿠폰 코드가 Controller 쪽으로 넘어오지 않는 것을 확인 할 수 있었습니다.
어디서 사라진걸까요?
Controller 에 값이 넘어오지 않는다면 Spring Framework 를 쓰는 사용자 입장에서는
대표적으로 두가지를 먼저 확인하게 됩니다.
- Dispatcher Servlet 의 prehandler
- Filter Chain 의 Custom Filter
확인 결과 Custom Filter 에서문제가 발생했습니다.
- Custom Filter를 하나 생성하고 해당 Filter 에서
- HttpServletRequestWrapper 를 상속받은 Custom Request Wrapper 를 구현,
- 구현체에서 Paramter 를 가져오는 함수들을 overriding 하고
- 다음 Filter 를 호출 할 때 넘겨주는 request 를 Custom Request Wrapper 로 넘겨준 것
어떻게 사라진거죠?
overriding한 메서드에서 파라미터를 가져올 때 모든 파리미터 항목에 대해서
Base64 디코딩 후 복호화를 진행하는데
특이한 점은 디코딩 -> 복호화 를 진행하는 코드가 try 문으로 감싸져있었고 실패하는 순간
logging 만 처리하고 파라미터 값을 그대로 쓰던 상황이였습니다.
보안 상 회사 코드의 자세한 내부 로직이나 코드를 게시할 수는 없지만,,
결과적으로 base64 인코딩 하지 않은 내용을 디코딩한 후 복호화 하려고 했고
디코딩 된 바이트 배열을 복사하는 과정에서 특정 자릿수의 값이 문제가 생기는 상황이였습니다.
여기서 특정 자릿수에 문제가 있다는 것을 파악하기 위해 base64의 인코딩/디코딩 구조에 대해서 학습 할 필요가 생겼습니다.
base64.. 그냥 64진법인줄...
예전에 단축 URL 토이 프로젝트를 진행한적이 있었는데 해당 프로젝트에서 base64 인코딩 기법을 활용하여 단축 URL을 생성했었습니다.
그때 간단하게 DB에 auto increment 되는 ID 값을 두고 해당 ID 값을 base64로 인코딩하여 단축된 URL 을 사용했었죠
그렇다보니 저에게 있어 base64는 그냥 64진법이구나 긴 값을 짧게 줄여주는구나 정도로 인식을 했습니다.
그런데 base64의 실체
그런데 base64는 실제로는 문자를 각 8비트 단위(바이트) 배열로 만들고 그 비트들을 이어붙인 후 6비트 단위로 쪼개서 6비트 단위로 재구성하여 해당 비트별 base64의 문자에 대응하는 구조였습니다.
이해를 돕기위해 예시를 들어보겠습니다. LDK 라는 문자열에 대해서 각 문자를 바이트 배열로 생성합니다.
이 때, ASCII 코드표를 기반으로 변환됩니다.

L D K
- L : 76 -> 0100 1100
- D : 68 -> 0100 0100
- K : 75 -> 0100 1011
이렇게 변환 된 값을 이어붙입니다
0100 1100 0100 0100 0100 1011
이렇게 붙인 값에서 6자리씩 구분을 짓습니다.
0100 11/00 0100/ 0100 01/00 1011/
이렇게 구분된 6자리의 비트로 base64 인코딩을 진행합니다.

19 / 4 / 17 / 11 값은
결국 T E R L 로 치환됩니다.
따라서, LDK 를 base64로 인코딩하면 TERL 이라는 결과가 나오게 됩니다.
https://www.base64encode.org/
Base64 Encode and Decode - Online
Encode to Base64 format or decode from it with various advanced options. Our site has an easy to use online tool to convert your data.
www.base64encode.org
온라인 base64 사이트에서 간단하게 검증 할 수 있습니다.
첫번째 의문, 항상 6자리로 나눠지나요?
첫번째 변환을 보고나면 저와 같은 의문을 가지는 분이 있으실겁니다.
결론부터 말하자면 항상 6으로 나눠지지 않습니다. 당연한 결과죠 문자열 하나당 8비트씩 차지하는데 이게 6비트와 정확히 일치하려면
원본 문자열이 3의배수여야만 합니다.(최소공배수 24)
base64 기법에서는 이와같은 상황을 해결하기 위해 padding 기법을 사용합니다. 의미를 지니지 않는 약속된 문자("=")를 추가해서 = 문자 하나당 2비트씩 추가된 비트가 있다고 명시하는 것이죠.
이전 예시에서 마지막 문자에 L을 추가해서 LDKL 을 인코딩 한다고 해봅시다.
L D K
- L : 76 -> 0100 1100
- D : 68 -> 0100 0100
- K : 75 -> 0100 1011
- L : 76 -> 0100 1100
0100 11/00 0100/ 0100 01/00 1011/ 0100 11/00 + 0000
19 / 4 / 17 / 11 / 19 / 0 값은
T E R L T A 로 치환되고 추가로 붙은 4비트를 명시하기 위해 =을 두개 붙여
결과적으로 TERLTA== 으로 생성이 됩니다.
근데.. 패딩이 꼭 필요한가요?
그런데, 이 패딩이 왜 필요한가에 대한 의문이 들었습니다.
왜냐하면 실제로 패딩이 없는 데이터를 가지고도 충분히 디코딩을 할 수 있기 때문입니다.
각 문자열을 비트로 변환하고 8비트로 구분 했을 때 남는 값을 그냥 버리면 되는거 아닌가 라는 생각이 들었습니다.
이에 대한 답변은 "이러한 약속을 base64 인코딩의 표준으로 정했고, 좀 더 효율적인 계산을 위함"이라는 것이였습니다.
실제로 java 라이브러리의 Base64 클래스를 살펴보면

padding 값인 "=" 이 있다면 명시적으로 추가하고 없다면 직접 padding 값을 계산합니다.
두번째 의문, 인코딩 하면 데이터가 늘어나는거 아닌가요?
맞습니다. 그도 그런것이 한 문자당 8비트 길이를 6비트로 구분한다는 얘기는 당연히 결과 문자열이 늘어나는 것을 의미합니다.
일반적으로는 전송되는 데이터를 어떻게든 줄이기 위해 용을 쓰는데 base64는 그렇지 않습니다.
제가 오해했던 부분도 이 내용과 관련있습니다.
단축 URL에서 사용한 인코딩은 정수값 자체를 base64 인코딩 하면서 값이 짧아지는 효과를 가져왔었기 때문입니다.
왜 데이터를 늘리면서까지 base64 인코딩을 할까요?
여기서 base64를 쓰는 근본적인 이유가 밝혀집니다.
결과적으로 base64는 데이터의 안전한 전송과 저장을 목적으로 사용됩니다.
가장 주로 사용되는 목적은
바이너리 데이터를 텍스트로 표현하기 위함입니다.
다양한 시스템과 프로토콜(HTTP 통신, 이메일, HTML)들은 텍스트 데이터만 처리할 수 있는 경우가 많습니다.
이미지 같은 데이터를 안전하고 효율적으로 보내기 위해서 이렇게 base64로 문자열로 전부 치환하여 보내는 것입니다.
특수문자 같은 데이터들을 그대로 사용할 수 없는 URL 생성에서도 많이 사용됩니다.(단축URL과 다른 개념)
마치며...
이번 글에서는 사라진 파라미터를 찾기 위한 험난한 여정과 그 과정에서 저를 혼란스럽게 했던 base64에 대해 다루어 보았습니다.
평소 base64로 인코딩된 URL을 자주 사용하면서도 왜 사용하는지, 마지막에 붙는 패딩 문자열에 대해 깊이 고민해본 적이 없었지만, 이번 기회를 통해 그 의미와 필요성을 이해하게 되었습니다.
덕분에 앞으로 관련 장애나 오류가 발생했을 때 더욱 빠르게 문제를 해결할 수 있는 시야를 가지게 되었습니다.
긴 글 읽어주셔서 감사드리며, 이 글이 비슷한 상황에 있는 분들에게 조금이나마 도움이 되기를 바랍니다.
.
.
.
추가로 알아보면 좋은 자료들
- Base64 외에도 바이너리 데이터를 텍스트로 변환하는 다른 방법들
- Hexadecimal(Base16) 인코딩이나 Base85 인코딩
- JWT(JSON Web Token)에서의 Base64의 역할
- Base64 class 에도 나와있던MIME(Multipurpose Internet Mail Extensions)
'Java' 카테고리의 다른 글
[GC] 나야 메모리, 근데 이제 누수를 곁들인 (2) | 2024.10.22 |
---|---|
[Transactional] 거래가 왜 없었을까요? (0) | 2024.08.18 |
[Transactional] 거래가 있었는데요.. 없었습니다 (2) | 2024.08.17 |
[Java의 동시성] - Java에서 동시성 문제를 해결하는 방법 (1) | 2023.05.07 |
[동시성 문제] - 동시성 문제란 무엇이며 어떻게 해결해야할까? (0) | 2023.05.01 |