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のほうが安全




