Base64 エンコード後の長さ計算
画像や証明書を、テキストしか通らない場所に載せたいことがあります。JSON の値、メールの本文、data: から始まる URL。そのときに使うのが Base64 です。載せる前に、どれだけ膨らむのかを見積もれると助かります。
そのままでは、テキストの中に置けない
バイト列には、改行や制御にあたる値も、画面に出せない値も含まれます。テキストとして扱われる経路に流すと、途中で書き換えられたり、そこで切られたりします。そこで、印字できる 64 種類の文字だけを使って書き直します。これが Base64 です。
暗号ではありません。受け取った側は誰でも元のバイト列に戻せます。隠したい中身は、別に暗号化しておく必要があります。
もとは、7 ビットの文字しか運べなかった時代のメールで、添付ファイルを送るために広まった仕組みです。いまも JWT の中身や、設定ファイルに書く鍵など、テキストの器にバイナリを入れたい場所で使われ続けています。
3 バイトが 4 文字になる
Base64 の 1 文字は 6 ビットを表します。バイトは 8 ビットなので、3 バイト分の 24 ビットが、ちょうど 4 文字分の 24 ビットに収まります。情報の量は変わりませんが、1 文字あたりが薄くなるぶん、長さは 4 対 3、およそ 33% 増えます。
プレーンテキスト
3 バイト 8 + 8 + 8 = 24 ビット
Base64 4 文字 6 + 6 + 6 + 6 = 24 ビット300 KB の画像を
data:URL に埋め込むと、HTML の側では 400 KB ほどを占めます。小さなアイコンを埋め込むかどうかは、この増分と、リクエストが 1 本減ることを見比べて決めます。
3 で割り切れないときに、1 組増える
入力が 3 の倍数とは限りません。半端が出たときは、足りないビットを 0 で埋めて 4 文字を作り、中身の無い文字の位置は = で埋めます。つまり出力は必ず 4 文字単位で、半端があれば 1 組ぶん増えます。ここで要るのが切り上げの割り算です。菓子を箱に詰める話と同じ形をしています。
Python
items = 12
per_box = 5
print(items // per_box) # 2 1 箱足りない
print((items + per_box - 1) // per_box) # 3// は切り捨てなので、そのままだと入りきらなかった分を数え落とします。割る前に、箱の大きさより 1 小さい数を足しておくと、半端があるときだけ商が 1 増えます。整数の割り算しか無い言語でも、この形なら切り上げが書けます。
JavaScript
console.log(Math.ceil(12 / 5)); // 3JavaScript や Python なら ceil を使っても同じ結果になります。読みやすいほうを選んでください。
末尾の = は、何ビット余ったかを受け取る側に伝えるための印です。これがあるおかげで、復号したときに元のバイト数まで戻せます。長さを数えるときも、この = を含めた 4 文字単位のかたまりを数えます。
数える対象にも気をつけます。数えるのはバイト数であって、文字数ではありません。日本語の 1 文字は UTF-8 では 3 バイトなので、文字列の長さをそのまま渡すと答えがずれます。
要件
- 公式 ceil(n / 3) * 4 で文字数を求める
- n=0 のときは 0 を返す
- 戻り値は整数 (Base64 文字数) で、パディング = も含めた長さを表す
入出力例
base64Length(0) → 0
base64Length(1) → 4
base64Length(2) → 4
base64Length(3) → 4
base64Length(4) → 8
base64Length(10) → 16
base64Length(100) → 136
base64Length(999) → 1332