블로그 목록

Base64 인코딩 원리: 3바이트가 4글자가 되는 이유와 한글 인코딩 예제

웹 개발을 하다 보면 data:image/png;base64,iVBORw0...처럼 알 수 없는 긴 문자열을 자주 만나게 됩니다. 이메일 첨부 파일, JSON 안에 담긴 이미지, 로그인 토큰의 일부도 비슷한 모양입니다. 이 문자열이 바로 Base64로 인코딩된 데이터입니다. 개발자가 아니더라도 "이 문자열은 암호인가?", "왜 원본보다 길어졌나?" 같은 의문이 들 수 있습니다. 이 글에서는 Base64가 무엇을 하는지, 실제 한글 문자열이 어떤 단계를 거쳐 바뀌는지, 그리고 자주 하는 실수를 차례로 정리합니다.

1. Base64는 무엇을 위한 것인가

컴퓨터의 데이터는 결국 바이트(0~255 값)의 나열입니다. 그런데 이메일(MIME)이나 JSON, HTML 속성처럼 텍스트만 다루도록 만들어진 통로에 이미지 같은 바이너리 바이트를 그대로 넣으면, 제어 문자나 줄바꿈 처리 차이 때문에 데이터가 깨지거나 문법이 망가질 수 있습니다.

Base64는 임의의 바이트를 64개의 문자(A–Z, a–z, 0–9, +, /)와 패딩 문자 =만으로 표현하는 방식입니다. 이 문자들은 대부분의 텍스트 환경에서 특별한 의미 없이 그대로 전달되므로, 바이너리를 텍스트 통로로 안전하게 옮길 수 있습니다. 표준은 RFC 4648에 정의되어 있습니다.

Base64는 암호화가 아닙니다

Base64는 누구나 같은 규칙으로 되돌릴 수 있는 표현 방식의 변환일 뿐입니다. 비밀번호나 개인정보를 Base64로 바꿔 두었다고 해서 보호되는 것은 아닙니다.

실제로 어디에서 쓰이나

  • data URL: 작은 이미지를 별도 파일 없이 data:image/png;base64,... 형태로 HTML이나 CSS에 직접 넣습니다.
  • 이메일 첨부 파일: MIME 규격에서 첨부 파일을 본문 텍스트 안에 담을 때 사용합니다.
  • HTTP Basic 인증: 사용자이름:비밀번호 문자열을 Base64로 바꿔 Authorization 헤더에 넣습니다(RFC 7617). 인코딩일 뿐이므로 반드시 HTTPS와 함께 써야 합니다.
  • JWT 같은 토큰: 점(.)으로 구분된 각 부분이 URL-safe 변형인 Base64URL로 인코딩되어 있습니다.

2. 3바이트가 4글자가 되는 원리

1바이트는 8비트이고, 64개의 문자는 2의 6제곱이므로 문자 하나가 6비트를 나타냅니다. 8과 6의 최소공배수인 24비트를 한 묶음으로 보면, 3바이트(8비트×3)를 6비트×4조각으로 다시 나눌 수 있습니다. 각 조각(0~63)을 표의 문자로 바꾸면 4글자가 됩니다.

  • 원본: [8비트] [8비트] [8비트] = 24비트
  • 재분할: [6비트] [6비트] [6비트] [6비트] = 24비트
  • 결과: 문자 4개

그래서 인코딩 결과는 원본보다 약 3분의 1(약 33%) 길어집니다. 정확한 길이는 원본 길이를 3으로 나눈 나머지와 패딩 여부에 따라 달라지며, 이메일처럼 일정 길이마다 줄바꿈을 넣는 환경에서는 그만큼 더 늘어납니다.

3. 예제로 따라가기: "한글" 인코딩

