ファイルを選択
  1. base64hub
  2. Base64 を学ぶ
  3. パディング(=)
解説

Base64 のパディング:= 記号の役割

Base64 の末尾の = は、最後のブロックが短いことを示します。省略する形式もあれば、必須とするデコーダーもあり、元に戻すのはコード 1 行で済みます。

Base64 にパディングが必要な理由

Base64 は 3 バイトを 4 文字に変換します(アルゴリズム全体は Base64 の仕組みを参照)。入力の長さが 3 の倍数でないと、最後のブロックが短くなります。

最後のブロックのバイト数データの文字数パディング例
34なしMan → TWFu
23=Ma → TWE=
12==M → TQ==

= によって、出力の長さは常に 4 の倍数になります。これは MIME 時代に書かれたデコーダーにとって都合がよく、入力を 4 文字ずつ固定で読み進め、全体の長さを追わなくてもデータの終わりがわかりました。

厳密に言えば、この情報は冗長です。最後のグループが 3 文字なら 2 バイト、2 文字なら 1 バイトとしか解釈できないので、文字数を数えるデコーダーには = はまったく必要ありません。パディングなしの Base64 が成り立ち、多くの形式がそれを使っているのはこのためです。

パディングが本当に情報を持つケースが 1 つあります。連結です。パディング付きの文字列を 2 つつなげると、途中の = が 1 つ目の終わりを示します。ただしデコーダーはこれをうまく扱えません。TQ==TQ==(エンコードした M を 2 つ)を渡すと、Python のデフォルトのデコーダーは b'M\x04\xd0' を返し、Node の Buffer は M だけを返し、atob は例外を投げました。各部分を別々にデコードしてください。

パディングが任意の場合

RFC 4648 §3.2 は、Base64 を使う仕様が明示的に別段の定めをしない限り、エンコーダーはパディングを付けなければならない(MUST)としています。別段の定めをしている仕様はいくつもあります。

  • JWT と JOSE(RFC 7515)は、パディングを取り除いた Base64URL を使います。eyJhbGciOiJIUzI1NiJ9 のような JWT のヘッダーは、長さが 4 の倍数でなくても = を持ちません。JWT での Base64 をご覧ください。
  • URL やファイル名には、パディングなしの Base64URL がよく使われます。クエリ文字列では = に意味があり、%3D とエスケープしなければならないからです。違いの全体は Base64 と Base64URL の違いのページで扱っています。
  • WebAuthn と OAuth PKCE(RFC 7636 の code_challenge)も、パディングなしの Base64URL を使います。

メール、PEM ファイル、Data URI、ほとんどの JSON API で使われる標準 Base64 にはパディングがあります。迷ったら、標準 Base64 はパディング付き、Base64URL はパディングなしで出力し、自分のデコーダーはどちらも受け付けるようにしてください。

パディングを必須とするデコーダー

挙動は思った以上にばらばらです。各ライブラリのデフォルトのデコーダーで TWE(パディングなしの Ma)をデコードした結果は次のとおりです。

言語・ツール呼び出しTWE の結果
Python 3.13base64.b64decodebinascii.Error: Incorrect padding
Python 3.13base64.urlsafe_b64decode同じく Incorrect padding
.NETConvert.FromBase64StringFormatException
Gobase64.StdEncodingエラー。パディングなしの入力には RawStdEncoding を使う
Gobase64.RawStdEncodingMa。ただし TWE= は拒否
RubyBase64.strict_decode64ArgumentError: invalid base64
RubyBase64.decode64、urlsafe_decode64Ma
Java 21Base64.getDecoder().decodeMa
Node.jsBuffer.from(s, 'base64')、atobMa
PHP 8.4base64_decode(strict モードでも)Ma
GNU coreutils 9.7base64 -dMa
OpenSSL 3.5openssl base64 -d出力が空、終了コード 0

OpenSSL の行に注目してください。何も出力しないのに成功を報告しています。そのため、パディングなしの Base64 を openssl base64 -d にパイプするスクリプトは、黙って空のファイルを作ります。同じテストでは、末尾に改行のない 1 行の入力に対しても何も返しませんでした。この場合は -A を付けると直りました。

Go は逆方向に最も厳格です。RawStdEncoding はパディングが付いている文字列を拒否します。複数の出どころから入力を受け付けるなら、= を取り除いて raw のエンコーディングを使うか、パディングを付け足して標準のものを使ってください。

言語ごとの例はコード例のページにあります。たとえば Python での Base64 や Go での Base64 です。

欠けたパディングを戻す方法

ルールは、空白を取り除いた後の文字列の長さだけで決まります。

