Python urlsafe_b64encode()とは?URLで使えるBase64にする方法

Pythonのbase64.urlsafe_b64encode()は、URLに入れても壊れないBase64を作る関数です。

通常のBase64が使う+と/を、-と_に置き換えます。


標準のBase64はURLで壊れる

import base64

data = bytes([251, 255, 190])

print("標準   :", base64.b64encode(data).decode())
print("urlsafe:", base64.urlsafe_b64encode(data).decode())

実行結果:

標準   : +/++
urlsafe: -_--

+と/が-と_になりました。

この2文字がURLで問題になる理由です。

文字 URLでの意味 起きること
+ クエリ文字列ではスペース 受け取り側で空白に化ける
/ パスの区切り 別の階層と解釈される
= キーと値の区切り パラメータが分断される

「なぜかデータが壊れる」というトラブルの典型的な原因がこれです。


urlsafe版の使い方

import base64

data = b"hello world"

enc = base64.urlsafe_b64encode(data).decode()
print("エンコード:", enc)

dec = base64.urlsafe_b64decode(enc)
print("復元:", dec)

実行結果:

エンコード: aGVsbG8gd29ybGQ=
復元: b'hello world'

使い方は標準版とまったく同じです。b64をurlsafe_b64に変えるだけです。

エンコードとデコードは必ず同じ種類を使ってください。混ぜると+と-の解釈がずれて壊れます。


=は残るので自分で外す

注意点です。urlsafeでもパディングの=は付いたままです。

import base64

print(base64.urlsafe_b64encode(b"hello").decode())

実行結果:

aGVsbG8=

=もURLでは意味を持つ文字なので、削って渡す慣習があります(JWTなどが採用しています)。

import base64

def encode_nopad(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).decode().rstrip("=")

def decode_nopad(s: str) -> bytes:
    return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))

e = encode_nopad(b"hello")
print("パディングなし:", e)
print("復元:", decode_nopad(e))

実行結果:

パディングなし: aGVsbG8
復元: b'hello'

削るなら、戻すときに補う処理をセットで用意してください。補わずにb64decode()するとIncorrect paddingになります。


実践例|URLにデータを埋め込む

import base64, json

def make_link(params: dict) -> str:
    raw = json.dumps(params, ensure_ascii=False).encode("utf-8")
    token = base64.urlsafe_b64encode(raw).decode().rstrip("=")
    return f"https://example.com/view?d={token}"

def read_link(token: str) -> dict:
    raw = base64.urlsafe_b64decode(token + "=" * (-len(token) % 4))
    return json.loads(raw.decode("utf-8"))

url = make_link({"page": 3, "keyword": "検索語"})
print(url)

token = url.split("?d=")[1]
print(read_link(token))

実行結果:

https://example.com/view?d=eyJwYWdlIjogMywgImtleXdvcmQiOiAi5qSc57Si6KqeIn0
{'page': 3, 'keyword': '検索語'}

日本語を含む条件をURLに埋め込めました。1つのパラメータで複数の値を運べるのが利点です。

ただし重要な制約があります。これは誰でも読めるし、書き換えられます。

  • ユーザーがトークンを復元して中身を見られる
  • 好きな値に書き換えて再エンコードできる
  • 権限やユーザーIDを入れてはいけない

改ざんを防ぎたいなら署名を付けてください。

import hmac, hashlib, secrets

def sign(token: str, key: bytes) -> str:
    sig = hmac.new(key, token.encode(), hashlib.sha256).hexdigest()[:16]
    return f"{token}.{sig}"

def verify(signed: str, key: bytes) -> str | None:
    token, _, sig = signed.rpartition(".")
    expected = hmac.new(key, token.encode(), hashlib.sha256).hexdigest()[:16]
    return token if secrets.compare_digest(expected, sig) else None

JWTが採用しているのも、この「urlsafe Base64 + 署名」という構成です。詳しくは「Python secrets.compare_digest()とは?安全に文字列を比較する方法」で解説しています。


ファイル名にも使える

/はファイル名にも使えないため、同じ問題が起きます。

import base64

def safe_filename(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).decode().rstrip("=")

ただしWindowsではファイル名の大文字と小文字が区別されません。Base64は大文字と小文字を区別するため、aBとAbが同じファイルとして扱われ衝突します。

ファイル名にはuuid4().hexのほうが安全です。16進数なので大文字小文字の問題が起きません。詳しくは「Python uuid.uuid4()とは?ランダムなUUIDを生成する方法」を参照してください。


標準版とurlsafe版の使い分け

場所 使うもの
URLのパス・クエリ urlsafe_b64encode()
Cookieの値 urlsafe_b64encode()
JSONの中身 どちらでもよい
メールの添付 b64encode()(標準)
外部仕様で決まっている 仕様に従う

迷ったらurlsafe版で構いません。URL以外で使っても問題は起きません。


urlsafe_b64encode()のまとめ

  • +→-、/→_に置き換える
  • URLでは+がスペースに、/が区切りに化ける
  • エンコードとデコードは必ず同じ種類で
  • =は残る——削るなら戻すとき補う
  • URLに埋めたデータは誰でも読めて書き換えられる
  • 改ざん対策には署名が必要
  • ファイル名にはuuid4().hexのほうが安全

あわせて読みたい

対面・無料|東京・永田町で毎週土曜日13時

1回の参加で、プログラミングの構造がわかる

ゼロからはじめるPython入門講座

8年間・累計600回開催/これまで全国で2万人以上が参加

「やはり対面で聞けるのはとても気づきがあり、価値があると思います」

参加者の声

  • 本当に初心者からの講座です。プログラミングに触ったことがなくて大丈夫です
  • 対面だから、その場で聞けます。つまずいた所を持ち帰らずに解決できます
  • 講座は1時間。そのあとも残って学習を続けられ、講師にも質問できます
  • 独学で挫折した方も。糸口が見つかって、講座のあと会員になる方が多くいます

無料で参加する|開催日程を見る

参加費は無料です