[GUIDE] Base64とは何か、どのように動作するのか?
原理から理解する実践的な解説。Base64の根幹にある6ビットグルーピングの仕組み、なぜ出力が33%大きくなるのか、=パディングはどこから来るのか、そしていつ使うべきかを学びます。
// 一文で表す定義
Base64とは、64個の印字可能なASCII文字だけを使ってバイナリデータをテキスト表現する方式です。具体的にはA–Z、a–z、0–9、そして2つの記号(通常は + と /、パディングマーカーとしての =)を使用します。これがすべてです。バイナリの3バイトごとに4つのBase64文字へ再グルーピングされ、それ以外は何も変わりません。
Base64はエンコーディングであり、暗号化ではありません。誰でもミリ秒単位で復元できます。その目的はプライバシーではなく、安全な転送です。Base64を使えば、バイナリペイロード(画像、証明書、PDFファイル、暗号鍵など)を、テキストしか受け付けない場所 — HTML属性、JSON文字列、URL、メール本文、YAML設定、データベースのTEXTカラム、環境変数 — に載せることができます。
// そもそもなぜ必要なのか
テキスト専用チャネルは、認識できないバイトを破棄したり改変したりします。7ビットASCIIしか理解しないメールゲートウェイは、英語以外の文字の最上位ビットをすべて壊してしまいます。JSONパーサーは埋め込まれたヌルバイトを拒否します。スペース、引用符、アンパサンドが直接含まれるURLは、パーセントエンコードしない限り無効です。1990年代のSMTP標準は、メール本文が常に英語のプレーンテキストであることを前提としていましたが、人々は写真やスプレッドシートも送りたかったのです。
Base64はこれを解決するために、既知のあらゆるテキストチャネルを無傷で通過する64文字に出力を制限します。26個の大文字、26個の小文字、10個の数字、そして (a) 印字可能で、(b) 一般的なエスケープ機構で使われず、(c) ASCIIを通してラウンドトリップできるほど十分に区別できる2つの記号です。さらに、これも安全なパディング文字 = も含まれます。
// 6ビットグルーピングの仕組み
Base64の機械的な核心を一行で表すと、次のようになります。入力をビットストリームとして受け取り、6ビットの塊に分割して、それぞれを64文字のアルファベットで参照する。
なぜ6ビットなのでしょうか?2^6 = 64だから、6ビットの塊は正確にアルファベットの1文字に対応します。なぜ7ビットや8ビットではないのか?7ビットでは128の値となり多すぎます(すべてが印字可能ではありません)。8ビットでは単なる生のバイナリに戻ってしまいます。6ビットは、あらゆる可能な値に人間が読める文字が割り当てられるスイートスポットなのです。
入力3バイト = 24ビット = ちょうど6ビットの4グループ = 4つのBase64文字。この「3入力4出力」の比率は固定されています。これがBase64出力が入力の約4/3倍、つまり約33%大きくなる理由です。
// Worked example: encoding the 3 bytes 'Man' (77 97 110)
// ASCII: M a n
// Binary: 01001101 01100001 01101110
// Re-group into 6-bit chunks:
// 010011 010110 000101 101110
// Decimal: 19 22 5 46
// Base64: T W F u
// Result: 'TWFu' (stored as 4 ASCII bytes: 84 87 70 117)
// Base64のアルファベット
RFC 4648 §4が標準アルファベットを定義しています。位置0–25はA–Z、26–51はa–z、52–61は0–9、位置62は +、位置63は / です。
URLセーフな派生版(RFC 4648 §5、base64urlとしても知られる)は、+ を - に、/ を _ に置き換えています。これにより、追加のエスケープなしで出力をURLやファイル名に安全に埋め込むことができます。どちらのバリアントも、対応するアルファベットを使えば同一にデコードできます。
// Standard Base64 alphabet (index → character)
// 0–25: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
// 26–51: a b c d e f g h i j k l m n o p q r s t u v w x y z
// 52–61: 0 1 2 3 4 5 6 7 8 9
// 62: + (or - in URL-safe)
// 63: / (or _ in URL-safe)
// pad: =
// = パディングはどこから来るのか
このアルゴリズムは、3バイトの倍数の入力サイズを想定しています(そうすれば4つのBase64文字にちょうど分割できるからです)。入力長が3の倍数でない場合、1〜2バイトが余り、最後の4文字出力グループを埋めるには不足します。= でパディングすることで、何バイトが不足したかを示します。
• 入力長 mod 3 == 0 → パディングなし(例:'Man' → 'TWFu')。
• 入力長 mod 3 == 1 → パディング文字2つ(例:'M' → 'TQ==')。
• 入力長 mod 3 == 2 → パディング文字1つ(例:'Ma' → 'TWE=')。
// Why 'M' becomes 'TQ==':
// Byte: M = 01001101 (8 bits)
// Padded to 12 bits with zeros: 010011 010000
// Decimal: 19, 16 → 'T', 'Q'
// Output length must be multiple of 4 → add '==' padding
// Result: 'TQ=='
//
// When decoding, the decoder strips '==' and recovers the first 8 bits,
// discarding the trailing zero bits.
// 33%のオーバーヘッドは正確なのか?
ほぼ正確ですが、厳密には異なります。正確な計算式は、長さnのバイト入力に対して ceil(n / 3) × 4 文字です。100 KBの入力であれば ceil(102400 / 3) × 4 = 136,534 文字となり、100 KBより33.3%大きくなります。実際には、HTTPレベルでgzipやBrotliによる圧縮を加えると、ワイヤーに乗せた際のコストは10〜15%程度に近づきます。Base64テキストはそれなりに圧縮が効くからです(生のバイナリほどではありませんが)。
小さなペイロードではパディングが効いてきます。1バイトのエンコードには4文字(サイズの4倍)、2バイトのエンコードでも4文字かかります。そのため、小さなBase64文字列は入力に比べて意外なほど長く見えるのです。
// Base64が実世界で使われる場所
-
>
HTML/CSS内のデータURI — アイコンやロゴをインラインで埋め込む:
<img src="data:image/png;base64,…"> - > JSONとGraphQLペイロード — テキストプロトコル内でバイナリ(ファイルアップロード、サムネイル)を送る必要があるAPI
- > JWTトークン — JWTの3つのドット区切りパーツはすべてbase64url文字列
- > HTTP Basic認証 — username:password は Authorization ヘッダーでBase64エンコードされる(TLSなしでは依然として安全ではない!)
- > メール(MIME) — 添付ファイルは、レガシーな7ビットSMTPサーバーを通過できるようBase64エンコードされる
- > PEM形式の証明書と鍵 — -----BEGIN CERTIFICATE----- ブロックはBase64エンコードされたDERバイナリをラップする
- > SSHキー — id_rsa.pub や authorized_keys の各行はBase64エンコードされた公開鍵
- > データベースのTEXTカラム — BLOB型が使えないとき、Base64を使えばバイナリをテキストとして保存できる
- > 環境変数 — Kubernetes SecretsはBase64エンコードされている(ただし暗号化ではない)
- > QRコードとマジックリンク — URLに埋め込んでも安全な短いBase64トークン
// BASE64は暗号化ではない
これは最も一般的な誤解です。Base64はデータの内容を隠しません。公開され十分に文書化されたマッピングを使って、バイナリをテキストに書き換えるだけです。パスワードが cGFzc3dvcmQ= としてBase64エンコードされていても、それは平文の password とまったく同じ脆弱性を持ちます。誰でも1回の関数呼び出しでデコードできます。Base64は16進数と同じように扱ってください。それは表現であって、保護ではありません。
プライバシーが必要なら、まず平文に本物の暗号プリミティブ(AES-GCM、ChaCha20-Poly1305、age、PGP)を適用し、そのうえで暗号文をテキストチャネルで転送する必要があるならBase64エンコードします。Base64のステップはラストマイルであって、セキュリティ層ではありません。
Kubernetes Secretsは典型的な落とし穴です。Kubernetesはシークレット値をBase64エンコードして保存するため、漠然と保護されているように見えます。しかしそうではありません。namespace への読み取りアクセス権を持つ誰もがそれを復元できます。真のシークレット保護には、SealedSecrets、Vault、SOPS、あるいはクラウドネイティブなKMS統合などのツールが必要です。
// 各言語での手早いエンコード/デコード
// JavaScript (browser):
// btoa('Hello') → 'SGVsbG8='
// atob('SGVsbG8=') → 'Hello'
// (btoa/atob only handle Latin-1; use TextEncoder for Unicode)
//
// Node.js:
// Buffer.from('Hello').toString('base64') → 'SGVsbG8='
// Buffer.from('SGVsbG8=', 'base64').toString() → 'Hello'
//
// Python:
// import base64
// base64.b64encode(b'Hello') → b'SGVsbG8='
// base64.b64decode('SGVsbG8=') → b'Hello'
//
// Go:
// base64.StdEncoding.EncodeToString([]byte("Hello")) → "SGVsbG8="
//
// Ruby:
// Base64.strict_encode64('Hello') → 'SGVsbG8='
//
// Shell:
// echo -n 'Hello' | base64 → 'SGVsbG8='
// echo 'SGVsbG8=' | base64 -d → 'Hello'
// 避けるべき典型的なミス
-
>
URLセーフな文字列を標準アルファベットでエンコードする — 受信側のデコーダーは
+や/を拒否します。URL、Cookie、ファイル名に出力を載せるときはbase64urlを使いましょう。 - > 二重エンコード — すでにBase64のテキストをさらにエンコーダーに通してしまう。エンコード前に、そのデータがすでにBase64でないか必ず確認してください。
-
>
UTF-8を忘れる —
btoa('héllo')はブラウザで例外を投げます。éはLatin-1の範囲外だからです。まずTextEncoderで文字列をバイト列に変換してください。 -
>
パディング削除のミスマッチ — base64urlではしばしば
=パディングが省略されます。デコーダーが厳格なら、パディングを追加し直してください:input + '==='.slice((input.length + 3) % 4)。 - > Base64が安全だと思い込む — そうではありません。暗号化は別物です。常に。
- > 巨大なバイナリにBase64を使う — 500 MBのファイルを単一の文字列にエンコードするとメモリが尽きます。ストリーミングで処理しましょう。
-
>
行折り返しあり/なし — MIMEやPEMはBase64を64または76桁で
\n折り返しします。それ以外の多くの文脈では単一行が期待されます。適切に改行を除去または挿入してください。
// 30秒でわかるBase64 — チートシート
- > 64個の印字可能ASCII文字(A–Z、a–z、0–9、+、/)に加えて、= パディング
- > 3バイト入力 → 4文字出力(固定比)
- > 出力は入力より約33%大きい
- > 可逆:エンコーディングであって暗号化ではない
- > URLとファイル名にはbase64url(+ → -、/ → _ に置換)を使う
- > 出力長が4の倍数になるように、短い入力には1〜2個の = でパディングする
- > Unicodeテキストの場合:文字列をまずUTF-8バイト列にエンコードし、それをBase64化する
- > 主要言語の標準ライブラリすべてでデコード可能
- > HTML、JSON、URL、メール、PEMファイル内で安全
- > セキュリティ層としては安全ではない — 機密データには必ず本物の暗号化と組み合わせる
// 次のステップ
仕組みが分かったら、弊社の Base64エンコーダー や Base64デコーダー を試し、文字単位で出力を確認してみてください。URLセーフなペイロードには base64url に切り替えましょう。画像なら 画像 → Base64 をご覧ください。
関連する詳細記事:
• Base64 URLセーフ vs 標準 — どちらのアルファベットをいつ使うか
• UTF-8とUnicodeのためのBase64 — btoa() の落とし穴を避ける
• JavaScriptとNode.jsでのBase64 — atob、btoa、Buffer
• Base64 vs Base64URL vs Base32 — 比較表