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 で決められています。

  1. 入力を 3バイト(24ビット)ずつ区切ります。
  2. 24ビットを 6ビットずつ4つに分けます。6ビットは 0〜63 の値です。
  3. それぞれの値を表の文字に置き換えます。
値 文字
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)

ブログへ