btoa('안녕') 한 줄에 빨간 InvalidCharacterError가 뜬 적 있나요. 영문은 멀쩡한데 한글만 넣으면 멈추고, 어디선가 받은 base64를 풀었더니 안녕 같은 글자가 나옵니다. 원인은 함수 하나의 오래된 가정에 있습니다.
btoa가 한글 앞에서 멈추는 이유
브라우저 내장 btoa와 atob는 한 글자를 1바이트, 즉 0~255 사이의 값으로 가정합니다. 이 범위에 담기는 건 Latin-1 문자뿐입니다.
한글 ‘안’의 코드포인트는 U+C548입니다. 1바이트에 들어가지 않으니 btoa는 받아들이지 못하고 InvalidCharacterError를 던집니다. MDN도 btoa가 Latin-1 범위만 다룬다고 설명합니다. 이모지는 한 글자가 4바이트까지 가므로 더 확실하게 막힙니다.
Latin-1 직접 인코딩과 UTF-8 경유의 차이
같은 입력이라도 처리 경로에 따라 결과가 갈립니다.
| 입력 | btoa 직접 호출 | UTF-8 바이트 경유 |
|---|---|---|
Hello | 성공 | 성공 |
안녕하세요 | InvalidCharacterError | 성공 |
🌏 (이모지) | InvalidCharacterError | 성공 |
영문은 어차피 Latin-1 안에 있어 두 경로가 같습니다. 갈라지는 건 한글·이모지처럼 1바이트를 넘는 문자입니다.
해결의 핵심은 UTF-8 바이트로 먼저 바꾸기
방법은 단순합니다. 문자열을 그대로 btoa에 넘기지 말고, TextEncoder로 UTF-8 바이트 배열로 바꾼 뒤 인코딩합니다. 디코딩은 반대로 TextDecoder('utf-8')를 거칩니다. 이러면 모든 문자가 1바이트 단위로 쪼개져 btoa의 가정에 들어맞습니다.
실제로 PiPi Worlds Base64 인코딩 도구에 안녕하세요 🌏를 넣으면 7JWI64WV7ZWY7IS47JqUIPCfjI8=가 나옵니다. 이 값을 그대로 다시 붙여 넣고 디코딩하면 안녕하세요 🌏가 글자 하나 안 틀리고 돌아옵니다. 도구가 내부에서 UTF-8을 거치기 때문이고, 직접 코드를 짤 때도 같은 원리를 적용하면 됩니다.
표준 base64와 URL-safe의 차이
표준 base64는 +, /, 그리고 끝의 = 패딩을 씁니다. 문제는 이 문자들이 URL이나 파일명 안에서 특수문자로 해석된다는 점입니다.
그래서 URL-safe 변형이 있습니다. +를 -로, /를 _로 바꾸고 = 패딩을 떼어 냅니다. 위 예시의 URL-safe 출력은 7JWI64WV7ZWY7IS47JqUIPCfjI8로, 끝 패딩이 사라졌습니다. JWT 토큰의 header·payload 조각이 바로 이 base64url 형식이라, 토큰을 뜯어볼 땐 JWT 디코더가 편합니다. 주소창에 넣을 값 전체를 다뤄야 한다면 URL 인코딩 도구가 따로 있습니다.
붙여넣기 전에: 그 토큰은 어디로 가는가
base64는 암호화가 아닙니다. 누구나 되돌릴 수 있는 인코딩일 뿐입니다. 그래서 운영 환경의 액세스 토큰이나 자격증명을 디코딩할 때, 입력값이 모르는 서버로 전송되는 온라인 도구는 그 자체가 유출 경로가 됩니다.
PiPi Worlds Base64 도구는 인코딩·디코딩이 전부 브라우저 안에서 실행됩니다. 입력은 서버로 전송되지 않으므로 토큰·비공개 조각을 다룰 때도 부담이 없습니다. 디코딩은 표준과 URL-safe 입력을 자동으로 가려내고, 중간에 섞인 공백이나 줄바꿈도 정리해 줍니다. 한글이 깨질 걱정 없이, 받은 값을 그대로 붙여 넣어 확인하세요.