DevToolBox

Base64デコードで日本語が文字化けする原因と対処

最終更新日: 2026-04-19公開日: 2026-04-19執筆: DevToolBox編集部

btoa("こんにちは")InvalidCharacterError を出したり、 一見成功したのに復号すると "ã" だらけになる——UTF-8 を絡めた Base64 で頻出の罠です。 原因は btoa/atobLatin-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.

Base64 Encoder / Decoder

文字化けが起きたBase64文字列をその場でデコードして中身を確認。UTF-8として正しく戻るかをブラウザ内で検証できます。

今すぐ試す →

TL;DR

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 -d

4. よくあるハマり / Common pitfalls

5. Node.jsでの簡単な書き方 / The simple way in Node.js

ブラウザにはBufferがないためTextEncoderを経由する必要がありますが、 Node.jsはBufferが文字エンコーディングを直接扱えるため、はるかに簡潔に書けます。

// Encode
const b64 = Buffer.from("こんにちは", "utf-8").toString("base64");

// Decode
const text = Buffer.from(b64, "base64").toString("utf-8");

ブラウザ向けのTextEncoder/TextDecoderによるバイト列変換は、まさにNode.jsのBufferが内部で行っていることを手動で再現しているものです。

6. 新しいネイティブAPI: Uint8Array.fromBase64 / toBase64

TC39提案(Uint8ArrayのBase64/Hex変換)がECMAScriptに採用され、対応ブラウザ・Node.jsではbtoa/atobを介さずBase64を直接扱えます。UTF-8のバイト列を扱う分には Latin-1制限の問題が最初から発生しません。

// 対応環境(要フィーチャーチェック)
const bytes = new TextEncoder().encode("こんにちは");
const b64 = bytes.toBase64();          // Uint8Array -> Base64文字列
const decoded = Uint8Array.fromBase64(b64);
new TextDecoder().decode(decoded);     // "こんにちは"

// 対応チェック
const supportsNativeBase64 = typeof Uint8Array.prototype.toBase64 === "function";

比較的新しいAPIのため、幅広い環境をサポートする必要がある場合は本記事冒頭のTextEncoder+btoa方式か、Buffer(Node.js)の利用を推奨します。

7. 環境別・正しい変換方法の早見表 / Correct method by environment

環境 / EnvironmentUTF-8安全な方法
ブラウザ(広く対応させたい場合)TextEncoderString.fromCharCodebtoa
ブラウザ(新しめの環境限定でよい場合)Uint8Array.prototype.toBase64/fromBase64
Node.jsBuffer.from(str, "utf-8").toString("base64")
Pythonbase64.b64encode(s.encode("utf-8"))
直接 btoa("日本語")InvalidCharacterErrorまたは文字化け

8. 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.

よくある質問 / FAQ

btoa と atob はなぜ日本語で壊れるのか?
btoa/atob は Latin-1(0-255)のコードポイントのみ対応します。UTF-8 のマルチバイト列をそのまま渡すとバイナリが崩れます。TextEncoder/TextDecoder で UTF-8 を経由してください。
Why do btoa/atob break on Japanese?
They handle only Latin-1 code points. Go through TextEncoder/TextDecoder to round-trip UTF-8 correctly.
JWT の Base64URL で失敗する
標準Base64と異なり -_ を使い、末尾の = を省略します。-/_ を +// に戻し、長さを4の倍数にパディングしてからデコードしてください。

関連ツール / Related tools

関連ガイド / Related guides