Base64 는 바이너리 데이터를 64 글자의 ASCII 텍스트로 표현하는 인코딩 방식이다. 이메일 첨부, JWT 의 헤더·페이로드, 데이터 URI, 환경 변수에 섞인 인증서 등 "바이너리를 텍스트만 허용하는 곳에 끼워 넣어야 할 때" 에 거의 모든 곳에서 쓰인다. 이름이 "암호화" 처럼 느껴져 오해가 잦은데, Base64 는 전혀 보안 기능이 아니다. 이 가이드는 원리, 변형, 오버헤드, 자주 빠지는 함정을 정리한다.
왜 인코딩이 필요한가
많은 프로토콜과 포맷이 텍스트만 안전하게 다룬다. SMTP 는 7-bit ASCII 만 보장하던 시절의 유산이 남아 있고, JSON 은 임의 바이너리를 그대로 담지 못한다. 이미지나 키 같은 바이너리를 이런 채널에 실으려면 텍스트로 바꿔야 한다.
Base64 는 RFC 4648 이 정한 표준이다. 8 비트 바이트 3 개(24 비트)를 6 비트 단위로 자르고, 각 6 비트를 64 글자 알파벳에 매핑한다 — 그래서 이름이 Base64. 입력 3 바이트가 출력 4 글자가 되므로 크기는 약 4/3 배 (33% 증가) 한다.
알파벳과 패딩
표준 알파벳은 다음 64 글자다.
A-Z a-z 0-9 + /입력 길이가 3 의 배수가 아니면 출력 끝에 = 패딩이 붙어 4 글자 단위를 맞춘다.
| 입력 바이트 수 | 출력 글자 수 | 패딩 |
|---|---|---|
| 3 | 4 | 없음 |
| 2 | 4 | = 하나 |
| 1 | 4 | == 두 개 |
예를 들어 ASCII "Hi" (2 바이트) → "SGk=". Base64 인코딩 / 디코딩 에서 직접 입력해 보면 출력이 그렇게 나온다.
URL-safe 변형 (Base64URL)
표준 알파벳의 + 와 / 는 URL 에서 다른 의미가 있다 (+ 는 공백, / 는 경로 구분자). 그래서 RFC 4648 §5 가 URL-safe 변형을 정의했다.
+→-/→_- 패딩
=은 흔히 생략 (URL 에 그대로 박을 수 있도록)
JWT 의 세 조각이 정확히 이 변형이다 — + 나 / 가 나오면 표준 Base64. - 나 _, 또는 = 없는 4 의 배수가 아닌 길이가 나오면 Base64URL. Base64 인코딩 / 디코딩 는 두 변형을 동시에 받아 자동 감지한다.
크기 오버헤드 — 실제 영향
4/3 배 증가는 작은 수처럼 보이지만, 메가바이트급 이미지를 데이터 URI 로 박으면 페이지 크기가 33% 늘고 gzip 압축률도 떨어진다 (이미 압축된 바이너리를 Base64 로 인코드한 결과는 거의 압축되지 않는다).
그래서 다음 규칙이 있다.
- 작은 아이콘 (~5 KB 미만) 은 data URI 로 인라인 — HTTP 요청 줄이는 이점이 오버헤드를 상쇄.
- 큰 이미지 는 그대로 파일/CDN. Base64 로 박을 이유 없음.
- JSON/API 에 작은 바이너리 (서명·해시 등) 를 박을 때는 hex 보다 Base64 가 33% 짧다. Hex 인코딩 / 디코딩 와 비교해 보면 체감.
흔한 사용처
1. 데이터 URI
data:image/png;base64,iVBORw0KGgo... 형식. CSS 의 background-image, HTML 의 <img src>, 이메일 첨부에서 자주 본다. 이미지 → Base64 (Data URI) 가 이미지 파일을 직접 data URI 로 변환.
2. JWT
앞서 봤듯 JWT 의 header·payload 는 Base64URL 로 인코드된 JSON 이다. JWT 디코더 가 자동으로 풀어 준다.
3. Basic 인증 헤더
Authorization: Basic dXNlcjpwYXNz"user:pass" 를 Base64 한 결과. 암호화가 아니므로 HTTPS 필수 — 평문이나 다름없다.
4. 환경 변수에 키 박기
TLS private key, GCP service account JSON 등 줄바꿈이 섞인 멀티라인 키 를 환경 변수 한 줄에 박을 때 Base64 로 감싼다. CI/CD 시크릿 매니저에 흔한 패턴.
5. URL 토큰
세션 토큰·invite link·CSRF 토큰을 URL 에 박을 때 Base64URL 을 쓴다. URL 인코딩 / 디코딩 와 헷갈리기 쉬운데, 두 인코딩은 목적이 다르다 — URL 인코딩은 특수 문자 를 %XX 로, Base64URL 은 임의 바이너리 를 텍스트로.
자주 빠지는 함정
1. 암호화가 아니다
Base64 로 박힌 문자열은 누구나 한 번에 풀 수 있다. "Base64 로 숨겨뒀어" 는 보안이 아니라 가독성 회피일 뿐. 진짜 보안이 필요하면 AES/암호화 후 Base64.
2. 줄바꿈 / 공백
RFC 4648 표준은 줄바꿈 없는 단일 문자열이다. 하지만 PEM (TLS 인증서) 같은 일부 포맷은 76 글자마다 줄바꿈을 넣는다. 디코드 전에 줄바꿈/공백을 제거하지 않으면 라이브러리에 따라 에러 또는 잘못된 결과.
3. 패딩 누락
Base64URL 에서 패딩을 생략한 토큰을 표준 Base64 디코더에 그대로 넘기면 "invalid length" 에러. 길이가 4 의 배수가 되도록 = 를 다시 붙이거나, 패딩 허용 옵션이 있는 디코더를 쓴다.
4. 문자 인코딩 혼동
한국어 텍스트를 Base64 하기 전에 반드시 UTF-8 바이트로 직렬화. JavaScript 의 btoa() 는 Latin-1 만 받아서 비-ASCII 입력에 에러를 던진다. 표준 패턴은 btoa(unescape(encodeURIComponent(text))) 또는 new TextEncoder().encode(text) 를 거친 뒤 Base64.
5. base64 ≠ MIME base64
RFC 2045 (MIME) 은 줄바꿈을 의무화한 또 다른 변형이다. Python 의 base64.b64encode 는 RFC 4648 이지만 base64.encodebytes 는 MIME — 결과가 다르다. 어느 변형이 필요한지 명시해 두자.
도구로 직접 보기
- Base64 인코딩 / 디코딩 — 텍스트/바이너리 ↔ Base64. 표준과 URL-safe 동시 지원.
- 이미지 → Base64 (Data URI) — 이미지 파일을 data URI 로 변환.
- JWT 디코더 — JWT 안의 Base64URL payload 자동 디코드.
요약
- Base64 = 3 바이트 → 4 글자, 33% 크기 증가.
- 표준 알파벳 + 패딩
=. URL-safe 변형은+/대신-_와 패딩 생략. - 인코딩이지 암호화가 아니다 — Basic 인증·JWT payload 모두 평문.
- JS 환경에서 비-ASCII 텍스트는 UTF-8 직렬화를 먼저 —
btoa()만 쓰면 깨진다. - 큰 바이너리는 Base64 로 박지 말고 파일/CDN 으로. 4/3 배 오버헤드와 압축 손실이 누적된다.