문자열을 Base64로 바꾸려면 먼저 문자열을 바이트로 바꿔야 합니다. 웹에서는 보통 UTF-8을 사용하며, 한글 한 글자는 UTF-8에서 3바이트입니다.

  1. 문자 → UTF-8 바이트: "한"(U+D55C)은 ED 95 9C, "글"(U+AE00)은 EA B8 80입니다. 총 6바이트입니다.
  2. 바이트 → 비트: 첫 3바이트는 11101101 10010101 10011100입니다.
  3. 6비트씩 자르기: 111011 011001 010110 011100 → 10진수로 59, 25, 22, 28
  4. 문자표 대응: 0~25는 A–Z, 26~51은 a–z, 52~61은 0–9, 62는 +, 63은 /입니다. 59는 7, 25는 Z, 22는 W, 28은 c이므로 7ZWc가 됩니다.
  5. 나머지 3바이트(글)도 같은 방식으로 6riA가 되어, 최종 결과는 7ZWc6riA입니다.

6바이트가 8글자가 되었으니 3:4 비율이 그대로 확인됩니다.

4. 패딩(=)은 언제 붙나

원본 길이가 3의 배수가 아니면 마지막 묶음이 모자랍니다. 이때 부족한 비트는 0으로 채우고, 만들어지지 않은 자리는 =로 표시합니다.

  • 1바이트가 남을 때: 8비트에 0을 4비트 붙여 12비트(문자 2개)로 만들고 ==를 붙입니다. 예: "A" → QQ==
  • 2바이트가 남을 때: 16비트에 0을 2비트 붙여 18비트(문자 3개)로 만들고 =를 붙입니다. 예: "AB" → QUI=

5. JavaScript로 다루기

브라우저의 btoa()와 atob()는 문자열의 각 문자를 1바이트로 취급하므로, 한글처럼 코드값이 255를 넘는 문자를 그대로 넣으면 InvalidCharacterError가 발생합니다. 먼저 TextEncoder로 UTF-8 바이트를 만든 뒤 변환하는 방법이 MDN에서도 안내하는 방식입니다.

// 문자열 → UTF-8 바이트 → Base64
function encodeBase64(text) {
  const bytes = new TextEncoder().encode(text);
  let binary = "";
  for (let i = 0; i < bytes.length; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

// Base64 → UTF-8 바이트 → 문자열
function decodeBase64(base64) {
  const binary = atob(base64);
  const bytes = Uint8Array.from(binary, (ch) => ch.charCodeAt(0));
  return new TextDecoder().decode(bytes);
}

encodeBase64("한글");     // "7ZWc6riA"
decodeBase64("7ZWc6riA"); // "한글"

6. 자주 하는 실수

  • 한글을 btoa()에 바로 넣기: 오류가 나거나, 우회하려다 문자가 깨진 결과를 만들기 쉽습니다. 항상 바이트로 바꾼 뒤 인코딩합니다.
  • Base64와 Base64URL 혼동: URL이나 JWT에서는 +, / 대신 -, _를 쓰는 URL-safe 변형(RFC 4648 5절)을 사용하고, 패딩을 생략하는 경우가 많습니다. 일반 Base64 디코더에 그대로 넣으면 실패할 수 있습니다.
  • 용량 절감을 기대하기: Base64는 데이터를 늘립니다. 큰 이미지를 HTML에 직접 넣으면 문서가 무거워지고 브라우저 캐시도 따로 활용하기 어렵습니다. 작은 아이콘 정도에만 쓰는 것이 일반적입니다.
  • 보안 수단으로 사용하기: 앞에서 말했듯 누구나 즉시 복원할 수 있습니다.

7. 정리

Base64는 바이트를 6비트씩 나눠 64개의 안전한 문자로 옮기는 인코딩입니다. 3바이트가 4글자가 되므로 크기는 약 33% 늘어나고, 끝이 모자라면 =로 채웁니다. 한글은 UTF-8 바이트로 바꾼 뒤 인코딩해야 하며, 암호화가 아니라는 점을 기억하면 대부분의 실수를 피할 수 있습니다.

직접 결과를 확인해 보고 싶다면 anyTools의 Base64 변환기를 사용할 수 있습니다. 입력값을 서버로 보내지 않고 브라우저에서 처리하므로 토큰 같은 값을 확인할 때도 부담이 적습니다. URL에서 쓰는 퍼센트 인코딩과의 차이는 URL·HTML 인코딩 정리에서 다룹니다.

참고 자료