文字列のエンコードとデコード
Python の Base64 関数はバイト列を扱うので、テキストは先に UTF-8 にエンコードし、最後にデコードして戻す必要があります。どちらかを忘れるのが最もよくあるミスです。
import base64
s = "héllo wörld"
encoded = base64.b64encode(s.encode("utf-8")).decode("ascii")
print(encoded) # aMOpbGxvIHfDtnJsZA==
decoded = base64.b64decode(encoded).decode("utf-8")
print(decoded) # héllo wörld
b64decode は str と bytes の両方を受け付けますが、b64encode が受け取るのは bytes-like オブジェクトだけです。文字列を渡すと TypeError: a bytes-like object is required, not 'str' が発生します。
結果を手作業で確認するなら、ブラウザ内でローカルに動作する Base64 デコーダー に貼り付けてください。
パディングあり・なしの URLセーフ Base64
URLセーフなアルファベットでは、+ が - に、/ が _ に置き換わります(Base64 と Base64URL の違い を参照)。Python は = のパディングを残すので、対象の形式(JWT や多くの API)がパディングを求めない場合は自分で取り除いてください。
data = b"<<???>>"
print(base64.b64encode(data)) # b'PDw/Pz8+Pg=='
print(base64.urlsafe_b64encode(data)) # b'PDw_Pz8-Pg=='
print(base64.urlsafe_b64encode(data).rstrip(b"=")) # b'PDw_Pz8-Pg'
Python は長さが 4 の倍数であることを要求するため、パディングのない入力をデコードするにはパディングを戻す必要があります。
def b64url_decode(s: str) -> bytes:
return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
print(b64url_decode("PDw_Pz8-Pg")) # b'<<???>>'
b64encode と b64decode は、+/ を置き換える 2 バイトの文字列 altchars も引数に取ります。altchars=b"-_" は URLセーフ版の関数と同じです。
print(base64.b64decode("PDw_Pz8-Pg==", altchars="-_")) # b'<<???>>'
ファイルのエンコードとデコード
メモリに収まるファイルなら、バイト列を読み込んで 1 回の呼び出しでエンコードします。
from pathlib import Path
b64 = base64.b64encode(Path("hello.txt").read_bytes()).decode("ascii")
print(b64) # aGVsbG8K
Path("hello-copy.txt").write_bytes(base64.b64decode(b64))
コードを書かずに 1 回だけ変換したいなら、ファイルを Base64 に変換 ツールで同じことをブラウザでできます。
大きなファイルとストリーム処理
Base64 は入力 3 バイトごとに出力 4 文字を対応させるので、最後以外の各チャンクが 3 バイトの倍数であれば、ストリームをチャンク単位でエンコードできます。こうすれば途中にパディングが入らず、つなげた出力は 1 回の呼び出しでエンコードした結果と一致します。
CHUNK = 3 * 1024 * 256 # 768 KiB, a multiple of 3
with open("big.bin", "rb") as src, open("big.b64", "wb") as dst:
while chunk := src.read(CHUNK):
dst.write(base64.b64encode(chunk))
100 KB のランダムなファイルで base64 -w 0 と比較したところ、バイト単位で同一でした。改行を含まない入力なら、チャンクサイズを 4 文字の倍数にすることで、デコードも同じようにチャンク単位で行えます。
MIME の折り返し
メールや一部のレガシーシステムでは、1 行 76 文字以内が求められます(RFC 2045)。encodebytes がこれを行い、ファイルオブジェクト間では base64.encode / base64.decode が同じことをします。
print(base64.encodebytes(b"x" * 60))
# b'eHh4eHh4...eHh4\neHh4\n' (76 chars, newline, rest, newline)
with open("big.bin", "rb") as src, open("big.mime", "wb") as dst:
base64.encode(src, dst)
with open("big.mime", "rb") as src, open("big.out", "wb") as dst:
base64.decode(src, dst)
末尾の改行に注意してください。encodebytes は必ず改行を 1 つ付けます。オンライン版は MIME Base64 エンコーダー をご覧ください。
入力の検証
デフォルトの b64decode は寛容で、デコードの前にアルファベット外の文字をすべて捨てます。これはバグを隠します。厳格にするには validate=True を渡してください。
import binascii
print(base64.b64decode("aGk=!!")) # b'hi' (junk ignored)
try:
base64.b64decode("aGk=!!", validate=True)
except binascii.Error as e:
print(e) # Only base64 data is allowed
validate=True では改行も不正とみなされるので、MIME の改行は先に取り除いてください。表示されるエラーはすべて binascii.Error(ValueError のサブクラス)です。
| 入力 | メッセージ |
|---|---|
aGk | Incorrect padding |
a | Invalid base64-encoded string: number of data characters (1) cannot be 1 more than a multiple of 4 |
aGk==(validate=True) | Excess padding not allowed |
aGk=!!(validate=True) | Only base64 data is allowed |
それぞれの原因は Base64 のエラーの解説 で説明しています。Base64 バリデーター では、文字列のどこが壊れているかを確認できます。
よくある落とし穴
URLセーフな入力を b64decode に渡す。 validate=True がないと、- と _ が黙って捨てられ、エラーではなくおかしなデータが返ってきます。
print(base64.b64decode("PDw_Pz8-Pg==")) # b'<<\x0f\xcf\xc3\xe0'
print(base64.urlsafe_b64decode("PDw_Pz8-Pg==")) # b'<<???>>'
urlsafe_b64decode は標準の入力も受け付けるので、受け取ったのがどちらのアルファベットかわからないときは、こちらをデフォルトにするほうが安全です。
バイナリをテキストとしてデコードする。 ペイロードが画像や ZIP の場合、.decode("utf-8") は UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte で失敗します。バイト列のままファイルに書き出してください。
出力に b'...' が付く。 b64encode の戻り値を print すると bytes リテラルが表示されます。その b' が JSON やヘッダーに紛れ込んでいたら、.decode("ascii") を忘れています。
Base64 を保護手段とみなす。 Base64 はエンコードであり、誰でも元に戻せます。エンコードと暗号化の違い をご覧ください。
バージョンごとの注意
base64.b64encode、b64decode、urlsafe_*、altcharsは Python 3 のすべてのリリースにあります。b64decodeは Python 3.3 以降、bytes だけでなく ASCII のstrも受け付けます。encodestringとdecodestringは 3.9 で削除されました。encodebytesとdecodebytesを使ってください。binascii.b2a_base64(data, newline=False)(3.6 以降)はb64encodeの裏で動く低レベル関数で、b"hi"に対してb'aGk='を返します。- 同じモジュールには、ほかのアルファベット向けに
b32encode、b16encode、b85encode、a85encodeもあります。
よくある質問
b64encode が文字列ではなく b'...' を返すのはなぜですか?
Python の Base64 関数はバイト列を受け取り、バイト列を返すからです。str が欲しければ結果に .decode("ascii") を呼び、エンコードの前にはテキストに .encode("utf-8") を呼んでください。
Python でパディングのない Base64 をデコードするには?
デコードの前に不足している = を補います:s + "=" * (-len(s) % 4)。Python はパディングのない入力を binascii.Error: Incorrect padding で拒否します。
b64decode と urlsafe_b64decode の違いは何ですか?
urlsafe_b64decode は、デコードの前に - と _ を + と / に変換します。素の b64decode は、validate=True を渡さない限り - と _ をゴミとして黙って捨てるため、エラーではなく誤ったバイト列を返します。
base64.encodestring はまだ使えますか?
使えません。encodestring と decodestring は Python 3.9 で削除されました。改行付きの MIME 形式の出力には encodebytes と decodebytes を使ってください。