要点
Base64 は、任意のバイト列を 64 種類の安全な文字(A–Z、a–z、0–9、+、/)だけで書き表す方法です。画像、PDF、鍵といったバイナリデータを、テキストしか扱えないシステム(メール本文、JSON の文字列、URL、HTML の属性)に通すために存在します。これはエンコードであって暗号化ではありません。鍵はなく、デコードはどの言語にも、どのブラウザの開発者コンソールにもある関数を 1 回呼ぶだけです。「Base64 で」保存されたパスワードは、手間を一つ増やしただけの平文です。
なぜ存在するのか
メールは 1970 年代に 7 ビット ASCII 向けに設計されました。添付ファイルが登場したとき(MIME、1992 年)、JPEG のバイト列は 256 通りの値をすべて使い、その中にはメールサーバーにとって「行末」や「メッセージ終了」を意味する値も含まれていたため、無害なテキストに偽装する必要がありました。その答えが Base64 です。3 バイト(24 ビット)を取り、6 ビットずつ 4 つに分け、それぞれを 64 文字のどれかに対応させます。3 バイトが 4 文字になる。これが有名な 33% のオーバーヘッドで、3 MB の添付ファイルは 4 MB のメールになります。「25 MB」の上限に収まるファイルが 18 MB なのはこのためです。
同じ手法は、テキストしか通らない場所ならどこにでも現れます。小さな画像を Web ページに直接埋め込む(data:image/png;base64,…)、HTTP ヘッダーにバイナリのトークンを入れる(Authorization: Basic …)、PEM ファイルで証明書や鍵を運ぶ、JSON のフィールドにバイト配列を詰め込む、といった場面です。
つまずきやすいバリエーション
- 標準形式と URL セーフ形式。 標準の Base64 は
+と/を使いますが、どちらも URL の中では特別な意味を持ちます。URL セーフのアルファベット(RFC 4648 §5)はこれを-と_に置き換えます。一方の形式で作ったトークンをもう一方としてデコードすると、この 2 文字で失敗します。JWT は URL セーフ形式です。 - パディング。 出力の長さは常に 4 の倍数で、足りない分は
=で埋めます。パディングを省く生成側もあれば(これも JWT)、パディングを必須とするデコーダーもあります。ほかに問題のない文字列でデコードが失敗したら、長さが 4 で割り切れるまで=を足してみてください。 - 改行。 MIME は 76 文字、PEM は 64 文字で折り返します。デコーダーの多くは空白を無視しますが、すべてではありません。
- 元になる文字コード。 Base64 がエンコードするのはバイトです。文字列を Base64 にするには先にバイト列に変換する必要があり、UTF-8、UTF-16、Latin-1 のどれを選ぶかで結果が変わります。JavaScript の定番バグ、
btoa("é")が例外を投げる現象はまさにこれで、btoaは Latin-1 しか受け付けません。先に UTF-8 のバイト列にエンコードしてください。
Base64 変換は両方のアルファベット、パディング、改行、UTF-8 に対応し、テキストとしても、ダウンロード可能なファイルとしてもデコードできます。data: URL やメールの添付ファイルを、ブラウザの中で元のファイルに戻せます。
エンコード、ハッシュ、暗号化
この 3 つの言葉はくだけた文章では混同されがちですが、意味はまったく異なります。
| 元に戻せるか | 鍵が必要か | 目的 | |
|---|---|---|---|
| エンコード(Base64、16 進、URL エンコード) | はい、誰でも | いいえ | データを伝送路に合わせる |
| ハッシュ(SHA-256、MD5) | いいえ | いいえ | データの指紋を取り、改変されていないか確かめる |
| 暗号化(AES、RSA) | はい、鍵があれば | はい | 鍵を持たない人からデータを秘匿する |
ファイルのハッシュを使えば、ダウンロードしたものが配布元の公開したものと一致するかを確認できます。ハッシュ計算はブラウザ内で MD5、SHA-1、SHA-256/384/512 を計算し、貼り付けたチェックサムと照合します。ただし何かを隠すわけではなく、ファイルはファイルのままです。3 つのうち秘匿性を提供するのは暗号化だけで、その強さは鍵の強さで決まります。そこで必要になるのが十分にランダムなパスワードです。
秘密情報を Base64 にしようとしているなら
やめてください。具体的には次のとおりです。
- Base64 のパスワードを書いた設定ファイル(古い Java や .NET のアプリでよく見るパターン)は、ファイルを開いた人にとっては平文のパスワードです。
- フロントエンドの JavaScript に「難読化」して置いた API キーは、Base64 であろうとなかろうと、ネットワークタブで丸見えです。
- Basic 認証を素の HTTP で使うと、ユーザー名とパスワードが Base64 エンコードされた状態、つまり平文で送られます。許容できるのは、通信路が暗号化を担う HTTPS の中だけです。
データを保存または送信し、なおかつ秘密にしておく必要があるなら、本物の暗号(プラットフォームの暗号ライブラリ経由の AES-256-GCM)で暗号化し、そのうえでテキストの伝送路を通す必要があれば暗号文を Base64 にします。暗号化のあとにエンコードするのは問題ありません。誤りなのは、暗号化の代わりにエンコードすることです。