Base64 とは何か:エンコードのしくみとよくある誤解
Base64 が3バイトを4文字に変える方法、結果が約33%大きくなる理由、末尾の = の意味、URL-safe Base64、日本語を扱うときの注意点を RFC 4648 をもとに整理しました。
メールの添付ファイル、ページに埋め込まれた data:image/png;base64,iVBOR… の画像、ログイントークン(JWT)の真ん中の部分。どれも Base64 で書かれたデータです。英数字が長く続くこの文字列は暗号ではなく、バイナリデータを文字として書き写す方法です。
なぜ必要なのか
メール(SMTP)や JSON、HTML、URL は、もともと文字をやり取りするために作られています。画像や圧縮ファイルのようなバイナリデータには、改行や制御文字、引用符として解釈されるバイトが混じっているため、そのまま入れると途中で壊れたり、文法が崩れたりします。Base64 はどんなバイトでもどこでも安全な64種類の文字だけに置き換えて、この問題を避けます。
3バイトを4文字に
規則は RFC 4648 で決められています。
- 入力を 3バイト(24ビット)ずつ区切ります。
- 24ビットを 6ビットずつ4つに分けます。6ビットは 0〜63 の値です。
- それぞれの値を表の文字に置き換えます。
| 値 | 文字 |
|---|---|
| 0–25 | A–Z |
| 26–51 | a–z |
| 52–61 | 0–9 |
| 62 | + |
| 63 | / |
「Man」を例にすると次のようになります。
M a n
01001101 01100001 01101110 ← 3バイト
010011 010110 000101 101110 ← 6ビットずつ4つ
19 22 5 46
T W F u → "TWFu"
末尾の = は何か
入力が3バイトで割り切れないと、最後の区切りが足りなくなります。足りないビットは0で埋め、作られなかった文字の位置は = で埋めて、結果の長さを常に4の倍数にそろえます。
| 入力 | 結果 |
|---|---|
Man(3バイト) |
TWFu |
Ma(2バイト) |
TWE= |
M(1バイト) |
TQ== |
そのため = は末尾に最大2つまでしか付きません。デコーダーはこれを見て、元のデータが何バイトだったかを判断します。
サイズは約33%大きくなる
3バイトが4文字になるので、結果は元の 4/3倍です。nバイトをエンコードすると 4 × ⌈n ÷ 3⌉ 文字になります。300KB の画像を Base64 で埋め込むと約 400KB になります。さらにメール(MIME、RFC 2045)では1行を76文字以下で折り返すため、改行の分だけ増えます。
小さなアイコンを data: URI で埋め込んでリクエスト数を減らすのは有効ですが、大きな画像は別ファイルにしたほうがキャッシュも効き、転送量も少なくなります。
Base64 は暗号化ではない
最もよくある誤解です。Base64 には鍵がありません。同じ表を使えば誰でも元に戻せます。パスワードや個人情報を Base64 で「隠す」ことは、何も守っていません。JWT の真ん中の部分(ペイロード)も Base64URL でエンコードされているだけなので、誰でも読めます。JWT が保証するのは署名による改ざん防止であって、内容の秘密ではありません。
URL-safe Base64(Base64URL)
標準の Base64 の + と / は、URL の中で特別な意味を持ちます(/ はパスの区切り、+ はフォームデータで空白)。そこで RFC 4648 は、2文字だけを変えた Base64URL を別に定めています。
| 値 | 標準 Base64 | Base64URL |
|---|---|---|
| 62 | + |
- |
| 63 | / |
_ |
Base64URL は末尾の = パディングも省略することが多く、JWT(RFC 7515)がその代表です。デコード時に「不正な文字」エラーが出たら、受け取った値がどちらの形式かをまず確認します。
日本語を Base64 にするとき
Base64 が扱うのはバイトです。文字を入れるには先にバイトに変える必要があり、通常は UTF-8 を使います。「日」は UTF-8 で E6 97 A5 の3バイトで、Base64 では 5pel になります。
JavaScript の btoa() は1文字を1バイトとして扱うため、U+00FF より大きい文字(日本語、韓国語、絵文字)を入れると InvalidCharacterError になります。先に UTF-8 のバイトに変えてからエンコードします。
const bytes = new TextEncoder().encode("日本");
const b64 = btoa(String.fromCharCode(...bytes)); // "5pel5pys"
デコードするときは、逆にバイトを UTF-8 として解釈します。Shift_JIS など別のエンコーディングの文字列から作られた Base64 を UTF-8 として戻すと、文字化けします。
実際に試す
Base64 エンコード・デコードツールは、日本語・韓国語・絵文字を UTF-8 で処理し、URL-safe 形式にも対応しています。入力はブラウザの中だけで変換され、サーバーには送られません。「Man」「Ma」「M」を順に入れると、= が付く規則をその場で確かめられます。
参考
- RFC 4648, The Base16, Base32, and Base64 Data Encodings
- RFC 2045, MIME Part One(6.8 Base64 Content-Transfer-Encoding)
- RFC 7515, JSON Web Signature (JWS)