URL-safe な符号化の選び方:Base64url・Base32・Base58・hex の使い分け
「バイト列を文字列で運ぶ」必要がある場面は意外と多く、JWT のトークン、TOTP のシークレット、暗号通貨のアドレス、ハッシュ値の表示などすべてが該当します。よく見ると、それぞれ違う符号化方式を使っています。本記事では Base64url・Base32・Base58・hex の 4 種を、用途と落とし穴の両面から並べて比較し、新しい仕様を選ぶときの基準を提示します。
4 種類の概観
| 形式 | アルファベット | 文字数 | パディング | バイト数 N に対する出力長 | 大文字小文字 |
|---|---|---|---|---|---|
| Base64url | A-Z a-z 0-9 - _ | 64 | =(省略可) | ⌈4N/3⌉ | 区別あり |
| Base32 (RFC 4648) | A-Z 2-7 | 32 | = | ⌈8N/5⌉ | 区別なし |
| Base58 (Bitcoin) | 1-9 A-H J-N P-Z a-k m-z | 58 | なし | 約 1.37 N(可変) | 区別あり |
| hex | 0-9 a-f | 16 | なし | 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_b64decodeは padding 必須(足りないと例外) - 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 4648 | Crockford |
|---|---|---|
| アルファベット | A-Z 2-7 | 0-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_decode か base58check_decode か)。
罠:性能と長さ
Base58 のエンコード・デコードは内部で大きな整数の除算を行うため、Base64 や hex と比べて実装が複雑で遅い。短さと可読性のトレードオフです。
hex:人が比較する・他のシステムが必ず読める用途
ハッシュ値の表示・バイナリのデバッグダンプの定番。5d41402abc4b2a76b9719d911017c592 のような形。
- アルファベット 16 文字なので、出力サイズは入力の 2 倍に膨らむ(Base64 系より長い)
- どの言語・どの環境でも標準ライブラリで扱える互換性の高さ
- 人が「ここまでは一致するな」と前方一致を目視で比較しやすい
罠:大文字/小文字どちらに揃えるか
仕様上は同値ですが、暗黙の慣習があります。
- SHA / MD5 / HMAC 系の hex 出力は 小文字(OpenSSL も Python
hashlibも小文字) - イーサリアムのアドレスは 大文字小文字混在で checksum を表現(EIP-55)
「全部小文字に統一して比較」のような正規化を入れる前に、大文字混在に意味があるかどうかを仕様で確認します。
選び方:4 つの質問
新しい用途で符号化を決めるとき、以下の順で絞り込めます。
URL や HTTP ヘッダに直接入れるか? YES → Base64url(短い)か Base58(更に短い、暗号系で実績)。 NO → 次へ。
人が読み上げる、または紙に書き写すか? YES → Base32(RFC 4648 / Crockford のいずれか)。 NO → 次へ。
他のシステムが必ず読める互換性が最優先か? YES → hex。 NO → 次へ。
長さを最小にしたいか? YES → Base64url。 NO → 用途に合わせて Base32(人寄り)か hex(機械寄り)。
まとめ
- Web のトークンに入れる → Base64url(padding 省略は受信側の互換に注意)
- TOTP・DNS・電話で読む → Base32(4648 / Crockford / Base32hex の差を確認)
- 暗号通貨アドレス・短縮 ID → Base58(Base58Check との違いに注意)
- ハッシュ・低レベル表示 → hex(大文字混在 checksum 仕様の有無を仕様書で確認)
すべて「バイト列を文字列にする」操作ですが、設計者が想定した読み手(機械か人か、人ならどう読むか)が違います。具体的なバイト列の挙動を試したいときは Base64 ツール で encode / decode を確認できます。