Decodex

DECODEX · BASE64 · UTF-8

JavaScript 한글 Base64 인코딩·디코딩 — btoa 오류 해결

JavaScript btoa의 한글·이모지 오류를 TextEncoder와 TextDecoder로 해결합니다. UTF-8 인코딩·디코딩 코드와 검증된 예제를 제공합니다.

btoa()는 각 코드 단위가 1바이트 범위에 들어가는 바이너리 문자열을 받습니다. 한글·힌디어·이모지를 직접 넣으면 InvalidCharacterError가 발생할 수 있습니다. Unicode 텍스트는 먼저 UTF-8 바이트로 바꾼 뒤 Base64로 인코딩하고, 복원할 때는 디코딩된 바이트를 UTF-8로 읽어야 합니다.

btoa("안녕하세요")가 실패하는 이유

JavaScript 문자열이 그대로 UTF-8 바이트인 것은 아닙니다. btoa()는 UTF-8 변환을 자동으로 수행하지 않습니다. 바이트 범위를 벗어난 문자는 오류가 나고, 범위 안의 일부 문자도 의도한 UTF-8과 다른 바이트로 인코딩될 수 있습니다. 악센트가 있는 문자까지 일관되게 처리하려면 TextEncoder를 사용하세요.

한글과 이모지를 처리하는 안전한 코드

아래 코드는 텍스트를 UTF-8 바이트로 명시적으로 변환합니다. 인코더는 큰 배열을 한 번에 펼쳐 함수 인수 제한에 걸리지 않도록 작은 묶음으로 처리합니다. 디코더는 Base64URL 문자와 공백을 처리하고, 형식을 검사한 뒤 패딩을 복원합니다. TextDecoder의 fatal 옵션으로 잘못된 UTF-8을 대체 문자로 덮지 않고 오류로 알립니다. 이 코드는 텍스트용이며 파일 미리보기 코드는 아닙니다.

한글·이모지 왕복 변환 확인하기

정확히 안녕하세요 😀를 인코딩하면 7JWI64WV7ZWY7IS47JqUIPCfmIA=가 됩니다. 다시 디코딩하면 공백과 이모지를 포함한 원문과 같아야 합니다. 아래 예제는 브라우저 변환 코드로 검증했습니다. 예제 링크에는 미리 정한 이름만 넣고 사용자 입력 텍스트를 URL에 넣지 않습니다.

Base64 오류와 UTF-8 오류는 해결 방법이 다릅니다

atob() 단계의 오류는 잘못된 Base64 문자, 패딩, 데이터 URL 접두사 같은 문제일 가능성이 있습니다. fatal 모드 TextDecoder 오류는 Base64 형식과 별개로 바이트가 올바른 UTF-8이 아니라는 뜻입니다. 이미지·PDF·다른 문자 인코딩도 이 오류를 낼 수 있습니다. 어느 단계에서 실패했는지 확인해야 정확하게 해결할 수 있습니다.

브라우저와 Node.js의 코드 구분

Node.js에서는 Buffer.from(text, "utf8").toString("base64")로 UTF-8 텍스트를 인코딩할 수 있습니다. Buffer 디코딩은 일부 입력을 관대하게 받아들이므로 변환 성공만으로 신뢰할 수 없는 입력을 검증했다고 보면 안 됩니다. 브라우저에서는 아래 TextEncoder/TextDecoder 코드로 처리할 수 있습니다. Decodex는 최대 5 MB의 UTF-8 텍스트를 브라우저 안에서 변환합니다.

예제로 직접 변환하기

// Browser JavaScript: text → UTF-8 bytes → Base64.
function utf8ToBase64(text) {
  const bytes = new TextEncoder().encode(text);
  let binary = '';
  for (let i = 0; i < bytes.length; i += 8192) {
    binary += String.fromCharCode(...bytes.subarray(i, i + 8192));
  }
  return btoa(binary);
}

// Accept standard Base64 or Base64URL, with complete or omitted padding.
function base64ToUtf8(value) {
  let normalized = value.replace(/\s/g, '').replace(/-/g, '+').replace(/_/g, '/');
  if (!/^[A-Za-z0-9+/]*={0,2}$/.test(normalized) ||
      normalized.length % 4 === 1 ||
      (normalized.includes('=') && normalized.length % 4 !== 0)) {
    throw new Error('Invalid Base64 characters, length or padding');
  }
  normalized += '='.repeat((4 - normalized.length % 4) % 4);
  const binary = atob(normalized);
  const bytes = Uint8Array.from(binary, character => character.charCodeAt(0));
  return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
}

const text = '안녕하세요 😀';
const encoded = utf8ToBase64(text);
console.log(encoded); // 7JWI64WV7ZWY7IS47JqUIPCfmIA=
console.log(base64ToUtf8(encoded)); // 안녕하세요 😀

예제로 직접 변환하기 →

Base64 → UTF-8 · UTF-8 → Base64

자주 묻는 질문

atob()만 쓰면 한글이 바로 나오나요?

아니요. atob()의 결과는 바이너리 문자열입니다. 바이트 값을 Uint8Array로 옮긴 뒤 TextDecoder("utf-8")로 읽어야 합니다.

btoa(unescape(encodeURIComponent(text)))를 써도 되나요?

TextEncoder와 TextDecoder를 사용하는 명시적인 바이트 변환을 권장합니다. 문자 인코딩 과정이 분명하고 오래된 unescape() 함수에 의존하지 않습니다.

참고 자료

더 알아보기