len % 4付け足すもの意味
0なしブロックが揃っている
2==最後のブロックは 1 バイト
3=最後のブロックは 2 バイト
1修正不可途中で切れているか、余計な文字がある

余りが 1 になることは決してありません。Base64 の 1 文字が持つのは 6 ビットで、1 バイトには足りないため、いくらパディングを足してもデコードできません。このケースに出会ったら、文字の欠落、余分な文字、数に入ってしまった改行を探してください。

ほとんどの言語では、修正は式 1 つで済みます。

import base64
s = "eyJzdWIiOiIxMjM0In0"            # 19 chars, 19 % 4 == 3
padded = s + "=" * (-len(s) % 4)     # adds one "="
print(base64.urlsafe_b64decode(padded))
# b'{"sub":"1234"}'
const s = "eyJzdWIiOiIxMjM0In0";
const padded = s + "=".repeat((4 - (s.length % 4)) % 4);
// "eyJzdWIiOiIxMjM0In0="

Base64 検証ツールは文字列の長さとパディングの状態を表示し、Base64 の修復ツールは欠けた = の追加、空白や引用符の除去、URLセーフな文字の変換を一度に行います。

非正規エンコーディング

パディングには目立たない親戚がいます。最後のデータ文字にある未使用のビットです。Ma をエンコードすると、その 16 ビットは 6 ビットのグループ 2 つ半を埋めます。3 文字目は a の下位 4 ビットと 2 つの詰め物のビットを持ち、RFC 4648 §3.5 は、エンコーダーはこの詰め物のビットをゼロにしなければならないとしています。その結果が E(000100)です。

デコーダーは詰め物のビットを無視するので、TWF=(F は 000101 で、詰め物のビットは 01)も同じ Ma にデコードされます。私たちのテストでは、Python、Node、Java、PHP のすべてが TWF= を受け付けて Ma を返しました。同様に、TR== も TQ== と同じく M にデコードされます。

TWF= のような文字列は非正規と呼ばれます。問題になる場面がいくつかあります。

  • Base64 テキストに対する署名やハッシュ。 異なる 2 つの文字列が同じバイト列にデコードされるので、データが等しいかを Base64 文字列の比較で確かめると誤った答えになることがあります。デコードしたバイト列を比較してください。
  • 重複排除やキャッシュのキー。 同じ問題です。デコードしてから再エンコードして正規化してください。
  • 厳格なパーサー。 RFC 4648 は、詰め物のビットがゼロでない入力をデコーダーが拒否することを認めています。Go の StdEncoding.Strict() と GNU の base64 -d は、どちらも TWF= を拒否します。

余分なパディングも関連する奇妙なケースです。TWE== は = が 1 つ多すぎます。Python と Node の Buffer はこれを受け付け、atob は InvalidCharacterError を投げました。

分割してエンコードするときのパディング

大きなファイルを分割して Base64 にする場合、最後以外の各部分は 3 バイトの倍数でなければなりません。そうしないと各部分がそれぞれパディングを持ち、つなげた出力が誤ったものになります。hello world!! を 4 バイトずつエンコードすると aGVsbA==byB3bw==cmxkIQ==IQ== になり、3 バイトずつなら aGVsbG8gd29ybGQhIQ== で、全体を一度にエンコードした結果と同じになりました。MIME の行折り返しでは、1 行 57 バイトが自然な分割サイズです。57 バイトはちょうど 76 文字になるからです。

パディングを自動で修正

パディングが欠けている、または壊れている文字列を貼り付けると、有効な Base64 に直して返します。

Base64 修復

よくある質問

Base64 の = 記号は削除してもかまいませんか?

データは失われません。文字列の長さから、最後のブロックが何バイトかをデコーダーが判断できるからです。ただしデコードできるかどうかは受け手次第です。JWT の Base64URL はパディングなしを前提としていますが、Python の b64decode や .NET の Convert.FromBase64String はパディングのない入力を拒否します。

Base64 文字列の末尾に = がないのはなぜですか?

入力の長さが 3 バイトの倍数でパディングが不要だったか、生成側がパディングなしの Base64 を使っているかのどちらかです。後者はトークンや URL の Base64URL でよく見られます。

Python の Incorrect padding エラーはどう直せばいいですか?

デコードの前に '=' * (-len(s) % 4) を付け足してください。文字列が URLセーフなら base64.urlsafe_b64decode も使います。長さを 4 で割った余りが 1 の場合はパディングでは直りません。文字列が途中で切れているか、余計な文字が混ざっています。

Base64 文字列が = 3 つで終わることはありますか?

ありません。最後のブロックに残るのは 1 バイトか 2 バイトで、それぞれ == か = になります。= が 3 つなら、データのないブロックということになりますが、そのようなブロックは存在しません。

続けて読む