Base64エンコードはなぜ4/3に膨らむのか、URL safe版との違い
Base64 は触る機会が多いわりに、「なぜサイズが膨らむのか」「+ / を - _ に置き換える派生は何のためか」を都度確認したくなる技術です。仕様を理解しておくとデバッグや実装で迷わなくなるので、本記事では Base64 の中身を整理します。
そもそも Base64 が必要な理由
ネットワークやテキストプロトコル(メール、HTTP ヘッダ、JSON、URL)は、もともとバイナリデータをそのまま運ぶことを想定していないものが多くあります。例えば:
- メール(SMTP)は7-bit ASCII を前提にしていて、8-bit のバイナリはそのままでは送れない
- JSON 文字列に NULL バイトや改行を含めると、パースで壊れる
- URL に
?や&を含むデータをそのまま入れると、クエリパーサが誤動作する
これを回避するために、任意のバイナリを ASCII の安全な文字だけで表現する符号化が必要で、その代表格が Base64 です。仕様は RFC 4648 にまとまっています。
エンコードの基本:3バイト → 4文字
Base64 のアイデアは単純で、入力の 3 バイト(24 bit)を 6 bit ずつ4つに区切り、各 6 bit を1文字で表現するというものです。
入力: [byte0 ][byte1 ][byte2 ]
8 + 8 + 8 = 24 bit
出力: [c0 ][c1 ][c2 ][c3 ]
6 + 6 + 6 + 6 = 24 bit 6 bit が表現できる値は 2^6 = 64 通りで、これに A-Z a-z 0-9 + / の 64 文字を割り当てています(だから “Base64”)。
例:Man という3バイトの ASCII 文字列をエンコードすると:
'M' = 0x4D = 01001101
'a' = 0x61 = 01100001
'n' = 0x6E = 01101110
ビット連結: 010011010110000101101110
6bit分割: 010011 010110 000101 101110
= 19, 22, 5, 46
インデックス: T, W, F, u
→ "TWFu" 24 bit を 4 つの 6 bit に再分割して、テーブル引きするだけです。デコードは逆を行うだけなので対称的です。
なぜ 4/3 に膨らむのか
ここで気付くのは、入力の 3 バイト(24 bit)を出力の 4 文字(4 × 8 bit = 32 bit)で表現している点です。出力1文字は ASCII で 1 バイト(8 bit)必要で、6 bit のデータを 8 bit のスロットに入れるため、2 bit 分が無駄になります。
結果として、サイズの比率は:
出力サイズ / 入力サイズ = 32 / 24 = 4/3 ≈ 1.333 つまり Base64 は約33%サイズが増える符号化です。これは仕様上避けられないオーバーヘッドで、画像を data URI 形式で HTML に埋め込むと元の画像より大きくなるのはこのためです。
余りバイトの処理:パディング =
入力のバイト数が3の倍数とは限りません。残り1バイト・2バイトのケースをどう扱うかが、Base64 の地味にややこしい部分です。
| 入力余り | 処理 | 出力 |
|---|---|---|
| 1 バイト(8 bit) | 6 bit + 2 bit を 8 bit にゼロ埋め → 2文字 + == | 4 文字(うち 2 文字は =) |
| 2 バイト(16 bit) | 6 bit + 6 bit + 4 bit を 6 bit にゼロ埋め → 3文字 + = | 4 文字(うち 1 文字は =) |
| 3 バイト(24 bit) | パディング不要 | 4 文字 |
= は「パディング文字(このスロットは無効)」を意味するもので、デコード時に末尾の = の数を見て元のバイト数を復元できます。
実装では = を省略する Base64 もあり(“unpadded Base64”)、デコーダは入力長から余りを推測するか、エラーにするかを決める必要があります。後述する Base64URL ではパディングを省略するのが一般的です。
URL safe 版(Base64URL)の存在理由
標準の Base64 アルファベットには + と / が含まれますが、これは URL にそのまま入れるとマズいです。
+は URL のクエリ文字列でスペースを意味する(application/x-www-form-urlencoded)/はパスの区切り文字=はkey=valueの区切り文字
これらをそのまま URL に入れると、解釈が壊れたり別の意味になったりするので、URL に乗せる前にパーセントエンコードが必要になります。それを避けたいケースのために、Base64URL という派生規格が用意されています(RFC 4648 §5)。
| 標準 Base64 | Base64URL |
|---|---|
+ | - |
/ | _ |
= パディングあり | = パディング省略が普通 |
JWT のヘッダ・ペイロードは Base64URL でエンコードされていて、- と _ を見たら「これは URL safe 版だな」と判断できます。
デコード実装のときの注意
Base64URL を標準の atob()(ブラウザ)や Buffer.from(str, 'base64')(Node.js)でデコードする場合、事前に文字を戻す必要があります。
function fromBase64Url(s) {
// -, _ を +, / に戻す
let b = s.replace(/-/g, '+').replace(/_/g, '/');
// 4の倍数になるよう = を付け足す
while (b.length % 4) b += '=';
return atob(b);
} 逆に標準 Base64 を Base64URL に変換するなら、+ → -、/ → _、末尾の = を削除、の3ステップです。
エンコード != 暗号化
Base64 にまつわる一番大きな誤解は「Base64 でエンコードすればちょっと安全」というものです。Base64 は変換であって暗号ではありません。テーブル引きでデコードできるので、誰でも元のバイト列を復元できます。
ログに API キーを Base64 でエンコードして出すのは、平文で出すのと同じ危険度です。データを隠したいなら暗号化、識別子として使いたいだけならハッシュ、という使い分けを意識する必要があります。
ちょっとした文字列を試しにエンコード・デコードしたいときは、本サイトの Base64 ツールが手早く確認できます。すべてブラウザ内で処理されるので、機密データを試す場面でも外部に送られない点で安心して使えます。