本文へスキップ
LeedyBox
支援する

Base64・URL エンコード・JWT の解説

データをテキストとして送る場面、つまり URL、HTTP ヘッダー、JSON、ログイントークンには、この 3 つがいつも登場します。しかし、どれもデータを隠すものではありません。このガイドでは、それぞれの仕組み、出会う場所、そして多くのトラブルの原因になる間違いを説明します。

エンコードは暗号化ではない

エンコードは、特定の文字しか通さない経路をデータが通れるように、書き方を変えることです。鍵を使わないので、誰でも元に戻せます。暗号化もデータを変えますが、元に戻せるのは鍵を持つ人だけです。ハッシュはさらに別物で、元に戻せない一方向の指紋です。詳しくはハッシュ関数の解説をご覧ください。

Base64 と URL エンコードはエンコードです。JWT は通常署名されていて、誰が発行したかと内容が変更されていないことを証明しますが、内容そのものはエンコードされているだけです。値を秘密にする必要があるなら、これらの形式ではなく暗号化が必要です。

Base64 の仕組み

Base64 はデータを 3 バイト(24 ビット)ずつ読み、6 ビットずつ 4 つのグループに分けます。各グループは 0〜63 の数で、64 文字のアルファベットの 1 文字として書きます。0〜25 は A〜Z、26〜51 は a〜z、52〜61 は 0〜9、その後が + と / です。「Man」という単語で手順を見てみましょう。

手順Man
バイト値7797110
ビット010011010110000101101110
6 ビットのグループ 4 つ010011 · 010110 · 000101 · 101110 = 19 · 22 · 5 · 46
Base64T W F u → TWFu

入力の長さが 3 の倍数でないときは、最後のグループを埋めてパディングで示します。1 バイト余れば 2 文字と ==、2 バイト余れば 3 文字と = になります。「Ma」は TWE=、「M」は TQ== です。

3 バイトが 4 文字になるため、Base64 はデータを約 3 分の 1 大きくし、形式によっては改行も加わります。メールに添付した 3 MB の写真は約 4 MB のテキストとして送られ、スタイルシートにデータ URL で埋め込んだ画像は元のファイルより約 3 分の 1 重くなります。

Base64 に出会う場所

  • メールの添付ファイル(MIME)は、1 行 76 文字で折り返した Base64 テキストです。
  • データ URL は data:image/png;base64,iVBORw0… のように、小さなファイルを HTML や CSS に直接埋め込みます。
  • HTTP 基本認証は user:password を Base64 にして Authorization ヘッダーで送ります。パスワードを一見して読めなくするだけなので、安全なのは HTTPS 上だけです。
  • 証明書と鍵の PEM ファイルは、-----BEGIN …----- と -----END …----- の行の間にある、64 文字で折り返した Base64 です。
  • JSON と XML にはバイナリ型がないため、署名やサムネイルなどのバイナリ値を Base64 文字列で持ちます。

Base64URL とパディング

標準 Base64 の + と / は URL やファイルパスで意味を持つため、2 つ目のアルファベットである Base64URL は代わりに - と _ を使います。デコーダーが長さを計算できるので、= のパディングも通常は省きます。JWT、多くの API トークン、一部のファイル名が Base64URL を使います。標準 Base64 を Base64URL にするには、+ を -、/ を _ に置き換え、末尾の = を消します。元に戻すときは置き換えを逆にし、長さが 4 の倍数になるまで = を付け足します。

URL のパーセントエンコード

URL に使える文字は限られていて、そのうちいくつかはアドレスの構造を表します。/ と ? はパスとクエリを分け、& と = はパラメーターを区切り、# はフラグメントの始まりです。値にこれらの文字や ASCII 以外の文字が含まれる場合はパーセントエンコードします。値を UTF-8 にした各バイトを、% の後に 16 進数 2 桁で書く方法です。é は C3 A9 の 2 バイトなので、「café」は caf%C3%A9 になります。

英字、数字、そして - . _ ~ はエンコードの必要がありません。トラブルの原因になりやすい文字は次のとおりです。

文字エンコード後問題になる理由
スペース%20URL に使えない
&%26新しいパラメーターが始まる
=%3Dパラメーター名が終わる
?%3Fクエリが始まる
#%23フラグメントが始まり、その部分はサーバーに送られない
/%2Fパスの階層が増える
+%2Bフォームデータではスペースとして読まれる
%%25エスケープシーケンスが始まる

値を 1 つずつエンコードしてから URL を組み立てましょう。JavaScript の encodeURIComponent は値 1 つ用で、/ ? & = # もエンコードします。一方 encodeURI はアドレス全体用なので、これらの文字をそのまま残します。値 1 つに encodeURI を使うのは、クエリ文字列が壊れるよくある原因です。

プラス記号、スペース、二重エンコード

