Base64 と Base64URL の違い — RFC 4648 で押さえる3つの落とし穴
DDEVELOPER
開発(更新: 2026-09-04)6 分で読める

Base64 と Base64URL の違い — RFC 4648 で押さえる3つの落とし穴

Base64 は RFC 4648(2006年)で定義される、バイナリを ASCII 印字可能文字列に変換するためのエンコーディング方式です。実務でよく使われるのは JWS / JWT、メール添付、Data URL スキームなどですが、URLやファイル名へ無加工で埋め込むと、文脈によって+/=のエスケープが必要になる点で初学者が足を取られがちです。本記事では RFC 4648 に規定される標準 Base64 と Base64URL(base64url)の違いを整理します。

#Base64#Base64URL#RFC 4648#エンコーディング

Base64 の基本原理

Base64 は「6ビット単位でバイナリを切り出し、それぞれを64種類の印字可能文字にマップする」という仕組みです。

  • 入力: 任意バイト列
  • 出力: A-Z a-z 0-9 + / の64文字 + パディング文字 =

3バイト(24ビット)を4個の6ビット値へ分け、それぞれをASCII文字に対応させます。パディングを含む標準Base64の出力長は 4 × ceil(入力バイト数 / 3) 文字で、十分に長い入力では元の約 4/3 倍です。文字列として保存・送信すると通常は1文字1バイト以上を使うため、「4文字も24ビットだから容量が同じ」という意味ではありません。入力が3の倍数でない場合、末尾に1〜2個の = を付けて4文字境界に揃えます。

// 例: "ABC" (3バイト) → "QUJD" (4文字)
// "AB"  (2バイト) → "QUI=" (パディング1個)
// "A"   (1バイト) → "QQ=="  (パディング2個)

RFC 4648 が定める2つのアルファベット

RFC 4648 は Base64 の2種類のアルファベットを規定しています。

表が収まらない場合は、左右にスクロールできます。

種別62番目63番目パディングセクション
標準 Base64+/=§4
Base64URL-_参照仕様が許可する場合に省略可§5

Base64URLは、標準Base64の +/ を、URIの非予約文字である -_ に置き換えた別のアルファベットです。+ が空白へ変換されるのはURL一般の規則ではなく、application/x-www-form-urlencodedとしてクエリを解析する場合です。/ もクエリ内で常にパス区切りになるわけではありませんが、パスセグメントやファイル名などでは構造文字と衝突します。

落とし穴1 — URL パラメータに標準 Base64 を直接入れる

標準Base64をURLへ直接連結するのは安全ではありません。どの文字が問題になるかは、クエリ、パス、フォーム符号化など埋め込み先の規則で変わります。

// application/x-www-form-urlencoded のパーサーでは "+" が空白になる可能性がある
https://example.com/api?token=AA/+AA==

// 標準Base64を使うなら、値としてパーセントエンコードする
https://example.com/api?token=AA%2F%2BAA%3D%3D

// URLSearchParamsなら値をフォーム形式に符号化できる
new URLSearchParams({ token: "AA/+AA==" }).toString();
// "token=AA%2F%2BAA%3D%3D"

埋め込み先の仕様を選べるなら、Base64URLを採用して必要なパーセントエンコードを減らすのが実用的です。ただし、Base64URLのパディングを省略するかどうかは、そのデータ形式の仕様で別途決めます。JWSの仕様(RFC 7515)は末尾の = をすべて省略したBase64URLを定義します。JWTはJWSまたはJWEの形で表現され、JWEも同じパディングなしBase64URLを定義しています。

