Base64デコードで日本語が文字化けする原因と対処
btoa("こんにちは") が InvalidCharacterErrorを出したり、 一見成功したのに復号すると "ã" だらけになる——UTF-8 を絡めた Base64 で頻出の罠です。 原因は btoa/atob が Latin-1 限定であること。 本記事では UTF-8 安全なコードと、Base64URL の扱いをまとめます。
btoa/atob only accept Latin-1 code points, so UTF-8 multi-byte strings get mangled. Use TextEncoder/TextDecoder for a safe round-trip.
TL;DR
- ブラウザの
btoa/atobは Latin-1 専用。UTF-8 は別途エンコードが必要 - UTF-8 安全な方法:
TextEncoder→ バイト列 →btoa - JWT の Base64URL は
-_→+/置換 +=パディングが必要
1. UTF-8 安全な JavaScript 実装 / UTF-8 safe JavaScript
// Encode (string → Base64)
function utf8ToBase64(s) {
const bytes = new TextEncoder().encode(s);
let bin = "";
bytes.forEach(b => (bin += String.fromCharCode(b)));
return btoa(bin);
}
// Decode (Base64 → string)
function base64ToUtf8(b64) {
const bin = atob(b64);
const bytes = Uint8Array.from(bin, c => c.charCodeAt(0));
return new TextDecoder().decode(bytes);
}
utf8ToBase64("こんにちは"); // "44GT44KT44Gr44Gh44Gv"
base64ToUtf8("44GT44KT44Gr44Gh44Gv"); // "こんにちは"2. Base64URL 対応 / Base64URL
JWT や OAuth 関連の Base64URL は URL安全文字セットを使い、末尾パディングを省略します。
function base64UrlDecode(s) {
const b64 = s.replace(/-/g, "+").replace(/_/g, "/");
const pad = b64.length % 4;
const padded = pad ? b64 + "=".repeat(4 - pad) : b64;
return base64ToUtf8(padded);
}3. 言語別の正しい書き方 / Other languages
Python
import base64
b64 = base64.b64encode("こんにちは".encode("utf-8")).decode("ascii")
text = base64.b64decode(b64).decode("utf-8")Shell
echo -n "こんにちは" | base64 # macOS の base64 は -w 0 不要
echo "44GT44KT44Gr44Gh44Gv" | base64 -d4. よくあるハマり / Common pitfalls
- メール添付の Base64 は 76 文字ごとに改行。デコード前に
/\s/gで除去 - 画像の Data URI に
data:image/png;base64,のプレフィクスを付け忘れ - Windows の
certutilは CRLF を挿入する点に注意
5. English summary
Browser btoa/atob only handle Latin-1 code points, so passing Japanese (UTF-8) strings directly produces garbage or throws InvalidCharacterError. The safe path is: TextEncoder to get UTF-8 bytes, map bytes to a Latin-1 string viaString.fromCharCode, then btoa. Reverse the process for decoding. JWT segments use Base64URL: replace -_ with +/ and pad with =before decoding. Strip whitespace from MIME-wrapped Base64 first.