HTML フォームはフィールドを application/x-www-form-urlencoded 形式で送り、スペースは + になります。一般的な URL の構文では、+ はただのプラス記号です。そのため「C++」の検索を q=C++ と送ると、フォームデータをデコードするサーバーには「C」の後にスペース 2 つが付いた値として届きます。正しくは q=C%2B%2B と送ります。クエリ文字列を手でデコードするときは、どの規則で作られたかを先に確かめてください。

二重エンコードは、すでにエンコードされた値をもう一度エンコードすると起こります。% 自体が %25 になるので、%20 は %2520 になります。URL に %25 の後に 16 進数 2 桁が続く部分があれば、処理のどこかで 2 回エンコードしています。一度デコードして、余分な手順がどこにあるかを探してください。

JWT の中身

JSON Web Token(JWT)は、ユーザーが誰か、トークンがいつまで有効かといったクレームを、発行するシステムから受け入れるシステムへ運びます。よく使われる署名付きの形式は、ドットで区切られた Base64URL の 3 つの部分、つまりヘッダー、ペイロード、署名からなります。前の 2 つをデコードすると普通の JSON です。

Header:  {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "user-42", "role": "editor", "iat": 1767225600, "exp": 1767229200}

標準(RFC 7519)はいくつかの登録クレームを定めており、アプリケーションは上の role のように独自のクレームを追加します。

クレーム意味
iss発行者:トークンを作った側
sub主体:通常はユーザーやアカウントの ID
aud対象:トークンを使うサービス
exp, nbf, iat有効期限、有効開始、発行時刻:秒単位の Unix 時間
jtiトークン ID:再利用の検出や 1 つのトークンの失効に使う

ペイロードはエンコードされているだけなので、トークンを持つ人なら誰でも読めます。個人情報や秘密は入れず、トークンは小さく保ちましょう。トークンはリクエストのたびに送られ、多くのサーバーはリクエストヘッダーのサイズを制限しています。

署名は秘密ではない:JWT の検証方法

署名はヘッダーとペイロードをもとに計算します。HS256 では、発行者と受信者が 1 つの秘密鍵を共有し、SHA-256 による HMAC を使います。RS256 や ES256 では、発行者が秘密鍵で署名し、受信者は対になる公開鍵で確認するため、受信者が自分でトークンを作ることはできません。

トークンを受け取るサーバーは、どのクレームを信用するにも、その前に想定したアルゴリズムと鍵で署名を検証し、exp、nbf、iss、aud を確認する必要があります。LeedyBox のツールのようにデコードするだけでは、これらをすべて省いています。トークンに何が書かれているかはわかりますが、本物かどうかはわかりません。ライブラリは、署名なしのトークンを表す「none」アルゴリズムも拒否しなければなりません。署名なしのトークンを使うと決めている場合を除きます。内容そのものを隠す必要があるときはトークンを暗号化(JWE)し、その場合トークンは 5 つの部分からなり、鍵がなければ読めません。

よくある問題と対処法

症状考えられる原因対処法
Base64 をデコードすると文字化けするバイナリデータ、または別のエンコードのテキスト何をエンコードしたかを確認し、バイナリデータはファイルとして保存する
デコード時に「不正な文字」エラーdata: の接頭辞、引用符、または Base64URL のアルファベットカンマより前をすべて消し、- と _ を受け付けるデコーダーを使う
パラメーターが途中で切れる値にエンコードされていない & や #値ごとに encodeURIComponent でエンコードする
URL に %2520 がある値を 2 回エンコードしたURL を組み立てる箇所で 1 回だけエンコードする
exp が未来なのにトークンが拒否される対象や発行者の不一致、時計のずれ、鍵の不一致aud、iss、署名鍵をサーバーが期待する値と比べる
有効期限が数千年先になるexp を秒ではなくミリ秒で書いたexp、nbf、iat を秒単位で保存する

よくある質問

Base64 でパスワードや API キーを守れますか?

いいえ。Base64 は誰でも一瞬で元に戻せます。設定ファイルや URL に Base64 で入っている認証情報は、平文と同じくらい丸見えです。

JWT のペイロードに個人情報を入れてもよいですか?

避けましょう。ブラウザー拡張機能、ログ、リクエストを見るプロキシなど、トークンを手にした側は誰でもペイロードを読めます。トークンには ID だけを入れ、詳しい情報はサーバーで参照してください。

URL エンコードはインジェクション攻撃を防ぎますか?

値が URL の構造を壊せないという意味でのみ有効です。サーバーは、デコードした値を SQL、HTML、シェルコマンドなど実際に使う場所に合わせて検証し、エスケープする必要があります。

Base64 の文字列が元のファイルより長いのはなぜですか?

3 バイトが 4 文字になるため、テキストは約 33% 大きくなり、改行がさらに少し加わります。大きなファイルは Base64 にするより、バイトのまま送るほうが効率的です。