// JavaScript での変換例
// btoa() はUnicode文字列一般ではなく、各文字が1バイトを表すbinary string向け。
// Unicodeテキストは先にTextEncoder等でUTF-8バイト列へ変換する。
// 注: 入力に "/" や "+" があっても、それらが必ず出力に現れるわけではない。
//     出力に "/" や "+" が現れるのは、6 ビット境界の値が 62 や 63 になったとき。
const std = btoa("Subjects?");              // 標準: "U3ViamVjdHM/"
const urlSafe = std.replace(/\+/g, "-")
                   .replace(/\//g, "_")
                   .replace(/=+$/, "");     // URL-safe: "U3ViamVjdHM_"

落とし穴2 — パディングの扱い

RFC 4648 §3.2 の原則は、参照する仕様が明示的に例外を定めない限り、エンコーダーは適切なパディングを付けることです。一方、§5 はデータ長が暗黙に分かる環境ではパディングを省略できると説明しています。したがって「Base64URLなら常に省略」ではなく、JWSのような参照仕様や双方の合意が必要です。

JWS / JWE / JWTでは仕様としてパディングを省略します。デコード側は長さから不足分を復元できますが、4で割った余りが1の入力は有効なBase64URLにならない点にも注意が必要です。

// パディング付き・なしの両方を受け入れ、正規形も検証する
function decodeBase64Url(value) {
  const match = /^([A-Za-z0-9_-]*)(={0,2})$/.exec(value);
  if (!match) {
    throw new Error("Invalid base64url");
  }

  const body = match[1];
  const explicitPadding = match[2];
  if (body.length % 4 === 1) {
    throw new Error("Invalid base64url length");
  }

  const requiredPadding = (4 - body.length % 4) % 4;
  if (explicitPadding.length !== 0 && explicitPadding.length !== requiredPadding) {
    throw new Error("Invalid base64url padding");
  }

  const base64 = body.replace(/-/g, "+").replace(/_/g, "/")
                     + "=".repeat(requiredPadding);
  const decoded = atob(base64); // binary string。UTF-8本文なら別途デコードする
  const canonical = btoa(decoded).replace(/\+/g, "-").replace(/\//g, "_")
                                 .replace(/=+$/, "");
  if (canonical !== body) {
    throw new Error("Non-canonical base64url");
  }
  return decoded;
}

上の例は、パディングなし入力と、必要数の = を正しく付けた入力の両方を受け入れます。復号後に再エンコードして一致を確認することで、未使用のパッドビットが0ではない非正規表現も拒否します。逆に、標準 Base64 を受け付けるデコーダにパディングなし文字列を渡すと、実装によってはエラーになります。サーバーとクライアントでパディング規則を揃えることがトラブル防止の第一歩です。

落とし穴3 — 改行挿入(MIME Base64 との混同)

歴史的経緯として、RFC 2045(MIME)はBase64の符号化出力を76文字以下の行で表し、行区切りにCRLFを使うことを定めています。

RFC 4648 は、参照仕様から明示されない限り改行を追加してはならないと定めています。一方、Pythonの base64.encodebytes() やOpenSSLの base64 コマンドは改行を含む出力を生成できます。利用するAPIが改行を許すか確認し、改行なしが必要なら生成時に抑止するのが安全です。デコーダーによって非アルファベット文字の扱いが異なるため、常に「後から除去すればよい」とは限りません。

# Python で改行なしにしたい場合
import base64
encoded = base64.b64encode(data).decode()   # 改行なし ✓(推奨 API)
# encodebytes() は MIME 流(76 文字ごとに改行)。
# encodestring() は Python 3.9 で削除済み。

# Node.js
const encoded = Buffer.from(data).toString("base64"); // 改行なし ✓

# OpenSSL: -A フラグで改行抑止
openssl base64 -A -in input.bin

RFC 4648 以外のバリアント

関連するエンコーディングとして、同 RFC は以下も定義しています。

  • Base32(§6): A-Z 2-7。数字の 01 を含まないため、OI / L との取り違えを避けやすい一方、英字の ILOS 自体はアルファベットに含まれる
  • Base32 拡張Hex(§7): 0-9 A-V。符号化済みデータを文字列として比較したとき、元データのビット列順を保てる
  • Base16(§8): 16進エンコーディング。0-9 A-F。いわゆる hex dump

十分に長い入力に対する文字数の増加率は、Base64系が約4/3、Base32が約8/5、Base16が2倍です。これらは圧縮ではなくバイナリ表現の符号化なので、利用可能な文字、可読性、データ長の増加を用途に合わせて選びます。

実務での使い分け指針

表が収まらない場合は、左右にスクロールできます。

用途推奨根拠
メール添付標準 Base64 + 76文字改行RFC 2045 MIME
HTTP Basic認証のcredentials標準Base64RFC 7617
URL パラメータ / パスBase64URL(パディングは利用仕様に従う)RFC 4648 §3.2 / §5
JWS / JWE / JWTBase64URL(パディングなし)RFC 7515 / RFC 7516 / RFC 7519
ファイル名Base64URL/ が使えないため
Data URL スキーム標準 Base64RFC 2397

Base64は暗号化ではありません。見た目が読みにくくなっても機密性はなく、誰でも復号できます。パスワード、APIキー、個人情報等を保護する手段として使わないでください。

まとめ

  • RFC 4648 は標準 Base64(§4)と Base64URL(§5)の 2種類を定義
  • Base64URLは +/-_ に置換する。パディング省略は参照仕様や合意がある場合に限る
  • + が空白になるのはURL一般ではなく、フォーム形式で解析するとき。標準Base64を埋め込むなら文脈に応じてパーセントエンコードする
  • URLやファイル名ではBase64URLが扱いやすく、JWS / JWE / JWTはパディングなしBase64URLを仕様として使う
  • MIME Base64(RFC 2045)は76文字の行制限がある。改行なしが必要なら生成時に抑止し、受信側の規則も確認する
  • パディング省略ありなしはサーバー/クライアントで合意が必要
  • Base64は暗号化ではなく、機密性を提供しない
  • Base32(§6)/ Base16(§8)も同 RFC で定義

参考文献・ソース

記事作成に関する注記

本記事は AI(大規模言語モデル)を編集補助として活用して作成しています。 公開前に編集者が内容を確認していますが、事実誤認・仕様の解釈ミス・最新情報との齟齬が含まれる可能性があります。 重要な判断を行う際は、本文中の一次ソースや公式ドキュメントを必ずご自身でご確認ください。 誤りにお気づきの場合は、お問い合わせフォームよりご連絡いただけると助かります。

🔧 関連ツール

📚 関連記事