計算式
n バイトの入力に対して、パディング付きの Base64(RFC 4648 のデフォルト)の長さは正確に次のとおりです。
length = 4 × ceil(n / 3)
JWT 用の Base64URL のようにパディングがない場合は次のとおりです。
length = ceil(4n / 3)
両者の差は = の数で、n mod 3 に応じて 0、1、2 のいずれかになります。その由来は パディングの解説 で説明しています。
逆に、長さ L のパディング付き Base64 文字列をデコードしたサイズは次のとおりです。
bytes = 3 × L / 4 − (number of trailing "=")
つまり TWFu は 3 バイト、TWE= は 2 バイト、TQ== は 1 バイトです。数える前に空白と改行を取り除いてください。Base64 サイズ計算ツール は、改行や Data URI の接頭辞も含めて両方向の計算を行います。
コードでは次のようになります。
import base64, math, os
for n in [1, 2, 3, 100, 1000]:
data = os.urandom(n)
assert len(base64.b64encode(data)) == 4 * math.ceil(n / 3)
const b64Len = (n) => 4 * Math.ceil(n / 3);
const rawLen = (s) => (s.length * 3) / 4 - (s.endsWith("==") ? 2 : s.endsWith("=") ? 1 : 0);
b64Len(1000); // 1336
rawLen("TWE="); // 2
33% 増える理由
1 バイトは 8 ビットです。Base64 の 1 文字は 64 = 2⁶ 種類の記号の 1 つなので 6 ビットのデータを運びますが、保存されるのは 8 ビットの ASCII バイトとしてです。そのため、出力の 1 バイトには有効なビットが 6 つしかなく、出力は入力の 8/6 = 4/3 になります。これは 33.3% の増加で、越えられない下限です。アルファベットを捨てない限り、どの Base64 実装もこれより良くはなりません(ビットの組み替え方は Base64 の仕組み で順を追って示しています)。
小さな入力では、出力が常に 4 文字単位のブロックになるため、パディングによって比率が悪化します。1 バイトは 4 文字になり、300% の増加です。数百バイトにもなればパディングは無視できるほどになり、比率は 4/3 に落ち着きます。
より密なテキストエンコード方式もあります。Base85 は 4 バイトを 5 文字に収め(25% の増加)、Base91 はそれよりわずかに低くなります。ただし対応している環境は限られ、アルファベットには場面によって問題を起こす文字が含まれます。比較は Base64 と 16進数・Base32・Base58・Base85 の比較 にあります。
改行:MIME、PEM、コマンドラインツール
Base64 を行に折り返す形式もあり、行末の文字もサイズに含まれます。
| 形式 | 行の長さ | 行末 | 全体の増加率 |
|---|---|---|---|
| プレーンな Base64(RFC 4648) | 折り返しなし | なし | 33.3% |
| MIME メール(RFC 2045) | 76 | CRLF(2 バイト) | 36.8% |
GNU の base64、Python の encodebytes | 76 | LF(1 バイト) | 35.1% |
| PEM 形式の証明書と鍵 | 64 | LF | 35.4% |
MIME の数値は 4/3 × 78/76 から来ています。Base64 の 76 文字ごとに、CRLF の 2 バイトが加わるからです。L = 4 × ceil(n / 3) とすると、最後の行も含めて各行の後に CRLF を付けたサイズは次のとおりです。
mime_length = L + 2 × ceil(L / 76)
76 文字の完全な行にはちょうど 57 バイトの入力が入るので、MIME エンコーダーはデータを 57 バイト単位で処理します。メールについて詳しくは メールと Base64 をご覧ください。MIME Base64 エンコーダー は折り返された出力を生成します。
サイズ早見表
上の計算式で求め、Python の base64 モジュールで確認しました。
| 入力 | パディング付き Base64 | パディングなし | MIME(76 + CRLF) | 増加率(パディング付き) |
|---|---|---|---|---|
| 1 バイト | 4 | 2 | 6 | 300% |
| 2 バイト | 4 | 3 | 6 | 100% |
| 3 バイト | 4 | 4 | 6 | 33.3% |
| 10 バイト | 16 | 14 | 18 | 60% |
| 57 バイト | 76 | 76 | 78 | 33.3% |
| 100 バイト | 136 | 134 | 140 | 36% |
| 1,000 バイト | 1,336 | 1,334 | 1,372 | 33.6% |
| 1 KiB(1,024) | 1,368 | 1,366 | 1,404 | 33.6% |
| 1 MB(10⁶) | 1,333,336 | 1,333,334 | 1,368,424 | 33.3% |
| 1 MiB(1,048,576) | 1,398,104 | 1,398,102 | 1,434,898 | 33.3% |
| 10 MiB | 13,981,016 | 13,981,014 | 14,348,938 | 33.3% |
| 25 MB(25 × 10⁶) | 33,333,336 | 33,333,334 | 34,210,530 | 33.3% |
最後の行がメールのケースです。SMTP のサイズ制限(SIZE 拡張、RFC 1870)はエンコードを含めた送信時のメッセージを数えるので、25 MB のファイルは送信時に約 34.2 MB になり、25 MB のサーバー制限を通りません。添付ファイルのサイズで制限を説明しているプロバイダーもあるので、お使いのサービスがどう数えているか確認してください。
圧縮の効果
圧縮と Base64 の関係には 2 つの側面があり、順序が重要です。
両端を自分で制御できるなら、エンコードの前に圧縮します。 圧縮したデータの Base64 は、より小さな数値の 4/3 です。GPL-3 のライセンス文(35,149 バイト)を、Node.js の zlib で gzip レベル 9 を使って測定しました。
| 処理の流れ | サイズ |
|---|---|
| 元のテキスト | 35,149 |
| Base64 のみ | 46,868 |
| Base64 の後に gzip | 17,465 |
| gzip の後に Base64 | 16,124 |
| gzip のみ | 12,091 |
Gzip + Base64 ツールは、「gzip の後に Base64」の処理をブラウザで行います。
バイナリデータなら、転送時の圧縮で増加分の大半を取り戻せます。 gzip や Brotli で圧縮された HTTP レスポンスの中で Base64 文字列を送ると、圧縮器は 6 ビット分の情報しかない各文字を 6 ビット近くまで詰め直します。1 MiB のランダムなバイト列は、Base64 で 1,398,104 文字に増えましたが、gzip すると 1,053,023 バイト(元のデータより 0.4% 大きいだけ)になり、Brotli では 1,048,805 バイトになりました。同じデータに MIME の改行を入れたものは gzip で 1,088,689 バイトになったので、圧縮後でも CRLF の分だけ約 3.4% 余計にかかります。
テキストは事情が異なります。Base64 は圧縮器が頼りにする繰り返しを崩してしまうため、Base64 にしたテキストは元のテキストよりずっと圧縮されにくくなります。上の表では 12,091 バイトに対して 17,465 バイトでした。Data URI のパフォーマンス の記事には、PNG、JPEG、WOFF2、SVG での測定結果があります。
URL、ヘッダー、Cookie の制限
Base64 は URL、ヘッダー、Cookie に入れるものすべてを膨らませますが、この 3 つにはどれも制限があります。
- URL。 RFC 9110 は、送信側と受信側が少なくとも 8,000 オクテットの URI に対応することを推奨しています。多くのサーバーはデフォルトでそのあたりが上限です。nginx はリクエスト行用に 8 KB のヘッダーバッファーを割り当て、Apache の
LimitRequestLineはデフォルトで 8,190 バイトです。それより長い URL は、たいてい414 URI Too Longで失敗します。Base64URL なら、URL のほかの部分を除いて約 6,000 バイトのデータが入る計算です。 - ヘッダー。 同じサーバーバッファーが各ヘッダー行にも適用されます。
Authorization: Bearer ...ヘッダーの長い JWT や、大きな Basic 認証の資格情報で問題になります。 - Cookie。 RFC 6265 は、ブラウザが名前と属性を含めて Cookie 1 つあたり少なくとも 4,096 バイトに対応することを求めています。Base64 でエンコードすると、データとしては約 3 KB です。
Base64 のペイロードがこれらの制限に近づいているなら、リクエストボディで送るか、サーバー側に保存して ID を渡すようにしてください。
バイト数を入力するか文字列を貼り付けると、改行の有無を含めてエンコード後とデコード後のサイズを表示します。
よくある質問
Base64 は元のファイルよりどれくらい大きくなりますか?
数バイトを超えるデータなら約 33.3% です。MIME の改行(76 文字 + CRLF)を含めると約 36.8% になります。
Base64 文字列から元のサイズを計算するには?
空白を除いた長さに 3/4 を掛け、末尾の = の数を引きます。たとえば TWE= は 4 × 3/4 − 1 = 2 バイトです。
1 バイトの入力が 4 文字になるのはなぜですか?
パディングを使う場合、Base64 は常に 4 文字の完全なブロックで出力するからです。1 バイトは 2 文字分を埋め、残りの 2 文字は = になるため、ごく小さな入力では相対的な増加率がずっと高くなります。
Base64 を小さくすることはできますか?
エンコードの前に gzip などでデータを圧縮し、受け取り側が許すならパディングを省いてください。Base64 自体の比率は固定です。Base85 のようにより密なテキストエンコード方式もありますが、対応している環境は限られます。