Base64 vs Hex:核心的なトレードオフ
Base64とHexエンコーディングの選択は一つの質問に帰着します:コンパクトさが必要か、可読性が必要か?Hexエンコーディングは各バイトを2つの16進文字(00-FF)に変換し、入力サイズのちょうど2倍の出力を生みます。Base64は3バイトごとに4文字に変換し、入力サイズの1.33倍の出力を生みます。1MBのファイルで:Hexは2MB、Base64は1.33MB。Base64はサイズで勝ち、Hexはシンプルさで勝ちます。
Hexは読みやすくデバッグしやすいです。各バイトが正確に2文字にマッピングされるので、データをバイト単位で視覚的に解析できます。16進文字列"48656c6c6f"は明らかに5バイトで、各ペアをASCIIテーブルで調べられます(48=H, 65=e, 6c=l, 6c=l, 6f=o)。Base64の"SGVsbG8="はよりコンパクトですが、個々のバイトを目視で確認できません——6ビットのグルーピングがバイト境界をまたぐからです。
どちらもテキストセーフなエンコーディングです——任意のバイナリデータを印刷可能なASCII文字に変換します。違いは文字セット:Hexは16文字(0-9, a-f)、Base64は64文字(A-Z, a-z, 0-9, +, /)を使います。シンボルあたりの文字数が多いほど、1文字あたりの情報量が多くなります。これがBase64がよりコンパクトな理由です。10進数が2進数より短いのと同じ原理です。
Hexエンコーディングを使うべき場面
ハッシュ出力:SHA-256は32バイトを生成します。Hexでは64文字:"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"。すべてのハッシュツール、すべてのドキュメント、すべてのAPIがハッシュ値にHexを使います。普遍的な慣例です。hash-generatorツールはデフォルトでHexを出力します。開発者が見て比較することを期待するフォーマットだからです。
バイナリプロトコルのデバッグ:ネットワークパケット、ファイルヘッダー、メモリダンプを見るとき、Hexが標準的な表現です。WiresharkはHexを表示します。バイナリエディタはHexを表示します。PNGファイルの最初のバイトは"89504e47"——これらのマジックナンバーを覚えられます。Base64では同じバイトが"iVBORw=="になり、認識しにくくドキュメントで検索しにくいです。
カラーコード:CSSカラー(#FF6B35)、MACアドレス(00:1A:2B:3C:4D:5E)、UUID(550e8400-e29b-41d4-a716-446655440000)はすべてHexを使います。各コンポーネントがバイトにきれいにマッピングされるからです。カラーは3バイト(RGB)、MACアドレスは6バイト、UUIDは16バイト。Hexはこのバイトレベルの構造を視覚的に保持します。
小さなバイナリ値:100バイト以下のデータ(暗号鍵、短いバイナリ識別子、プロトコルヘッダー)では、Hexの2倍のオーバーヘッドは許容範囲で、可読性の利点に見合います。32バイトの暗号鍵で、Hexは64文字 vs Base64の44文字。20文字の差はほとんど問題になりませんが、64個のHex文字を数えて「確かに32バイトだ」と視覚的に確認できるのは開発中に便利です。
Base64エンコーディングを使うべき場面
メール添付ファイル(MIME):元々のユースケースです。SMTPは7ビットASCIIなので、バイナリ添付ファイルはテキストエンコードが必要です。Base64の33%オーバーヘッドは、数メガバイトのファイルでHexの100%オーバーヘッドに大きく勝ります。送ったことのあるすべてのメール添付ファイルがBase64を使っています。MIME標準(RFC 2045)は76文字ごとに改行するBase64を規定しています。
JSON/XMLへのバイナリ埋め込み:APIがテキストベースのフォーマットに画像、ファイル、バイナリブロブを含める必要があるとき、Base64が標準です。AWS S3の署名付きPOSTポリシーはBase64エンコードされたJSONを使います。JWTはペイロードをBase64urlでエンコードします。HTMLのData URI(data:image/png;base64,...)はマークアップに直接画像を埋め込みます。33%のオーバーヘッドはテキスト安全性のコストです。
テキスト環境での大きなバイナリデータ:数百バイト以上でサイズが重要な場面では、Base64が勝ちます。100KBの画像はHexで200KB、Base64で133KB。ネットワーク接続上で67KBの差は積み重なります。base64-encoderツールは最大50MBのファイルを扱い、エンコード後のサイズを表示してオーバーヘッドを評価できます。
認証トークンとCookie:OAuthトークン、セッションID、APIキーはBase64またはBase64urlエンコーディングをよく使います。コンパクトな表現はHTTPヘッダー(実用的なサイズ制限約8KB)やCookie(Cookieあたり4KB制限)に適しています。Base64url(+の代わりに-、/の代わりに_を使用)は追加のパーセントエンコーディングなしにURLセーフな環境向けに設計されています。
URLエンコーディング:特殊なケース
URLエンコーディング(パーセントエンコーディング)は厳密にはバイナリからテキストへのエンコーディングではありません——文字列をURLで安全にするテキストからテキストへのエンコーディングです。各安全でないバイトは%XX(パーセント記号 + 2桁の16進数)になります。スペースは%20、アンパサンドは%26、日本語の文字(3バイトのUTF-8)は%E4%B8%ADのようになります。オーバーヘッドは大きく変動します:ASCII文字は0%ですが、すべて特殊文字の文字列は3倍に膨張します。
URLエンコーディングはコンテキスト依存です。URLパスでは/は構造的(エンコードしない)。クエリパラメータ値では/はデータ(エンコードする)。フラグメントではほとんどエンコード不要。このコンテキスト依存性がURLエンコーディングをBase64やHexと根本的に異なるものにしています。後者はコンテキストに関係なくすべてを均一にエンコードします。url-encoderツールでコンテキスト選択付きのインタラクティブなエンコーディングができます。
URLエンコーディングがBase64と出会うとき:Base64データをURLに入れる必要がある場合、標準Base64の+と/文字はパーセントエンコーディング(%2Bと%2F)が必要です。これがBase64urlが存在する理由です——+を-に、/を_に置き換えて二重エンコーディングを回避します。JWTがBase64urlを使うのは、URLやHTTPヘッダーに出現するからです。URLで伝送されるデータには常にBase64url(標準Base64ではなく)を使いましょう。
典型的なユースケースのサイズ比較:バイナリ文字列"Hello, World! こんにちは"(22バイトのUTF-8)のエンコーディング。Hex:44文字。Base64:32文字。URLエンコーディング:"Hello%2C%20World%21%20%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF" = 67文字。URLエンコーディングはバイナリデータに最悪です。各バイトを3文字(%XX)にエンコードしつつASCII文字はそのまま残すため、オーバーヘッドは入力内容に完全に依存します。
生バイナリ:テキストエンコーディングが間違いな場面
答えが「エンコードしない」ときもあります。送信側と受信側の両方がバイナリデータを扱えるなら、テキストエンコーディングは不要なオーバーヘッドと処理時間を追加するだけです。Content-Type: application/octet-streamのHTTPレスポンスは生バイトを送ります。WebSocketバイナリフレームは生バイトを送ります。multipart/form-dataのファイルアップロードは生バイトを送ります。Protocol BuffersとMessagePackはJSON+Base64より小さく速いバイナリシリアライゼーションフォーマットです。
ルール:転送チャネルがテキストを要求する場合のみテキストエンコーディング(Base64、Hex)を使います。メール(SMTP)はテキスト要求 → Base64を使う。JSONペイロードはテキスト要求 → バイナリフィールドにBase64。URLパラメータはテキスト要求 → URLエンコーディングまたはBase64url。しかしHTTPボディ、WebSocketメッセージ、gRPC呼び出し、ファイルI/Oはすべてネイティブにバイナリをサポートします——これらのチャネルでバイナリデータをエンコードするのは帯域幅とCPUの無駄です。
パフォーマンス比較:1MBのバイナリデータのエンコーディング。Base64エンコーディングは約2msで1.33MBを生成。Hexエンコーディングは約3msで2MBを生成。生バイナリの送信はエンコーディング時間0msで1MB。5MBの画像を返すREST APIで、JSONにBase64エンコードすると6.67MBの転送 + エンコード/デコードのCPU時間。別のバイナリレスポンスとして提供すると5MBの転送 + ゼロのエンコーディングオーバーヘッド。大きなペイロードでの選択は明白です。
ハイブリッドアプローチ:メタデータにJSON、ペイロードにバイナリを使います。一般的なパターン:APIがバイナリリソースへのURLを含むJSONを返し、クライアントが別途取得します。これにより構造化されたメタデータ(タイトル、サイズ、content-type)はテキストフレンドリーなフォーマットで、実際のデータは効率的なバイナリ転送で得られます。すべてのCDN、オブジェクトストレージサービス、メディアプラットフォームがこのパターンを使っています。
比較表と判断ガイド
サイズオーバーヘッド:生バイナリ = 0%。Base64 = 33%。Hex = 100%。URLエンコーディング = 0-200%(内容による)。サイズに敏感なアプリケーション(モバイルAPI、組み込みシステム、高スループットサービス)では、可能な限りバイナリ転送でエンコーディングオーバーヘッドを最小化し、テキストエンコーディングが必要な場合はBase64を使いましょう。
可読性:Hexは開発者にとって最も読みやすい(バイト整列、デバッグツールで馴染み深い)。Base64はコンパクトだが不透明(個々のバイトを視覚的に解析できない)。URLエンコーディングはASCIIテキストには読みやすいがバイナリには醜い。生バイナリはHexビューアなしでは読めない。開発とデバッグ中に人間がデータを検査する必要があるかどうかで選びましょう。
互換性:URLエンコーディングはURL内で機能します(定義上)。Base64はJSON、XML、メール、ほとんどのテキスト環境で機能します。Hexはテキストが使えるところならどこでも機能しますが、大きなデータの標準的な選択になることは稀です。生バイナリはHTTPボディ、WebSocket、ファイル、バイナリプロトコルで機能しますが、JSONやURLでは使えません。エンコーディングを転送チャネルの要件に合わせましょう。
判断ツリー:転送チャネルはバイナリ対応か?→ 生バイナリを使う。URLか?→ テキスト値にはURLエンコーディング、バイナリ値にはBase64url。JSON/XMLか?→ バイナリフィールドにBase64。デバッグ/表示用か?→ Hex。ハッシュや暗号値か?→ Hex(慣例)。メール添付か?→ Base64(MIME標準)。迷ったら、バイナリをテキストにするシナリオではBase64が最も安全なデフォルトです。
16バイトのバイナリデータ(UUID)のエンコーディング比較:
生バイナリ: 16バイト(テキストとして表示不可)
Hex: "550e8400e29b41d4a716446655440000"(32文字)
Base64: "VQ6EAOKbQdSnFkRmVUQAAA=="(24文字)
Base64url: "VQ6EAOKbQdSnFkRmVUQAAA"(22文字、パディングなし)
1KBのバイナリデータのエンコーディング比較:
生バイナリ: 1,024バイト
Hex: 2,048文字(+100%)
Base64: 1,368文字(+33%)
URLエンコード:~3,072文字(+200%、最悪ケース)
速度(10MBエンコーディング、Node.js M1 Mac):
Buffer.toString('hex'): ~8ms
Buffer.toString('base64'): ~5ms
エンコードなし(生): ~0msエンコーディングフォーマット選択でよくある間違い
間違い1:既にテキストのデータをBase64エンコードする。JSON文字列を別のJSONフィールドに入れる前にBase64エンコードすると、無意味に33%のオーバーヘッドを追加しています。JSONはJSON を含められます(エスケープされた文字列またはネストされたオブジェクトとして)。実際のバイナリデータ(画像、ファイル、暗号素材)のみBase64エンコードしましょう。テキストデータはテキストのままにしましょう。
間違い2:大きなバイナリペイロードにHexを使う。10MBのファイルをHexにすると20MB——Base64(13.3MB)や生バイナリ(10MB)と比べて10MBの純粋な無駄です。Hexは表示とデバッグ用であり、データ転送用ではありません。APIでHexエンコードされたファイルを送っているなら、Base64かバイナリに切り替えて即座に33-50%の帯域幅を節約しましょう。
間違い3:同じシステム内でエンコーディングフォーマットを混在させる。一部のバイナリフィールドをHexで、他をBase64で返し、どちらがどちらかドキュメントに書いていないAPIを見たことがあります。API内のすべてのバイナリデータに一つのフォーマットを選び、明確にドキュメント化しましょう。一貫性がHexをBase64としてデコードする(またはその逆の)バグを防ぎます。
間違い4:デコードコストを考慮しない。エンコードとデコードは無料ではありません——CPUとメモリを消費します。毎秒数百万リクエストを処理する高スループットサービスでは、Base64エンコード/デコードの累積コストが無視できなくなります。JSONに入れるためだけにデータをエンコードし、反対側で即座にデコードしているなら、バイナリプロトコル(gRPC、MessagePack、Protocol Buffers)がこのオーバーヘッドを完全に排除できないか検討しましょう。