Base64 とは何か、なぜ存在するのか
Base64 は、任意のバイナリデータを 64 種類の印字可能な ASCII 文字だけで書き表す方法です。使う文字は A–Z、a–z、0–9、+、/ と、パディング用の = です。Base64 はエンコーディングなので、鍵なしで誰でも元に戻せます。何も隠しません(詳しくはエンコードと暗号化の違いを参照)。
目的は、テキストしか通さない経路でもバイト列を壊さずに届けることです。PNG ファイルには 0x00、0x1A、0x89 のようなバイトが含まれ、テキストの経路はこれらを削除したり、変換したり、制御コードとして扱ったりすることがあります。同じファイルを Base64 で書くと iVBORw0KGgo... となり、メール、JSON、XML、URL(URLセーフなバリアントを使う場合)、ターミナル、コピー&ペーストをそのまま通り抜けます。
現在の定義は RFC 4648 です。MIME メール(RFC 2045)や PEM 証明書の古い仕様も同じアルファベットを定めていますが、行の長さについての追加ルールがあります。バリアントの概要は Base64 の規格のページにまとめています。
メールに必要だった理由
初期のメールは 7 ビット ASCII のテキスト用に設計されていました。当時のメール中継サーバーは各バイトの最上位ビットを落としたり、長い行を折り返したり、改行コードを書き換えたりすることがあり、手紙なら無害でも ZIP ファイルにとっては致命的でした。MIME には、どんな添付ファイルでもこの経路を通して運ぶ方法と、途中のあらゆるシステムで壊れないアルファベットが必要でした。
Base64 が 64 文字を選んだのはそのためです。英字と数字は ASCII でも、ほぼすべての古い文字セットでも同じで、+ と / もそれに近いくらい安全です。どれも制御文字や空白ではありません。また 64 は、この安全な集合から埋められる最大の 2 のべき乗で、2 のべき乗ならビット演算も単純です。代償はサイズです。8 ビットの文字 1 つにつき有効なビットは 6 なので、出力は入力の 4/3 にパディングを加えた大きさになります。
8 ビット問題は現代のメールサーバーからはほぼ消えましたが、バイナリデータをテキスト形式の中に置かなければならない場所では、同じ制約が今も現れます。JSON にはバイナリ型がなく、HTML の属性、CSS、HTTP ヘッダーはすべてテキストなので、そこにバイト列を入れる標準的な方法は今でも Base64 です。
アルゴリズム:3 バイトを入れて 4 文字を出す
Base64 は 24 ビットのブロック単位で処理します。24 は 8(1 バイト)と 6(Base64 の 1 文字)の両方で割り切れる最小の数なので、3 バイトがちょうど 4 文字に対応します。
各ブロックの処理:
- 3 バイトを取り出し、24 ビットを一列に並べます。
- その 24 ビットを 6 ビットずつ 4 つのグループに区切ります。
- 各グループを 0〜63 の数として読みます。
- その数をアルファベットで引き、対応する文字を出力します。
アルファベットは固定の表です。インデックス 0 は A、25 は Z、26 は a、51 は z、52 は 0、61 は 9、62 は +、63 は / です。完全な表は Base64 アルファベットのページにあります。
| 範囲 | 文字 | 例 |
|---|---|---|
| 0–25 | A–Z | 19 → T |
| 26–51 | a–z | 46 → u |
| 52–61 | 0–9 | 52 → 0 |
| 62 | + | |
| 63 | / |
デコードは同じ手順を逆にたどります。各文字を表で引いて 6 ビットを得て、4 つをつなげて 24 ビットにし、それを 3 バイトに分けます。
計算例:「Man」が TWFu になる
定番の例は ASCII テキストの Man です。ASCII では M が 77、a が 97、n が 110 です。
| M | a | n | |
|---|---|---|---|
| 10進数 | 77 | 97 | 110 |
| 2進数 | 01001101 | 01100001 | 01101110 |
ビットをつなげると 010011010110000101101110 になります。これを 8 ビットごとではなく 6 ビットごとに区切ります。
| 6 ビットのグループ | 010011 | 010110 | 000101 | 101110 |
|---|---|---|---|---|
| 値 | 19 | 22 | 5 | 46 |
| 文字 | T | W | F | u |
こうして Man は TWFu にエンコードされます。出力のどの文字も入力の文字と 1 対 1 に対応していない点に注目してください。W は M の下位 4 ビットと a の上位 2 ビットからできています。入力を 1 バイト変えると出力が 2 文字変わるのも、入力がただの英文でも Base64 を目で読めないのもこのためです。
どのシェルでも確認できます。
echo -n Man | base64
# TWFu
-n を付けないと echo が改行のバイトを加え、Man\n は TWFuCg== になります。
入力が 3 の倍数でないとき:パディング
実際のデータの長さが 3 で割り切れることはまれです。その場合、最後のブロックは 1 バイトか 2 バイトになり、Base64 はゼロのビットと = で埋め合わせます。
残りが 2 バイト、「Ma」→ TWE=。 2 バイトは 16 ビットです。ゼロのビットを 2 つ足して 18 ビット、つまり 6 ビットのグループ 3 つにします。
| 6 ビットのグループ | 010011 | 010110 | 000100 | (なし) |
|---|---|---|---|---|
| 値 | 19 | 22 | 4 | |
| 文字 | T | W | E | = |
3 つ目のグループは a の 0001 に足した 2 つのゼロが続いたもので、値は 4、つまり E です。= 1 つが欠けた 4 文字目の代わりになります。
残りが 1 バイト、「M」→ TQ==。 1 バイトは 8 ビットです。ゼロのビットを 4 つ足して 12 ビット、つまり 010011(19、T)と 010000(16、Q)の 2 グループにします。= 2 つでブロックを埋めます。
つまり = の数を見れば、最後のブロックが何バイトだったかがわかります。なしなら 3 バイト、1 つなら 2 バイト、2 つなら 1 バイトです。残りが 0 バイトのブロックは存在しないので、= が 3 つになることはありません。= を省略できる場合と元に戻す方法はパディングの解説で扱っています。
import base64
for s in [b"Man", b"Ma", b"M"]:
print(s, base64.b64encode(s))
# b'Man' b'TWFu'
# b'Ma' b'TWE='
# b'M' b'TQ=='
テキストは先にバイト列にする必要がある
Base64 がエンコードするのはバイト列です。テキストは文字コードを選んで初めてバイト列になり、その選択によって結果が変わります。café という単語は UTF-8 では 5 バイト(é が 2 バイト)なので Y2Fmw6k= になり、Latin-1 では 4 バイトで Y2Fm6Q== になります。
これが JavaScript でよく知られた罠の原因です。ブラウザの関数 btoa() は文字列の各文字を 1 バイトとして扱い、これは Latin-1 の挙動です。
btoa("café"); // "Y2Fm6Q==" (Latin-1, not UTF-8)
btoa("café ✓"); // throws InvalidCharacterError
JavaScript で UTF-8 の Base64 を得るには、先に TextEncoder でバイト列にするか、Node.js なら Buffer を使います。動作するパターンは JavaScript のコード例に、新しい組み込みメソッドについては Uint8Array.toBase64() にあります。このサイトの Base64 エンコーダーはデフォルトで UTF-8 を使い、別の文字コードも選べます。
Base64 に出会う場所
- メールの添付ファイル。 生の
.emlファイルにある添付ファイルは、どれも 76 文字で折り返された Base64 のブロックです。メールでの Base64 をご覧ください。 - Data URI。
data:image/png;base64,iVBORw0KGgo...は画像を HTML や CSS に直接埋め込みます。それが得策かどうかは Data URI のパフォーマンスで扱っています。 - HTTP Basic 認証。
Authorization: Basic dXNlcjpwYXNzというヘッダーは、user:passを Base64 にしただけのものです。 - JWT。 JSON Web Token のドットで区切られた 3 つの部分は Base64URL です。JWT での Base64 をご覧ください。
- PEM ファイル。
-----BEGIN ...-----の行に挟まれた証明書や鍵は、バイナリの DER データを Base64 にしたものです。 - API と設定。 Kubernetes の Secret、クラウドのメタデータ、Webhook のペイロード、多くの JSON API は、ファイルやバイナリのデータを Base64 文字列として運びます。
慣れてくると、先頭の文字でデータの種類がわかるようになります。iVBORw0KGgo は PNG、JVBERi0 は PDF、/9j/ は JPEG、eyJ は {" で始まる JSON です。ほかの例はよくある Base64 の接頭辞のページにあります。中身を確かめたいときは、Base64 をファイルに変換ツールが形式を判定し、ブラウザ内でプレビューします。
Base64 を貼り付けると、中のテキストやバイト列が表示されます。処理はブラウザ内で行われます。
よくある質問
Base64 は圧縮の一種ですか?
いいえ。Base64 は常にデータを大きくします。入力 3 バイトが出力 4 文字になるので、約 33% 増えます。正確な式は Base64 のサイズ増加をご覧ください。
なぜ Base64 という名前なのですか?
出力の各文字が 64 通りの値のいずれか、つまり 6 ビット分の情報を持つからです。名前はアルファベットの大きさを表しており、16進数が base 16 であるのと同じです。
どんなデータでも Base64 エンコードできますか?
はい。アルゴリズムはバイト列を扱い、その意味を気にしないので、テキスト、画像、PDF、鍵、圧縮アーカイブのどれも同じようにエンコードできます。テキストは先に文字コード(通常は UTF-8)でバイト列に変換する必要があります。
Base64 の出力に空白や改行が含まれることはありますか?
素の RFC 4648 Base64 には含まれません。形式によっては行を折り返すものがあり、たとえば MIME のメールは 76 文字、PEM ファイルは 64 文字で折り返します。ほとんどのデコーダーはこうした空白を読み飛ばします。