Base64 とは実際のところ何なのか
Base64 は、任意のバイト列を安全な 64 文字だけで書き表す方法です。考え方はそれだけです。暗号化でも、圧縮でも、チェックサムでもありません。狭い問題をひとつ解きます。バイナリデータがあり、通さなければならない経路はテキストしか受け付けない、という問題です。
その経路はメールの本文かもしれませんし、JSON のフィールド、URL、HTTP ヘッダー、YAML の設定ファイル、PEM ファイルかもしれません。生のバイト列を送れば、途中の何かが必ず壊します。ヌルバイトが文字列を切り、0x0Dが改行変換で書き換えられ、上位バイトが別の文字セットで読み替えられます。Base64 は英数字と 2 つの記号だけを使うことでこれをすべて回避します。どれもテキストとして扱われても生き残る文字です。
代償はサイズです。出力は入力より約 3 分の 1 大きくなります。それが取引であり、ほぼ常に見合っています。代わりになるのは、壊れて届くデータだからです。
3 バイトが 4 文字になる仕組み
Base64 は 3 バイトずつのまとまりで処理します。3 バイトは 24 ビットで、24 は 6 ビット × 4 にきれいに割り切れます。各 6 ビットが 64 文字の表の位置を指すので、3 バイト入れると 4 文字出てきます。
Manの 3 バイトで見てみます。
M = 77 = 01001101, a = 97 = 01100001, n = 110 = 01101110
ビットをつなげ、6 ビットずつに切り直します。
010011 010110 000101 101110 → 19, 22, 5, 46 → T, W, F, u
つまりManはTWFuになります。4 文字、パディングは不要です。3 バイトがちょうど割り切れたからです。この「ちょうど割り切れた」が、次の節の主題です。
64 文字の内訳
0–25 がA–Z、26–51 がa–z、52–61 が0–9、そして 62 と 63 が、あらゆる面倒の元になる 2 文字です。標準の Base64 ではこれが+と/です。
文字セットと、なぜ 2 種類あるのか
+と/は、メール本文では何の問題もなく、URL の中では明確に有害です。/はパスの区切りで、+はクエリー文字列では空白を意味します。つまり URL に貼った Base64 の値は、バイト列が変わったまま、どこにもエラーを出さずに戻ってくることがあります。
そこでbase64urlです。最初の 62 文字をそのまま使い、最後の 2 文字だけを入れ替えます。+が-に、/が_になります。JSON Web Token が使うのはこの形式で、JWT を手で読んだことがあれば base64url を読んだことになります。URL やファイル名を通るものはすべてこちらです。
2 つの形式はそれ以外は同一で、まさにそれが混用を危険にします。+、/、-、_をまったく含まない文字列は両方で妥当で、どちらで解釈しても同じ結果になります。含む文字列は、デコーダーが想定した形式によって別のバイト列になり、しかもたいていエラーは出ません。当サイトのBase64 エンコーダー・デコーダーが検出した文字セットを表示するのは、まさにこのためです。
パディングと、デコーダーごとに扱いが違う理由
入力が 3 の倍数バイトであることはまれです。1 バイト余れば 2 文字、2 バイト余れば 3 文字になり、標準の Base64 は=でそのまとまりを 4 文字に埋め戻します。
- 3 バイト → 4 文字、パディングなし
- 2 バイト → 3 文字 +
= - 1 バイト → 2 文字 +
==
パディングはデータを運びません。連結されたストリームを読むデコーダーが、ひとつの値の終わりを知るために存在します。単独の文字列をデコードするだけなら、まったく必要ありません。base64url が通常これを落とすのはそのためで、=はそもそも URL では扱いにくい文字でもあります。
実務での問題
パディングを必須とし、ないものを即座に拒否するデコーダーがあります。どちらも受け付けるものもあります。ある言語では通るトークンが、別の言語では素っ気ない「invalid base64」で落ちる最大の単独要因がこれです。直し方はほぼ常にパディングを戻すこと、つまり長さが 4 の倍数になるまで=を足すことです。パディングに厳しいデコーダーは壊れていませんし、それを省いたエンコーダーも壊れていません。同じ仕様の別々の半分に従っているだけです。
Base64 は暗号化ではありません
これは独立した節に値します。よくある、そして高くつく誤解だからです。Base64 に鍵はありません。何も隠しません。元に戻すのは関数呼び出し一回で、どの言語にもあり、このようなページでもできます。
典型例は HTTP Basic 認証です。Authorization: Basicヘッダーの中身は、ユーザー名:パスワードを Base64 にしただけのものです。見た目が不透明で、そこが危ないところです。デコードしたことがない人には「暗号化されている」ように読めますが、実際にはリクエストを見られる人は誰でも資格情報を読めます。Basic 認証が HTTPS 上でのみ許容されるのはこのためで、秘密を守っているのは通信路であって、エンコードは何も守っていません。
同じことが JWT のペイロードにも、設定ファイルの中の Base64 の塊にも、コミット前に「難読化」した API キーにも当てはまります。攻撃者とあなたの秘密の間にあるのが Base64 だけなら、攻撃者とあなたの秘密の間には何もありません。JSON Web Token のデコードとデバッグのガイドでは、トークンに即してさらに踏み込んでいます。
Base64 に実際に出会う場所
形を覚えてしまうと、あちこちで見かけます。
- メールの添付。MIME は 90 年代初頭からこの方法でバイナリを運んでいます。1 行 76 文字で折り返されます。
- data: URI。CSS や HTML に
data:image/png;base64,…として埋め込む小さな画像です。HTTP リクエスト 1 回を、3 分の 1 増のバイト数と引き換えにします。 - PEM ファイル。
-----BEGIN…に挟まれた証明書や鍵は、その下にある DER を Base64 にしたもので、64 文字で折り返されます。 - JWT。ヘッダーとペイロードは base64url の JSON、署名は生のバイト列の base64url です。
- Basic 認証と一部の API 資格情報。上記のとおりで、同じ注意が当てはまります。
- JSON の中のバイナリ。JSON にバイト型はないので、バイト列は Base64 の文字列として運ばれます。
一部の Base64 に改行が入っている理由
MIME は 76 文字、PEM は 64 文字で折り返します。どちらの形式も、長い行が当たり前になる前からあるためです。この改行はデータの一部ではなく、デコーダーは無視しなければなりません。ほとんどは無視しますが、しないものもあり、折り返された PEM の本文を厳格なデコーダーに貼ると、改行を取り除くまで失敗することがあります。
サイズの代償と、それが問題になるとき
3 バイトあたり 4 文字は 4/3 倍、つまり約 33 % の膨張で、パディングと改行はまだ数えていません。設定ファイルに一度書く値なら無関係です。しかしホットパス上のものなら話は別です。
10 KB の画像を data: URI として埋め込むと HTML がおよそ 13.3 KB 増え、しかも本物の画像と違って個別にキャッシュも遅延読み込みもできません。API 呼び出しごとに送られるトークンは、その増分をリクエストごとに運びます。そして圧縮は助けになりません。Base64 の出力は元のバイト列よりはるかに圧縮が効きません。エンコードによって、圧縮器が探すバイト単位のパターンが壊れてしまうからです。先に圧縮し、それからエンコードする。逆は決してしないでください。
テキストをエンコードするとは、バイト列をエンコードすること
Base64 がエンコードするのはバイト列です。文字という概念を持たないので、「この文字列を Base64 にする」は常に「この文字列をバイト列にし、そのバイト列をエンコードする」の省略形です。その中間の段階が崩れるところで、文字エンコーディングが必要になるのに、仕様はそれを決めてくれません。
実務上の答えは UTF-8 で、最近の API は既定でそうしています。しかし一方が UTF-16 や Latin-1 でテキストをエンコードし、他方が UTF-8 としてデコードすれば、Base64 は完全に妥当なまま、出てくるテキストは文字化けします。見分け方は、非 ASCII の文字がひどく壊れるのに素の ASCII は無事である点です。ASCII は UTF-8 の部分集合だからです。
往復テスト
処理の流れがテキストを正しく扱えているか不安なときは、濁点付きの文字と非ラテン文字を含む文字列をエンコードし、デコードし直して比べてください。Joséや絵文字が壊れるものは、Base64 ではなくバイト列への変換のどこかで情報を失っています。
デコードがうまくいかないとき
ほぼすべての場合、次の 4 つの切り分けで足ります。
- 「不正な文字」。文字セットにないものが混ざっています。多いのは紛れ込んだ空白、文字列と一緒にコピーされた引用符、先頭に残った
data:image/png;base64,です。 - 「不正な長さ」。空白を取り除いた長さを 4 で割った余りが 1 になっています。妥当な Base64 では決して生じない形で、途中で 1 文字失われています。
- デコードはできるが文字化けする。文字セットが食い違っているか(base64url を標準として解釈した、またはその逆)、前節のとおりテキストエンコーディングが食い違っています。
- テキストではないバイト列になる。これはエラーではありません。画像や鍵、直列化された構造を収めた Base64 は多くあります。よいデコーダーは、結果を文字列だと言い張らずに 16 進表示を見せてくれます。
この順に、文字、長さ、文字セット、エンコーディングと見ていけば、ほぼ解決します。そしてどこでもデコードできない値の最も確からしい説明は、いちばん退屈なものです。途中で切れていた、という説明です。Base64 には整合性の検査がないため、末尾を失った文字列は、最初から末尾がなかった文字列とまったく同じに見えます。
関連ツールとガイド:
- Base64 エンコーダー・デコーダー — テキストを Base64 に変換したり元に戻したり。URL セーフ出力と UTF-8 に対応。
- JWT デコーダー — JSON Web Token をデコードして、ヘッダー・ペイロード・クレーム・有効期限を読み取ります。
- JWT 解説: JSON Web Token のデコードとデバッグ — JSON Web Token の中身、安全なデコードの手順、401 を招くクレーム、そしてデコードが検証とはまったく別物である理由を解説します
よくある質問
Base64 は何に使うのですか。⌄
Base64 は、テキストしか受け付けない経路にバイナリデータを通します。メールの添付、CSS や HTML の data: URI、PEM ファイルの証明書と鍵、JSON の中のバイナリフィールド、Authorization: Basic のような HTTP ヘッダーなどです。生のバイト列なら壊れるか拒否される場所でも、Base64 なら元のまま通ります。
Base64 は暗号化ですか、エンコードですか。⌄
エンコードです。鍵はなく、秘密も守られません。デコードはどのブラウザーでもできる関数呼び出し一回です。Base64 はバイト列を運べるようにするだけで、読まれないようにはしません。パスワードやトークン、API キーを守る目的で使ってはいけません。
Base64 の文字列が = や == で終わるのはなぜですか。⌄
それがパディングです。Base64 は 3 バイトずつ処理するため、入力が割り切れないときは最後のまとまりを = 1 つか 2 つで 4 文字に埋めます。パディングはデータを運びません。base64url は通常これを省き、厳格なデコーダーの一部は逆に必須とします。ある言語ではデコードできて別の言語ではできない、という症状のよくある原因です。
Base64 と base64url の違いは何ですか。⌄
2 文字とパディングだけです。標準の Base64 は文字セットの最後 2 つに + と / を使い、base64url は代わりに - と _ を使います。+ と / は URL で特別な意味を持つためで、= のパディングも通常は省きます。JWT は base64url です。一方を他方として解釈すると、警告もなく別のバイト列になります。
Base64 にするとデータはどれくらい大きくなりますか。⌄
約 33 % です。3 バイトごとに 4 文字になり、さらにパディングと改行が加わります。サイズが問題になるなら、エンコードの後ではなく前に圧縮してください。エンコードは圧縮器が頼りにするバイト単位のパターンを壊すので、Base64 の出力はうまく圧縮できません。
Web 開発とセキュリティに 15 年以上携わってきた実務経験を持つ。実践的な Web セキュリティ、つまりサイトを安全・確実に保ち、トラブルを素早く解決できるようにすることに注力。BuildToolForge は、こうしたチェックを無料で、プライベートに、わかりやすく行えるようにするためのサイトです。ツールは可能な限りブラウザ内で動作し、ツールに入力した内容は保存されません。
ガイドの書き方: すべてのガイドは実務経験をもとに執筆し、公式の標準と照らし合わせ、実際のドメインで自社ツールを使って検証しています。標準やツールが変わればガイドも更新します。誤りを見つけたら contact@buildtoolforge.com までお知らせください。