[가이드] Base64란 무엇이며 어떻게 작동하는가?
기본 원리부터 시작하는 실용적인 해설서. Base64의 핵심인 6비트 그룹화 기법, 출력이 33% 더 커지는 이유, = 패딩이 어디서 비롯되는지, 그리고 언제 사용해야 하는지를 배워 봅니다.
// 한 문장으로 보는 정의
Base64는 64개의 인쇄 가능한 ASCII 문자, 즉 A–Z, a–z, 0–9, 그리고 두 개의 기호(일반적으로 +와 /, 패딩 표시자로서 =)만을 사용하여 이진 데이터를 텍스트로 표현하는 방식입니다. 아이디어는 이것이 전부입니다. 이진 데이터 3바이트마다 4개의 Base64 문자로 재그룹화되며, 그 외에는 아무것도 바뀌지 않습니다.
Base64는 암호화가 아니라 인코딩입니다. 누구나 밀리초 단위로 역변환할 수 있습니다. 목적은 기밀 유지가 아니라 안전한 전송입니다. Base64는 이진 페이로드(이미지, 인증서, PDF 파일, 암호 키 등)를 텍스트만 허용하는 곳(HTML 속성, JSON 문자열, URL, 이메일 본문, YAML 설정, 데이터베이스 TEXT 컬럼, 환경 변수 등)에 집어넣을 수 있게 해 줍니다.
// 왜 필요한가
텍스트 전용 채널은 자신이 인식하지 못하는 바이트를 제거하거나 변형합니다. 7비트 ASCII만 이해하는 이메일 게이트웨이는 영어가 아닌 문자의 최상위 비트를 망가뜨립니다. JSON 파서는 내장된 null 바이트를 거부합니다. 공백, 따옴표, 앰퍼샌드가 그대로 들어간 URL은 해당 문자들이 퍼센트 인코딩되기 전까지는 유효하지 않습니다. 1990년대 SMTP 표준은 메일 본문이 항상 평범한 영문 텍스트라고 가정했지만, 사람들은 사진과 스프레드시트도 보내고 싶어 했습니다.
Base64는 알려진 모든 텍스트 채널을 그대로 통과할 수 있는 64개의 문자, 즉 대문자 26자, 소문자 26자, 숫자 10자, 그리고 (a) 인쇄 가능하며, (b) 일반적인 이스케이프 메커니즘에서 사용되지 않고, (c) ASCII를 왕복 전송해도 서로 구분되는 두 개의 기호로 출력을 제한하여 이 문제를 해결합니다. 추가로 패딩 문자 =도 안전하게 사용됩니다.
// 6비트 그룹화 기법
Base64의 기계적인 핵심은 한 줄로 요약됩니다. 입력을 비트 스트림으로 보고, 6비트 조각으로 잘라서, 각 조각을 64자 알파벳에서 조회합니다.
왜 6비트일까요? 2^6 = 64이므로 각 6비트 덩어리는 알파벳의 한 문자와 정확히 대응됩니다. 왜 7비트나 8비트가 아닐까요? 7비트는 128개의 값을 주는데(너무 많고, 모두 인쇄 가능한 것은 아닙니다) 8비트는 다시 원시 이진 그 자체입니다. 6비트는 모든 가능한 값이 사람이 읽을 수 있는 문자와 대응되는 최적점입니다.
입력 3바이트 = 24비트 = 정확히 4개의 6비트 그룹 = Base64 문자 4개. 이 3대4의 비율은 고정되어 있으며, Base64 출력 크기가 입력의 약 4/3배, 즉 약 33% 더 커지는 이유입니다.
// Worked example: encoding the 3 bytes 'Man' (77 97 110)
// ASCII: M a n
// Binary: 01001101 01100001 01101110
// Re-group into 6-bit chunks:
// 010011 010110 000101 101110
// Decimal: 19 22 5 46
// Base64: T W F u
// Result: 'TWFu' (stored as 4 ASCII bytes: 84 87 70 117)
// BASE64 알파벳
RFC 4648 §4는 표준 알파벳을 정의합니다. 위치 0–25는 A–Z, 26–51은 a–z, 52–61은 0–9, 위치 62는 +, 위치 63은 /입니다.
URL 안전 변형(RFC 4648 §5, base64url로도 알려짐)은 +를 -로, /를 _로 바꿔서 추가 이스케이프 없이 URL과 파일 이름에 안전하게 넣을 수 있게 합니다. 두 변형 모두 해당하는 알파벳을 적용하면 동일하게 디코딩됩니다.
// Standard Base64 alphabet (index → character)
// 0–25: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
// 26–51: a b c d e f g h i j k l m n o p q r s t u v w x y z
// 52–61: 0 1 2 3 4 5 6 7 8 9
// 62: + (or - in URL-safe)
// 63: / (or _ in URL-safe)
// pad: =
// = 패딩은 어디서 비롯되는가
알고리즘은 입력 크기가 3바이트의 배수일 것을 기대합니다(그래야 Base64 문자 4개로 고르게 그룹화됩니다). 입력 길이가 3의 배수가 아닐 때는 1바이트 또는 2바이트가 남게 되며, 이는 마지막 4자 출력 그룹을 채우기에 부족합니다. =로 패딩하는 것은 몇 바이트가 부족했는지를 나타냅니다.
• 입력 길이 mod 3 == 0 → 패딩 없음(예: 'Man' → 'TWFu').
• 입력 길이 mod 3 == 1 → 패딩 문자 두 개(예: 'M' → 'TQ==').
• 입력 길이 mod 3 == 2 → 패딩 문자 한 개(예: 'Ma' → 'TWE=').
// Why 'M' becomes 'TQ==':
// Byte: M = 01001101 (8 bits)
// Padded to 12 bits with zeros: 010011 010000
// Decimal: 19, 16 → 'T', 'Q'
// Output length must be multiple of 4 → add '==' padding
// Result: 'TQ=='
//
// When decoding, the decoder strips '==' and recovers the first 8 bits,
// discarding the trailing zero bits.
// 33% 오버헤드는 정확한 값인가?
거의 맞지만 정확하지는 않습니다. 정확한 공식은 길이 n의 바이트 입력에 대해 ceil(n / 3) × 4 문자입니다. 100 KB 입력의 경우 ceil(102400 / 3) × 4 = 136,534자로, 100 KB보다 33.3% 더 큽니다. 실제로 HTTP 수준의 gzip 또는 Brotli 압축을 적용하면 전송 비용은 10–15%에 가까워집니다. Base64 텍스트가 원시 이진만큼은 아니지만 그럭저럭 잘 압축되기 때문입니다.
작은 페이로드에서는 패딩이 큰 영향을 미칩니다. 1바이트를 인코딩하는 데 4자가 들며(크기의 4배), 2바이트를 인코딩해도 4자가 듭니다. 그래서 아주 작은 Base64 문자열은 입력에 비해 놀랄 만큼 길어 보입니다.
// 실제 세계에서 BASE64가 쓰이는 곳
-
>
HTML/CSS의 데이터 URI — 아이콘과 로고를 인라인으로 삽입:
<img src="data:image/png;base64,…"> - > JSON 및 GraphQL 페이로드 — 텍스트 프로토콜 내부에서 이진 데이터(파일 업로드, 썸네일)를 전송해야 하는 API
- > JWT 토큰 — JWT의 점으로 구분된 세 부분은 모두 base64url 문자열
- > HTTP Basic Auth — Authorization 헤더에서 username:password가 Base64로 인코딩됨(TLS 없이는 여전히 안전하지 않습니다!)
- > 이메일(MIME) — 레거시 7비트 SMTP 서버를 통과하도록 첨부 파일이 Base64로 인코딩됨
- > PEM 형식 인증서와 키 — -----BEGIN CERTIFICATE----- 블록은 Base64로 인코딩된 DER 바이너리를 감쌈
- > SSH 키 — id_rsa.pub와 authorized_keys 라인은 Base64로 인코딩된 공개 키
- > 데이터베이스 TEXT 컬럼 — BLOB 타입을 쓸 수 없을 때 Base64로 이진 데이터를 텍스트로 저장
- > 환경 변수 — 쿠버네티스 Secrets 값은 Base64로 인코딩됨(단, 암호화된 것은 아닙니다)
- > QR 코드와 매직 링크 — URL에 안전하게 삽입할 수 있는 짧은 Base64 토큰
// BASE64는 암호화가 아니다
이것은 가장 흔한 오해 중 하나입니다. Base64는 데이터의 내용을 숨기지 않습니다. 공개되고 잘 문서화된 매핑을 사용해 이진 데이터를 텍스트로 다시 쓸 뿐입니다. cGFzc3dvcmQ=로 Base64 인코딩된 비밀번호는 평문 password와 정확히 동일한 수준으로 취약합니다. 누구나 함수 호출 한 번으로 디코딩할 수 있습니다. Base64는 16진수처럼 보호 수단이 아니라 표현 방식으로 취급해야 합니다.
프라이버시가 필요하다면 먼저 평문에 실제 암호화 프리미티브(AES-GCM, ChaCha20-Poly1305, age, PGP)를 적용하고, 그 다음에 텍스트 채널을 통해 전송해야 한다면 암호문을 Base64로 인코딩하십시오. Base64 단계는 최종 전달 구간일 뿐이지 보안 계층이 아닙니다.
쿠버네티스 Secrets가 대표적인 함정입니다. 쿠버네티스는 비밀 값을 Base64로 인코딩해 저장하기 때문에 어렴풋이 보호되는 것처럼 보입니다. 그렇지 않습니다. 해당 네임스페이스에 대한 읽기 권한이 있는 사람은 누구나 역변환할 수 있습니다. 진정한 비밀 보호를 위해서는 SealedSecrets, Vault, SOPS 또는 클라우드 네이티브 KMS 통합과 같은 도구가 필요합니다.
// 모든 언어에서의 빠른 인코딩 / 디코딩
// JavaScript (browser):
// btoa('Hello') → 'SGVsbG8='
// atob('SGVsbG8=') → 'Hello'
// (btoa/atob only handle Latin-1; use TextEncoder for Unicode)
//
// Node.js:
// Buffer.from('Hello').toString('base64') → 'SGVsbG8='
// Buffer.from('SGVsbG8=', 'base64').toString() → 'Hello'
//
// Python:
// import base64
// base64.b64encode(b'Hello') → b'SGVsbG8='
// base64.b64decode('SGVsbG8=') → b'Hello'
//
// Go:
// base64.StdEncoding.EncodeToString([]byte("Hello")) → "SGVsbG8="
//
// Ruby:
// Base64.strict_encode64('Hello') → 'SGVsbG8='
//
// Shell:
// echo -n 'Hello' | base64 → 'SGVsbG8='
// echo 'SGVsbG8=' | base64 -d → 'Hello'
// 피해야 할 흔한 실수
-
>
URL 안전 문자열을 표준 알파벳으로 인코딩 — 수신 측 디코더가
+와/를 거부합니다. 출력이 URL, 쿠키, 파일 이름으로 들어간다면 base64url을 사용하세요. - > 이중 인코딩 — 이미 Base64로 된 텍스트를 인코더에 다시 통과시키는 것. 인코딩 전에 데이터가 이미 Base64인지 항상 확인하세요.
-
>
UTF-8을 잊는 것 —
é가 Latin-1 범위 밖이기 때문에btoa('héllo')는 브라우저에서 예외를 던집니다. 먼저TextEncoder로 문자열을 바이트로 변환하세요. -
>
패딩 제거 불일치 — base64url은 흔히
=패딩을 생략합니다. 디코더가 엄격하다면 패딩을 다시 추가하세요:input + '==='.slice((input.length + 3) % 4). - > Base64가 안전하다고 가정하는 것 — 그렇지 않습니다. 암호화는 별개입니다. 항상 그렇습니다.
- > 거대한 이진 데이터에 Base64 사용 — 500 MB 파일을 하나의 문자열로 인코딩하면 메모리가 고갈됩니다. 대신 스트리밍하세요.
-
>
줄 바꿈 vs 한 줄 — MIME/PEM은 Base64를 64열 또는 76열에서
\n으로 줄 바꿈합니다. 대부분의 다른 문맥은 한 줄을 기대합니다. 상황에 맞게 개행을 제거하거나 삽입하세요.
// 30초 만에 BASE64 — 핵심 요약
- > 64개의 인쇄 가능한 ASCII 문자(A–Z, a–z, 0–9, +, /)와 = 패딩
- > 입력 3바이트 → 출력 4자(고정 비율)
- > 출력은 입력보다 약 33% 더 큼
- > 가역적: 암호화가 아니라 인코딩
- > URL과 파일 이름에는 base64url 사용(+ → -, / → _로 대체)
- > 짧은 입력은 1개 또는 2개의 = 문자로 패딩해 출력 길이가 4의 배수가 되도록 함
- > 유니코드 텍스트의 경우: 먼저 문자열을 UTF-8 바이트로 인코딩한 다음, 그 바이트를 Base64로 인코딩
- > 모든 주요 언어의 표준 라이브러리로 디코딩 가능
- > HTML, JSON, URL, 이메일, PEM 파일에서 안전
- > 보안 계층으로는 안전하지 않음 — 민감한 데이터에는 항상 실제 암호화와 함께 사용
// 다음 단계
이제 메커니즘을 이해하셨으니, 저희 Base64 인코더 또는 Base64 디코더를 사용해 보고 출력 결과를 문자별로 살펴보세요. URL 안전 페이로드의 경우에는 base64url로 전환하세요. 이미지는 이미지 → Base64를 참조하세요.
관련 심층 분석:
• Base64 URL 안전 vs 표준 — 각 알파벳을 언제 사용할지
• UTF-8 및 유니코드를 위한 Base64 — btoa() 함정 피하기
• JavaScript 및 Node.js에서의 Base64 — atob, btoa, Buffer
• Base64 vs Base64URL vs Base32 — 비교표