Base64는 무엇이고 왜 쓸까: 인코딩 원리와 흔한 오해
Base64가 3바이트를 4글자로 바꾸는 방식, 결과가 약 33% 커지는 이유, 끝의 = 의미, URL-safe Base64와 한글·일본어를 다룰 때의 주의점을 RFC 4648을 기준으로 정리했습니다.
이메일 첨부 파일, 웹 페이지에 박아 넣은 data:image/png;base64,iVBOR… 이미지, 로그인 토큰(JWT)의 가운데 부분. 모두 Base64로 쓰인 데이터입니다. 알파벳과 숫자가 길게 이어진 이 문자열은 암호가 아니라 바이너리 데이터를 글자로 옮겨 적는 방법입니다.
왜 필요한가
이메일(SMTP)이나 JSON, HTML, URL은 원래 글자를 주고받도록 만들어졌습니다. 이미지나 압축 파일 같은 바이너리 데이터에는 줄바꿈이나 제어 문자, 따옴표로 해석될 수 있는 바이트가 섞여 있어서, 그대로 넣으면 중간에 깨지거나 문법이 망가집니다. Base64는 어떤 바이트든 어디서나 안전한 64개의 글자만으로 바꿔서 이 문제를 피합니다.
3바이트를 4글자로
규칙은 RFC 4648에 정해져 있습니다.
- 입력을 3바이트(24비트)씩 묶습니다.
- 24비트를 6비트씩 4조각으로 나눕니다. 6비트는 0~63의 값입니다.
- 각 값을 표의 글자로 바꿉니다.
| 값 | 글자 |
|---|---|
| 0–25 | A–Z |
| 26–51 | a–z |
| 52–61 | 0–9 |
| 62 | + |
| 63 | / |
“Man”을 예로 들면 다음과 같습니다.
M a n
01001101 01100001 01101110 ← 3바이트
010011 010110 000101 101110 ← 6비트씩 4조각
19 22 5 46
T W F u → "TWFu"
끝의 =는 무엇인가
입력이 3바이트로 딱 나누어떨어지지 않으면 마지막 묶음이 모자랍니다. 모자란 비트는 0으로 채우고, 만들어지지 않은 글자 자리는 =로 채워 결과 길이를 항상 4의 배수로 맞춥니다.
| 입력 | 결과 |
|---|---|
Man (3바이트) |
TWFu |
Ma (2바이트) |
TWE= |
M (1바이트) |
TQ== |
그래서 =는 끝에 최대 두 개까지만 붙습니다. 디코더는 이를 보고 원래 데이터가 몇 바이트였는지 압니다.
크기는 약 33% 커진다
3바이트가 4글자가 되므로 결과는 원본의 4/3배입니다. n바이트를 인코딩하면 4 × ⌈n ÷ 3⌉글자가 됩니다. 300KB 이미지를 Base64로 박아 넣으면 약 400KB가 됩니다. 게다가 이메일(MIME, RFC 2045)에서는 한 줄을 76자 이하로 끊기 때문에 줄바꿈만큼 더 늘어납니다.
작은 아이콘을 data: URI로 넣어 요청 수를 줄이는 것은 괜찮지만, 큰 이미지는 별도 파일로 두는 편이 캐시도 되고 전송량도 적습니다.
Base64는 암호화가 아니다
가장 흔한 오해입니다. Base64에는 키가 없습니다. 누구나 같은 표로 되돌릴 수 있습니다. 비밀번호나 개인 정보를 Base64로 “숨기는” 것은 아무것도 보호하지 않습니다. JWT의 가운데 부분(페이로드)도 Base64URL로 인코딩되어 있을 뿐이라 누구나 읽을 수 있습니다. JWT가 보장하는 것은 서명을 통한 위조 방지이지 내용의 비밀이 아닙니다.
URL-safe Base64 (Base64URL)
표준 Base64의 +와 /는 URL에서 특별한 의미를 가집니다(/는 경로 구분, +는 폼 데이터에서 공백). 그래서 RFC 4648은 두 글자만 바꾼 Base64URL을 따로 정합니다.
| 값 | 표준 Base64 | Base64URL |
|---|---|---|
| 62 | + |
- |
| 63 | / |
_ |
Base64URL은 끝의 = 패딩도 생략하는 경우가 많습니다. JWT(RFC 7515)가 대표적입니다. 디코딩하다 “잘못된 문자” 오류가 나면, 받은 값이 어느 쪽 형식인지 먼저 확인합니다.
한글·일본어를 Base64로 바꿀 때
Base64는 바이트를 다룹니다. 글자를 넣으려면 먼저 바이트로 바꿔야 하고, 보통 UTF-8을 씁니다. “한”은 UTF-8로 ED 95 9C 3바이트이고, Base64로는 7ZWc가 됩니다.
JavaScript의 btoa()는 한 글자를 한 바이트로 취급하므로, U+00FF보다 큰 글자(한글, 일본어, 이모지)를 넣으면 InvalidCharacterError가 납니다. 먼저 UTF-8 바이트로 바꾼 뒤 인코딩합니다.
const bytes = new TextEncoder().encode("한글");
const b64 = btoa(String.fromCharCode(...bytes)); // "7ZWc6riA"
디코딩할 때는 반대로 바이트를 UTF-8로 해석해야 합니다. 다른 인코딩(예: EUC-KR, Shift_JIS)으로 인코딩된 Base64를 UTF-8로 풀면 글자가 깨집니다.
직접 해 보기
Base64 인코딩·디코딩 도구는 한글·일본어·이모지를 UTF-8로 처리하고, URL-safe 형식도 지원합니다. 입력은 브라우저 안에서만 변환되고 서버로 보내지 않습니다. “Man”, “Ma”, “M”을 차례로 넣어 보면 =가 붙는 규칙을 바로 확인할 수 있습니다.
참고
- RFC 4648, The Base16, Base32, and Base64 Data Encodings
- RFC 2045, MIME Part One (6.8 Base64 Content-Transfer-Encoding)
- RFC 7515, JSON Web Signature (JWS)