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에 정해져 있습니다.

  1. 입력을 3바이트(24비트)씩 묶습니다.
  2. 24비트를 6비트씩 4조각으로 나눕니다. 6비트는 0~63의 값입니다.
  3. 각 값을 표의 글자로 바꿉니다.
값 글자
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)

블로그로