URL-safe な符号化の選び方:Base64url・Base32・Base58・hex の使い分け

約9分

「バイト列を文字列で運ぶ」必要がある場面は意外と多く、JWT のトークン、TOTP のシークレット、暗号通貨のアドレス、ハッシュ値の表示などすべてが該当します。よく見ると、それぞれ違う符号化方式を使っています。本記事では Base64url・Base32・Base58・hex の 4 種を、用途と落とし穴の両面から並べて比較し、新しい仕様を選ぶときの基準を提示します。

4 種類の概観

形式アルファベット文字数パディングバイト数 N に対する出力長大文字小文字
Base64urlA-Z a-z 0-9 - _64=(省略可)⌈4N/3⌉区別あり
Base32 (RFC 4648)A-Z 2-732=⌈8N/5⌉区別なし
Base58 (Bitcoin)1-9 A-H J-N P-Z a-k m-z58なし1.37 N(可変)区別あり
hex0-9 a-f16なし2N(固定)区別なし

「短くしたい」だけで決めずに、人が読むか、URL に入るか、ライブラリ互換が要るかで使い分けます。

Base64url:トークンを URL や HTTP ヘッダに通す用途

JWT・OAuth アクセストークン・WebPush の購読情報など、Web 系のバイナリエンコード事実上の標準。

  • 通常の Base64 と違い、+- に、/_ に置き換える。これによりそのまま URL に入れられる
  • パディングの = は省略可能(RFC 4648 §3.2)。JWT は省略する慣習。

罠 1:padding 省略の解釈差

ライブラリによって挙動が違います:

  • Node.js Buffer.from(str, 'base64url') は padding なしを許容
  • Python base64.urlsafe_b64decodepadding 必須(足りないと例外)
  • Java java.util.Base64.getUrlDecoder() は省略可

JWT を扱うときに「ライブラリ A で生成したトークンを別言語で検証したら落ちた」という失敗の常連。手で = を補ってから渡すユーティリティをかませるのが安全:

function padBase64url(s) {
	return s + '='.repeat((4 - (s.length % 4)) % 4);
}

罠 2:通常の Base64 と取り違え

+ / = がそのまま入っている文字列を URL に乗せると、+ がスペースに、/ がパス区切りに、= がクエリの境界に化けます。通常 Base64 を URL に入れない

Base32:人が読み上げる・大文字小文字を区別したくない用途

TOTP(Google Authenticator)のシークレットキー(RFC 4226 / 6238)はこの形式。JBSWY3DPEHPK3PXP のような表記をどこかで見たはずです。

  • アルファベットが 32 文字で、小文字がない=大文字小文字を区別する必要がない
  • 紛らわしい 0/1/8/9 を含めない選択(標準 Base32 は 2-7、Crockford は別アルファベット)
  • 電話で読み上げる、紙に書き写す、QR から OCR する用途と相性が良い

罠:標準 Base32(RFC 4648)と Crockford Base32 の差

項目RFC 4648Crockford
アルファベットA-Z 2-70-9 A-Z から I,L,O,U を除外
大文字小文字区別なし区別なし(変換時に正規化)
チェックサムなし~,*,$,=,U で表現
パディング=なし

ULID は Crockford Base32 を使っており、TOTP の RFC 4648 とは別物。同じ「Base32」という言葉でも仕様が違うので、ライブラリ選定時に確認します。

Base58:先頭ゼロをそのまま運びたい用途

Bitcoin アドレス・IPFS CID・短縮 URL の ID で使われます。

  • 紛らわしい文字(0, O, I, l)を 意図的に除外しているのが最大の特徴
  • パディングなし。入力長と出力長が単純な比例関係にならない(先頭の 0x00 バイト数が出力の 1 の数として保存される)

罠:Base58 と Base58Check を混同しない

Bitcoin アドレスは「Base58」ではなく「Base58Check」と呼ばれる、末尾に 4 バイトの checksum を付けたバリアント。生の Base58 デコードは Base58Check の検証にはならず、checksum を別途検証する必要があります。

[version (1B)] [payload (20B)] [checksum (4B)] → Base58 → アドレス

ライブラリの API 名を必ず確認(base58_decodebase58check_decode か)。

罠:性能と長さ

Base58 のエンコード・デコードは内部で大きな整数の除算を行うため、Base64 や hex と比べて実装が複雑で遅い。短さと可読性のトレードオフです。

hex:人が比較する・他のシステムが必ず読める用途

ハッシュ値の表示・バイナリのデバッグダンプの定番。5d41402abc4b2a76b9719d911017c592 のような形。

  • アルファベット 16 文字なので、出力サイズは入力の 2 倍に膨らむ(Base64 系より長い)
  • どの言語・どの環境でも標準ライブラリで扱える互換性の高さ
  • 人が「ここまでは一致するな」と前方一致を目視で比較しやすい

罠:大文字/小文字どちらに揃えるか

仕様上は同値ですが、暗黙の慣習があります。

  • SHA / MD5 / HMAC 系の hex 出力は 小文字(OpenSSL も Python hashlib も小文字)
  • イーサリアムのアドレスは 大文字小文字混在で checksum を表現(EIP-55

「全部小文字に統一して比較」のような正規化を入れる前に、大文字混在に意味があるかどうかを仕様で確認します。

選び方:4 つの質問

新しい用途で符号化を決めるとき、以下の順で絞り込めます。

  1. URL や HTTP ヘッダに直接入れるか? YES → Base64url(短い)か Base58(更に短い、暗号系で実績)。 NO → 次へ。

  2. 人が読み上げる、または紙に書き写すか? YES → Base32(RFC 4648 / Crockford のいずれか)。 NO → 次へ。

  3. 他のシステムが必ず読める互換性が最優先か? YES → hex。 NO → 次へ。

  4. 長さを最小にしたいか? YES → Base64url。 NO → 用途に合わせて Base32(人寄り)か hex(機械寄り)。

まとめ

  • Web のトークンに入れる → Base64url(padding 省略は受信側の互換に注意)
  • TOTP・DNS・電話で読む → Base32(4648 / Crockford / Base32hex の差を確認)
  • 暗号通貨アドレス・短縮 ID → Base58(Base58Check との違いに注意)
  • ハッシュ・低レベル表示 → hex(大文字混在 checksum 仕様の有無を仕様書で確認)

すべて「バイト列を文字列にする」操作ですが、設計者が想定した読み手(機械か人か、人ならどう読むか)が違います。具体的なバイト列の挙動を試したいときは Base64 ツール で encode / decode を確認できます。