ファイルを選択
  1. base64hub
  2. 各言語での Base64
  3. Python
コード

Python での Base64

標準ライブラリの base64 モジュールで、標準と URLセーフのアルファベット、ファイル、MIME の行まですべて扱えます。以下のコードはすべて Python 3.13 で実行しました。

文字列のエンコードとデコード

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 のサブクラス)です。

入力メッセージ
aGkIncorrect padding
aInvalid 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 を使ってください。

続けて読む