btoa('日本語')で止まる場合と、受け取ったBase64を開いても読めない場合は別の問題です。まず「Base64の構文」「元の文字コード」「元のバイトが残っているか」を分けて確認してください。このページのBase64ツールはUTF-8テキスト用です。
訂正 — 2026年10月10日: 以前の「同じ値をUTF-8で読めば日本語が戻る」という説明は条件が不足していました。既知のShift_JIS例、復元できない条件、ASCIIとLatin-1の違い、利用先のパディング条件を追加しました。古い変換・URL-safeカードは記事から外しました。表紙はAI生成の概念画像であり、実際のエラー画面や動作記録ではありません。
受け取った日本語が読めないときの確認順
| 現象 | まず確認すること | 次の行動 |
|---|---|---|
btoa('日本語')がエラー | 文字列をバイトに変えたか | TextEncoderでUTF-8にしてからBase64へ |
atobの結果が日本語に見えない | 送り手の文字コード | 元のバイトを、確認済みの文字コードで読む |
| Base64の構文エラー | コピー時の欠落、不要な文字、パディング | 元の値と利用先の形式を照合する |
| 構文は通るがUTF-8エラー | 別の文字コードか、画像・圧縮ファイルか | 送り手に形式を確認。ファイルには専用ツールを使う |
途中で切れた、�に置き換えた後に保存した | 変更前のデータが残っているか | 原文・元のバイトを再取得する |
元のバイトが失われた場合、Base64を繰り返しても消えた内容は分かりません。�という文字が元の文章に含まれる場合もあるので、見た目だけで破損と断定しないでください。
送り手に聞くなら「文字コードはUTF-8ですか、Shift_JISですか」「文字列ではなく画像などのファイルですか」「変換前のバイトや欠けていない値がありますか」の3点から始められます。エラーが出ないだけでは、指定した文字コードが正しい証拠にはなりません。
同じ「日本語」でも、バイトが違う
次は機密情報を含まない固定サンプルです。ツールでデコードを選び、1行ずつ試してください。結果はこのページのUTF-8専用ツールに対するものです。
| 元の形式 | 貼り付けるBase64 | このツールでの結果 |
|---|---|---|
ASCIIのHello | SGVsbG8= | Hello |
UTF-8の日本語 | 5pel5pys6Kqe | 日本語 |
既知のShift_JISバイトの日本語 | k/qWe4zq | UTF-8エラー。結果のコピーは無効 |
btoa('café')の1バイト経路 | Y2Fm6Q== | UTF-8エラー。結果のコピーは無効 |
UTF-8のcafé | Y2Fmw6k= | café |
UTF-8のこんにちは 🌏 | 44GT44KT44Gr44Gh44GvIPCfjI8= | こんにちは 🌏 |
文字コードを自動判定しません。 Shift_JISの行は、本文で用意した93 fa 96 7b 8c eaという6バイトに対応します。UTF-8用ツールがエラーを出すことは、Base64そのものが壊れている証拠ではありません。一方、読めそうな結果が出るまで文字コードを次々変える方法でも、元の形式は確定できません。
btoaにはUnicode文字列ではなくバイト相当の値を渡す
btoaの仕様説明では、各値を0〜255のバイトとして扱います。あはU+3042で範囲外なので直接渡すとInvalidCharacterErrorになります。通常の日本語や絵文字は先にUTF-8バイトへ変換します。
ASCIIのHelloなら直接経路とUTF-8経路が一致します。しかしLatin-1範囲のcaféは直接渡せてもY2Fm6Q==になり、UTF-8経由のY2Fmw6k=とは違います。「btoaがエラーを出さない」ことと「UTF-8でエンコードした」ことは同じではありません。
以下は短い公開サンプル専用のブラウザーコードです。ファイル・巨大データ・不正なUTF-16入力の一般処理ではありません。
const text = 'こんにちは 🌏';
const bytes = new TextEncoder().encode(text);
let binary = '';
for (const byte of bytes) binary += String.fromCharCode(byte);
const base64 = btoa(binary);
const restoredBytes = Uint8Array.from(atob(base64), ch => ch.charCodeAt(0));
const restored = new TextDecoder('utf-8', {
fatal: true, ignoreBOM: true
}).decode(restoredBytes);
console.log({ base64, restored, byteLength: bytes.length });
出力のBase64は44GT44KT44Gr44Gh44GvIPCfjI8=、戻る文字列はこんにちは 🌏、UTF-8の長さは20バイトです。atobはバイト相当の文字列を返すので、そのまま日本語として表示するのではなくバイト配列に戻して読みます。
fatal: trueは不正なバイトを黙って置換せずエラーにします。ignoreBOM: trueは先頭のBOM(U+FEFF)を結果に含める指定です。「BOMを削除する」という意味ではありません。このツールもBOMを保持し、見えない文字を含む場合は注意を表示します。
Shift_JISだと確認できている場合の独立サンプル
Encoding StandardにはUTF-8以外のデコーダーも定義されています。次は送り手が文字コードを確認済みという前提で、この固定サンプルだけを比較するコードです。
const knownShiftJisBase64 = 'k/qWe4zq';
const bytes = Uint8Array.from(atob(knownShiftJisBase64), ch => ch.charCodeAt(0));
let utf8Result;
try {
utf8Result = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
} catch {
utf8Result = 'UTF-8 error';
}
const shiftJisText = new TextDecoder('shift_jis', { fatal: true }).decode(bytes);
console.log({ utf8Result, shiftJisText });
このサンプルではUTF-8側がUTF-8 error、Shift_JIS側が日本語です。PiPiのツールにShift_JIS 対応を追加したわけではありません。 未知の値の文字コードを推測する機能でも、失われた原文を直す機能でもありません。実データは所属組織で承認されたローカル手順と、送り手の形式情報で扱ってください。
URL-safeとサイズは、文字コードとは別に確認
RFC 4648のbase64urlは、標準の+・/を-・_に変えるアルファベットです。パディング省略は利用先の規則に従います。PiPiのURL-safeエンコードは末尾の=を省略するため、上の例は44GT44KT44Gr44Gh44GvIPCfjI8になります。ほかのAPIにも同じ規則があるとは限りません。
改行なし・パディングありの標準Base64の長さは4 × ceil(入力バイト数 / 3)文字です。日本語は9バイト → 12文字、こんにちは 🌏は20バイト → 28文字、Helloは5バイトから8文字になります。画面の日本語の文字数をそのまま計算に使わず、短い入力まで「必ず約33%増」と考えないでください。
本番の秘密は貼り付けない
Base64は暗号化ではありません。本番トークン・パスワード・非公開データは入力しないでください。変換自体がブラウザ内で動くことは、ページ全体やブラウザー環境で秘密が守られる保証ではありません。
JWTの構造を読むJWTデコーダーも、署名の検証や信頼性の判定ではありません。URLで送る文字の扱いはURLエンコードツールで別に確認できます。まずは上の公開サンプルで期待結果を照合し、自分の値については送り手の文字コード・形式・完全性を確かめてください。