レッスン 2
Base64 エンコードの仕組み
ビット、6 ビットグループ、64 文字のアルファベット。
Base64 がエレガントなのは、ビット、2 の累乗、ルックアップテーブル(アルファベット)という既知の概念を再利用しているからです。辞書的な文章変換や圧縮ステップはありません — 決定論的なビットの並べ替えと文字置換だけです。
バイトからビットストリームへ
1 バイトは 8 ビットです。3 バイトで 24 ビット:
Byte1: ########
Byte2: ########
Byte3: ######## → 順に 24 ビットを連結
Base64 は 24 ビットを 4 つの 6 ビット組(セクステット)に再グループ化します。
[######][######][######][######]
^ ^ ^ ^
idx 0 idx 1 idx 2 idx 3
各 6 ビット値は 0–63 の範囲で、64 記号の 1 つをインデックスします。このインデックス数が文字通り 「base 64」 の由来です: 数字 0–63 の代わりに印字可能な ASCII 文字で base 64 の数を書いています。
標準アルファベット(RFC 形式)
従来の Base64 は次のように割り当てます。
0–25 → A–Z
26–51 → a–z
52–61 → 0–9
62 → '+'
63 → '/'
デコードは逆マップ: 記号 → 6 ビット → 元の 8 ビットグループを再構成。
手計算のミニ例(2 バイト → パディングの直感付き 4 文字)
日常で手計算する必要は稀ですが、直感を固めます。ASCII "Hi" を取ると:
H=0x48(01001000)i=0x69(01101001)
16 ビットを連結: 0100100001101001。
6 で割り切れる長さになるまで右にゼロをパディング(パディングのレッスンで詳述):
010010 | 000110 | 100100 |
18 | 6 | 36 |
4 番目のセクステットはさらに入力が来るまで不完全。シリアライザーは = プレースホルダーを出力します(次のレッスンで詳述)。ライブラリは機械的に処理 — 分割を見ると長さがなぜ伸びるか、破損が下流ビットをなぜ大きく狂わせるかが説明できます。
出力が約 33% 伸びる理由
3 バイト(24 ビット)の組ごとに、同じエントロピーを印字可能テキストに広げた 4 文字になります。単純化した平均: 入力 n バイトに対し、パディング前に約 ceil(4n/3) 文字。
n mod 3 != 0 のときは = パディングも加わります。このオーバーヘッドは想定内。Base64 はサイズと互換性のトレードオフです。
デコードの対称性
デコード:
- 各アルファベット文字 → 6 ビット値(
+//の癖は後で選んだ variant 次第)。 - ビットを連結。
- 8 ビットチャンクに再グループ化。
文字列長が不正、または不正な記号(空白、間違ったアルファベットインデックス)があるとデコード失敗 — または緩いパーサーによって一貫性なく動作する、より悪い場合も。
要点
Base64 はビットを 6 ビットグループに再チャンクし、各チャンクを 64 個の決められた印字可能文字の 1 つに置き換えます。再グループ化をイメージできれば、パディングと URL-safe 置換は謎ではなくなります。