Base64란 무엇이고 왜 필요한가요
Base64는 임의의 바이너리 데이터를 출력 가능한 ASCII 문자 64개, 즉 A–Z, a–z, 0–9, +, /와 패딩용 =만으로 쓰는 방법입니다. 인코딩이므로 누구나 키 없이 되돌릴 수 있으며 아무것도 숨기지 않습니다(자세한 내용은 인코딩과 암호화를 참고하세요).
목적은 바이트를 텍스트 전용 채널에 손상 없이 통과시키는 것입니다. PNG 파일에는 텍스트 채널이 지우거나, 바꾸거나, 제어 코드로 취급할 수 있는 0x00, 0x1A, 0x89 같은 바이트가 들어 있습니다. 같은 파일을 Base64로 쓰면 iVBORw0KGgo...가 되어 이메일, JSON, XML, URL(URL-safe 변형 사용 시), 터미널, 복사 붙여넣기를 그대로 통과합니다.
현재의 정의는 RFC 4648입니다. MIME 이메일(RFC 2045)과 PEM 인증서에 대한 예전 명세도 같은 알파벳을 설명하며, 줄 길이에 관한 규칙이 더해져 있습니다. 변형들의 요약은 Base64 표준 페이지에 있습니다.
이메일에 Base64가 필요했던 이유
초기 이메일은 7비트 ASCII 텍스트를 위해 설계되었습니다. 당시의 메일 중계 서버는 각 바이트의 최상위 비트를 지우거나, 긴 줄을 나누거나, 줄 끝 문자를 바꿀 수 있었습니다. 편지에는 해가 없지만 ZIP 파일에는 치명적입니다. MIME에는 어떤 첨부 파일이든 그 통로로 실어 보낼 방법, 그리고 도중의 모든 시스템에서 살아남는 알파벳이 필요했습니다.
Base64가 이 64개 문자를 고른 이유가 여기에 있습니다. 영문자와 숫자는 ASCII와 거의 모든 레거시 문자 집합(EUC-KR 같은 것도 포함)에서 똑같고, +와 /도 거의 그만큼 안전하며, 제어 문자나 공백은 하나도 없습니다. 64는 그 안전한 집합으로 채울 수 있는 가장 큰 2의 거듭제곱이기도 하고, 2의 거듭제곱이면 비트 연산이 단순해집니다. 대가는 크기입니다. 8비트 문자마다 쓸모 있는 비트가 6개이므로 출력은 입력의 4/3에 패딩을 더한 크기가 됩니다.
현대의 메일 서버에서 8비트 문제는 거의 사라졌지만, 바이너리 데이터가 텍스트 형식 안에 들어가야 하는 곳이면 어디서나 같은 제약이 나타납니다. JSON에는 바이너리 타입이 없고, HTML 속성, CSS, HTTP 헤더는 모두 텍스트입니다. 그래서 여전히 Base64가 그 안에 바이트를 넣는 기본 방법입니다.
알고리즘: 3바이트가 들어가면 4글자가 나옵니다
Base64는 24비트 블록 단위로 동작합니다. 24는 8(바이트 하나)과 6(Base64 문자 하나)으로 모두 나누어떨어지는 가장 작은 수이므로, 3바이트가 정확히 4글자에 대응합니다.
블록마다 다음을 합니다.
- 3바이트를 가져와 24비트를 한 줄로 씁니다.
- 24비트를 6비트씩 네 묶음으로 자릅니다.
- 각 묶음을 0부터 63까지의 수로 읽습니다.
- 그 수를 알파벳에서 찾아 해당 문자를 출력합니다.
알파벳은 고정된 표입니다. 인덱스 0은 A, 25는 Z, 26은 a, 51은 z, 52는 0, 61은 9, 62는 +, 63은 /입니다. 전체 표는 Base64 알파벳 페이지에 있습니다.
| 범위 | 문자 | 예시 |
|---|---|---|
| 0–25 | A–Z | 19 → T |
| 26–51 | a–z | 46 → u |
| 52–61 | 0–9 | 52 → 0 |
| 62 | + | |
| 63 | / |
디코딩은 같은 단계를 거꾸로 밟습니다. 각 문자를 찾아 6비트를 얻고, 네 개를 이어 24비트로 만든 뒤 3바이트로 나눕니다.
예제: “Man"은 TWFu가 됩니다
고전적인 예제는 ASCII 텍스트 Man입니다. ASCII에서 M은 77, a는 97, n은 110입니다.
| M | a | n | |
|---|---|---|---|
| 10진수 | 77 | 97 | 110 |
| 2진수 | 01001101 | 01100001 | 01101110 |
비트를 이으면 010011010110000101101110입니다. 이제 8비트가 아니라 6비트마다 자릅니다.
| 6비트 묶음 | 010011 | 010110 | 000101 | 101110 |
|---|---|---|---|---|
| 값 | 19 | 22 | 5 | 46 |
| 문자 | T | W | F | u |
따라서 Man은 TWFu로 인코딩됩니다. 출력 문자의 경계가 입력 글자의 경계와 맞지 않는다는 점에 주목하세요. W는 M의 마지막 4비트와 a의 처음 2비트로 만들어집니다. 그래서 입력 바이트 하나를 바꾸면 출력 문자 두 개가 바뀌고, 입력이 평범한 영어라도 Base64를 눈으로 읽을 수 없습니다.
어떤 셸에서든 확인할 수 있습니다.
echo -n Man | base64
# TWFu
-n이 없으면 echo가 줄 바꿈 바이트를 더하고, Man\n은 TWFuCg==로 인코딩됩니다.
입력이 3의 배수가 아닐 때: 패딩
실제 데이터의 길이가 3으로 나누어떨어지는 경우는 드뭅니다. 그러면 마지막 블록에 1바이트나 2바이트가 남고, Base64는 0 비트와 = 기호로 이를 채웁니다.
2바이트가 남는 경우, “Ma” → TWE=. 2바이트는 16비트입니다. 0 비트 두 개를 더해 18비트, 즉 6비트 묶음 세 개를 만듭니다.
| 6비트 묶음 | 010011 | 010110 | 000100 | (없음) |
|---|---|---|---|---|
| 값 | 19 | 22 | 4 | |
| 문자 | T | W | E | = |
세 번째 묶음은 a의 0001 뒤에 더한 0 두 개가 붙은 것으로, 4, 즉 E가 됩니다. = 하나가 빠진 네 번째 문자를 대신합니다.
1바이트가 남는 경우, “M” → TQ==. 1바이트는 8비트입니다. 0 비트 네 개를 더해 12비트, 즉 묶음 두 개를 만듭니다: 010011(19, T)과 010000(16, Q). = 두 개가 블록을 채웁니다.
따라서 =의 개수는 마지막 블록에 몇 바이트가 있었는지 알려 줍니다. 없으면 3, 하나면 2, 둘이면 1입니다. 남는 바이트가 0인 블록은 존재하지 않으므로 세 번째 =는 절대 없습니다. 언제 =를 생략할 수 있는지, 어떻게 되돌리는지는 패딩 가이드에서 다룹니다.
import base64
for s in [b"Man", b"Ma", b"M"]:
print(s, base64.b64encode(s))
# b'Man' b'TWFu'
# b'Ma' b'TWE='
# b'M' b'TQ=='
텍스트는 먼저 바이트가 되어야 합니다
Base64는 바이트를 인코딩합니다. 텍스트는 문자 인코딩을 골라야 비로소 바이트가 되고, 그 선택에 따라 결과가 달라집니다. 단어 café는 UTF-8로 5바이트(é가 두 바이트)이므로 Y2Fmw6k=로 인코딩됩니다. Latin-1로는 4바이트이고 Y2Fm6Q==로 인코딩됩니다. 한글도 마찬가지여서, UTF-8과 EUC-KR은 같은 글자를 다른 바이트로 만들고 디코딩할 때 다른 인코딩을 고르면 글자가 깨집니다.
이것이 잘 알려진 JavaScript 함정의 원인입니다. 브라우저 함수 btoa()는 문자열의 각 문자를 1바이트로 취급하는데, 이는 Latin-1 동작입니다.
btoa("café"); // "Y2Fm6Q==" (Latin-1, not UTF-8)
btoa("café ✓"); // throws InvalidCharacterError
JavaScript에서 UTF-8 Base64를 얻으려면 먼저 TextEncoder로 바이트로 인코딩하거나, Node.js에서는 Buffer를 쓰세요. JavaScript 코드 페이지에 동작하는 패턴이 있고, 새 내장 메서드는 Uint8Array.toBase64()에서 다룹니다. 이 사이트의 Base64 인코더는 기본적으로 UTF-8을 쓰며 다른 문자 인코딩을 고를 수도 있습니다.
Base64를 만나는 곳
- 이메일 첨부 파일. 원본
.eml파일의 모든 첨부 파일은 76자로 줄을 나눈 Base64 블록입니다. 이메일 속 Base64를 참고하세요. - Data URI.
data:image/png;base64,iVBORw0KGgo...는 이미지를 HTML이나 CSS에 직접 넣습니다. 그렇게 하는 것이 좋은지는 data URI 성능에서 다룹니다. - HTTP Basic Auth. 헤더
Authorization: Basic dXNlcjpwYXNz는 그냥user:pass의 Base64입니다. - JWT. JSON Web Token의 점으로 나뉜 세 부분은 Base64URL입니다. JWT 속 Base64를 참고하세요.
- PEM 파일.
-----BEGIN ...-----줄 사이의 인증서와 키는 바이너리 DER 데이터의 Base64입니다. - API와 설정. Kubernetes Secret, 클라우드 메타데이터, 웹훅 페이로드, 많은 JSON API가 파일이나 바이너리 데이터를 Base64 문자열로 실어 나릅니다.
조금 지나면 첫 문자만 보고도 알아보게 됩니다. iVBORw0KGgo는 PNG, JVBERi0는 PDF, /9j/는 JPEG, eyJ는 {"로 시작하는 JSON입니다. 더 많은 예는 자주 보는 Base64 접두사 페이지에 있습니다. 데이터에 무엇이 들었는지 보려면 Base64를 파일로 도구가 형식을 감지하고 브라우저에서 미리 보여 줍니다.
Base64를 붙여넣으면 안에 든 텍스트나 바이트를 볼 수 있습니다. 브라우저에서 로컬로 실행됩니다.
자주 묻는 질문
Base64는 압축의 한 종류인가요?
아니요. 언제나 데이터를 크게 만듭니다. 입력 3바이트가 출력 4글자가 되므로 약 33% 늘어납니다. 정확한 공식은 Base64 크기 오버헤드를 참고하세요.
왜 Base64라고 부르나요?
출력 문자 하나가 64가지 값 중 하나, 즉 6비트의 정보를 담기 때문입니다. hex가 16진법이듯 이름은 알파벳의 크기를 뜻합니다.
어떤 데이터든 Base64로 인코딩할 수 있나요?
네. 알고리즘은 바이트에 대해 동작하며 그 의미에는 관심이 없으므로, 텍스트, 이미지, PDF, 키, 압축 파일 모두 같은 방식으로 인코딩됩니다. 텍스트는 먼저 문자 인코딩(보통 UTF-8)으로 바이트로 바꿔야 합니다.
Base64 출력에 공백이나 줄 바꿈이 들어가기도 하나요?
순수한 RFC 4648 Base64에는 없습니다. MIME 이메일은 76자, PEM 파일은 64자처럼 일부 형식은 줄을 나누며, 대부분의 디코더는 그 공백을 건너뜁니다.