Base64 は暗号化ではありません: 実際に何をしているのか
Base64 はバイナリをテキストに移すための転送用エンコーディングであり、セキュリティ手段ではありません。その仕組みと使いどころ、そしてこれを機密性と取り違えることがなぜ実際の事故につながるのかを整理します。
Base64 は、任意のバイナリデータを印字可能な 64 個の ASCII 文字だけで 表現する方法です。仕事はそれだけです。メール・URL・JSON・XML 属性など、 テキストを運ぶ前提で設計されたために生のバイトでつまずくシステムが多く、 そのために存在します。Base64 は RFC 4648 で定義されており、同じ文書が Base32 と Base16 も規定しています。そして機密性とはいっさい関係が ありません。Base64 にまつわる最もよくある間違いが、まさにその逆を仮定 することです。
エンコーディングの仕組み
Base64 は入力を 3 バイトずつ読みます。3 バイトは 24 ビットで、24 は 6 ビットずつ 4 グループにちょうど分かれます。各 6 ビットグループが 64 文字のアルファベットへのインデックスとなり、出力文字を 4 つ生み出し ます。つまり入力 3 バイトごとに出力 4 文字という固定の 4:3 比になります。
Input bytes: 0x4D 0x61 0x6E ("Man")
Binary: 01001101 01100001 01101110
Regrouped: 010011 010110 000101 101110
Index: 19 22 5 46
Output chars: T W F u ("TWFu")
標準のアルファベットは A–Z・a–z・0–9、そして値 62 と 63 にあたる
+ と / です。= 文字はパディング用に予約されており、データ値になる
ことは決してありません。
パディングは、長さが 3 の倍数でない入力を扱います。最後のグループが
1 バイト (8 ビット) だけなら 12 ビットに詰めて 2 文字として出力し、後ろに
== を付けます。2 バイト (16 ビット) なら 3 文字に = が 1 つ付きます。
パディングは出力長を 4 の倍数に保ち、ストリーミングデコーダが固定サイズの
ブロック単位で動作できるようにします。
代償はサイズです。3 バイトごとに 4 文字なので、約 33% 増えます。1MB の バイナリは、行折り返しのオーバーヘッドを除いても Base64 テキストで およそ 1.37MB になります。そのオーバーヘッドはテキスト専用チャネルを 通過するための代価であり、だからこそ必要のないものを Base64 にはしないの です。
実際に登場する場所
Base64 は、バイトがテキストを通って移動しなければならない場所なら どこにでもあります。
- MIME メール。 本来の動機でした。SMTP は 7 ビットプロトコルで、
添付ファイルは 8 ビットのバイナリです。
Content-Transfer-Encoding: base64は、PDF がメールリレーを無事に通過する方法です。 data:URI。 小さな画像やフォントを CSS や HTML に直接埋め込み、 別の HTTP リクエストを省きます。たとえばdata:image/png;base64,iVBORw0KGgo...のような形です。- JSON/XML 内のバイナリ。 JSON にはバイト型がありません。生のバイトを 運ぶ必要のあるフィールド、つまりサムネイル・証明書・署名などは、Base64 文字列として運びます。
- JWT セグメント。 JWT のドットで区切られた 3 つの部分は、それぞれ JSON (ヘッダーとペイロード) または生の署名バイトの Base64url です。
- HTTP Basic 認証。
Authorization: Basicヘッダーはbase64(username:password)です。注意すべきは、エンコードであって保護 ではないという点です。詳しくは後述します。
これらすべてのケースで目的は転送であって秘匿ではありません。 エンコーディングは設計上、そして誰にでも元に戻せます。
base64url バリアント
標準の + と / の文字は URL に対して敵対的です。+ は
application/x-www-form-urlencoded のクエリ文字列で空白として解釈され、
/ はパス区切り文字です。標準の Base64 を URL に入れると、さらにパーセント
エンコードし直すことになり、これはデバッグしづらいバグを生む二重
エンコーディングまさにその状況です。
これも RFC 4648 で定義された base64url は、問題の 2 文字を置き換えます。
- (マイナス) が + を、_ (アンダースコア) が / を代わりに使います。
どちらも URL セーフです。パディングは通常まったく省略されます。デコーダが
入力を 4 で割った余りから長さを復元でき、= 自体も一部の文脈では
エスケープが必要だからです。
だから JWT は base64url を使うのです。トークンは URL・ヘッダー・クッキーを
通って移動するように作られています。JWT をデコードして、+ や / が
あるべきところに - や _ が見えたら、それは base64url を見ているので
あり、標準アルファベットに設定されたデコーダはそれを拒否します。
Base64 は暗号化ではありません
ここが肝心な部分です。Base64 は機密性も、完全性も、認証も提供しません。
鍵を使いません。エンコードされた文字列を持つ人は誰でも、関数呼び出し
1 回と秘密の材料ゼロで元のバイトをそのまま復元できます。
echo dG9wLXNlY3JldA== | base64 -d は、地球上のどのマシンでも
top-secret を出力します。
Base64 を「安全のためのエンコード」と呼んだり、資格情報を「平文ではない ように」Base64 で送り出したりするのは明白なセキュリティ上の欠陥であり、 実際のコードレビューや侵害の事後分析に繰り返し登場します。HTTP Basic 認証が典型例です。パスワードが Base64 であり、だからこそ Basic 認証は TLS の上でのみ許容されます。機密性は TLS 層が提供し、Base64 はまったく 提供しません。TLS を取り除けば、資格情報は通信路上でそのまま読めます。
区別は明確です。
| 属性 | Base64 | 暗号化 | ハッシュ |
|---|---|---|---|
| 元に戻せるか | はい、簡単に | はい、鍵があれば | いいえ |
| 鍵が必要か | いいえ | はい | いいえ |
| 機密性を提供するか | いいえ | はい | 該当なし |
| 目的 | 転送 | 秘匿 | 指紋 / 検証 |
機密性が必要なら、実際に秘密として守る鍵を使う暗号が必要です。AES-GCM、 ChaCha20-Poly1305、あるいは KMS の envelope です。その暗号文も依然として 生のバイトなので、テキストチャネルを通すために後から Base64 をかけること はあります。2 つの操作はその順序で積み重なり、一方が他方の代わりになる ことはありません。どのツールが何をするかというより広い地図は身につけて おく価値があります。ハッシュ・暗号化・エンコーディング の違いは、この領域で最も一貫して混同される話題の 1 つです。
Base64 はどんな意味でも難読化でもありません。攻撃者を遅らせません。 見分けてデコードするのは自動です。Base64 で「隠した」秘密は、誰のツール でも無償で行う 2 ステップが付いただけの平文の秘密にすぎません。
いつ使い、いつ使わないか
バイナリデータがテキストしか許容しないチャネルを通過しなければならず、 約 33% のサイズコストを測定したか受け入れたなら、Base64 を使ってください。
data:URI に小さなアセットを埋め込み、リクエストを 1 つ減らすとき。- バイト (証明書・署名・blob) を JSON や XML のフィールドに入れるとき。
- URL やヘッダー用の値をエンコードするとき。そこでは標準アルファベットでは なく base64url を使ってください。
避けるべきとき。
- チャネルがすでにバイナリを扱えるとき。
multipart/form-dataで POST したり、 バイナリプロトコルでストリーミングしたりするファイルを Base64 にしないで ください。33% を無駄に払うことになります。 - アセットが大きいとき。数 MB の画像を
data:URI でインライン化すると HTML が膨らみ、個別にキャッシュされず、パーサをブロックします。通常の リソースとして配信してください。 - 何かを保護しようとしているとき。代わりに暗号を使ってください。
テキストを URL のパスやクエリ文字列に安全に入れるという関連する問題、 つまりアルファベットもエスケープ規則も異なるもう 1 つの転送エンコーディング については、URL パーセントエンコーディング を参照してください。URL という特定の文脈で似た転送問題を解きます。
JWT ペイロードを覗いたり、data: URI をデコードしたり、Basic 認証ヘッダー
に実際に何が入っているかを確認したりと、値を手作業でエンコード・デコード
する必要があるとき、私たちの Base64 エンコーダ/デコーダ は
標準と base64url の両方のアルファベットをブラウザ内で処理し、サーバーには
何も送りません。バイトをテキストとして移すのに適したツールです。そして、
バイトを秘密に保つための手段ではありませんし、そもそもそのために作られた
ものでもありません。