btoa('あいう') の一行で赤い InvalidCharacterError が出た経験はありませんか。英数字は通るのに日本語を入れると止まり、どこかで受け取った Base64 をデコードすると ã‚ã‚“ のような文字列になります。原因は、ある関数の古い前提にあります。
btoa が日本語の前で止まる理由
ブラウザ標準の btoa と atob は、1文字を1バイト、つまり 0〜255 の値だと仮定します。この範囲に収まるのは Latin-1 の文字だけです。
日本語の「あ」のコードポイントは U+3042 で、1バイトには収まりません。そのため btoa は受け付けられず InvalidCharacterError を投げます。MDN も btoa が Latin-1 の範囲しか扱わないと説明しています。絵文字は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 ツールへ こんにちは 🌏 を入れると 44GT44KT44Gr44Gh44GvIPCfjI8= が得られます。この値をそのまま貼り戻してデコードすると こんにちは 🌏 が一文字も崩れずに戻ります。ツールが内部で UTF-8 を経由しているためで、自分でコードを書くときも同じ原理を当てはめれば十分です。
標準 Base64 と URL-safe の違い
標準 Base64 は +・/、そして末尾の = パディングを使います。困るのは、これらが URL やファイル名の中で特殊文字として解釈される点です。
そこで URL-safe 版があります。+ を - に、/ を _ に置き換え、= パディングを取り除きます。先ほどの例の URL-safe 出力は 44GT44KT44Gr44Gh44GvIPCfjI8 で、末尾のパディングが消えています。JWT トークンの header・payload の断片はこの base64url 形式なので、トークンを覗くときは JWT デコーダーが便利です。アドレスに載せる値そのものを扱うなら URL エンコードツールが別にあります。
貼り付ける前に: そのトークンはどこへ行くのか
Base64 は暗号化ではありません。誰でも元に戻せる単なるエンコードです。だからこそ、本番環境のアクセストークンや資格情報をデコードするとき、入力値を見知らぬサーバーへ送信するオンラインツールは、それ自体が漏えい経路になります。
PiPi Worlds の Base64 ツールは、エンコード・デコードをすべてブラウザ内で実行します。入力はサーバーへ送られないため、トークンや非公開の断片を扱うときも安心です。デコードは標準と URL-safe の入力を自動で判別し、途中に混じった空白や改行も整えます。文字化けを気にせず、受け取った値をそのまま貼り付けて確認できます。