ハッシュ・暗号化・エンコーディング: 人々が混同する 3 つ

エンコーディング・暗号化・ハッシュは別々の問題を解きます。これらを取り違えることが、パスワードが漏れトークンが偽造される原因です。その正確な区別を整理します。

会話やコードレビューで 3 つの操作が同じ意味で使われ、その混同は学術的では ありません。平文も同然のパスワード保存や、誰でも読めるトークンの直接の 原因です。エンコーディング・暗号化・ハッシュはどれもデータを変換しますが、 別々の問いに答え、別々の可逆性を持ちます。間違ったものを選ぶと、失敗は静か です。コードは動き、テストは通り、持っているつもりだったセキュリティ特性は 存在しません。

たった 1 つの重要な区別

アルゴリズムを取り払うと、問いは 2 つです。

  • 元に戻せるか? エンコーディングと暗号化は元に戻せます。ハッシュは 戻せません。
  • 戻すのに秘密が要るか? 暗号化は鍵が要ります。エンコーディングは何も 要りません。変換は公開されています。

これで 3 つのバケツになります。

元に戻せるか? 鍵が要るか? 目的
エンコーディング はい いいえ 表現 / 転送 Base64、16 進数、URL パーセントエンコーディング、UTF-8
暗号化 はい はい 機密性 AES (対称)、RSA / ECC (非対称)
ハッシュ いいえ いいえ 完全性、指紋、参照 SHA-256、SHA-3、BLAKE3

「鍵が要るか」の列が、人々が飛ばす列であり、何かがそもそもセキュリティ管理 なのかを決める列です。エンコーディングは鍵が要らないので機密性を提供しません。 それだけです。誰でも秘密なしで逆変換を走らせられるなら、データは丸見えです。

エンコーディング: 表現を変える

エンコーディングはバイトをあるアルファベットから別のアルファベットへ写像し、 元のままでは運べないチャネルを通過させます。Base64 は任意のバイナリを 64 個の 印字可能な ASCII 文字に変え、JSON・メール・URL に収まるようにします。 パーセントエンコーディングは URL で意味を持つ文字をエスケープします。16 進数 はバイトを 0f のペアとして表示用にレンダリングします。UTF-8 はコード ポイントをバイト列にエンコードします。

このどれも秘密を伴わず、それがまさに要点です。デコーダはあらゆる標準ライブラリ に組み込まれています。ブラウザの atob()、Python の base64.b64decode、 コマンドラインの base64 -d はどれも鍵なしで即座に Base64 を戻します。

エンコーディングは、問題が のときに適した道具です。テキスト専用の フィールドを通る必要のあるバイナリ、URL セーフであるべきファイル名、表示される べきハッシュダイジェストなどです。問題が 秘匿 になった瞬間に間違った道具に なります。それをまったく提供しないからです。

暗号化: 鍵による機密性

暗号化は平文を、鍵なしでは戻せない暗号文に変換します。3 つのうち機密性を守る 唯一のものであり、鍵がそれを真にするものです。

2 つの系統があります。

  • 対称 (AES、ChaCha20) — 同じ鍵が暗号化し復号します。速く、大量データに 使います。ディスク暗号化、TLS レコードのペイロード、データベースの列の暗号化 です。
  • 非対称 (RSA、ECC) — 公開鍵が暗号化し、秘密鍵が復号します (署名の場合は その逆)。より遅く、事前共有の秘密なしに信頼をブートストラップし、対称鍵を 交換するのに使います。

暗号化が押しつけるトレードオフは鍵管理です。暗号化されたデータは鍵とちょうど 同じだけ安全であり、鍵はどこかに存在しなければなりません。KMS、HSM、環境変数、 設定ファイルです。漏れた鍵は、すべての暗号文をさかのぼって平文に戻します。 「うちは暗号化している」は、鍵がどこにあり誰が触れられるかを答えられるまで、 未完成の文です。

ハッシュ: 一方向の指紋

ハッシュ関数は任意サイズの入力を固定サイズのダイジェストに写像し、その定義的な 特性は逆向きに実行できないことです。鍵もデコード手順もありません。ダイジェスト が与えられたとき、それを生む入力を見つける唯一の方法は、1 つが一致するまで 入力を試すことです。

その不可逆性が機能です。ハッシュは、データ自体を保存したり明かしたりせずに 「これは同じデータか?」「これは変わったか?」に答えます。完全性チェック (ダウンロードしたファイルのダイジェスト比較)、重複排除、コンテンツアドレス ストレージ (Git のコミット ID)、ハッシュテーブル参照を支えます。

はっきり述べておくべき微妙な点が 1 つあります。MD5 と SHA-1 は 衝突耐性の点で 破られています。攻撃者が同じダイジェストを持つ別々の 2 つの入力を構成でき、 これは署名や証明書に致命的です。それでも 非セキュリティ用のチェックサム と しては今でも完全に問題ありません。偶発的な破損の検出だけを気にし、衝突を細工 する敵がいない場合です。攻撃者が影響を与えられるものには SHA-256 や BLAKE3 を 使ってください。MD5 は ETag やキャッシュキーとしてなら許容できます。

混同と、それが生むバグ

「パスワードを保存前に Base64 にする」

これはセキュリティと取り違えられたエンコーディングです。Base64 には鍵が ありません。保存された値は変装したパスワードです。テーブルの読み取り権限を 持つ誰もが、デコード 1 回ですべての資格情報を手にします。これは本来あるべき よりも頻繁に実際の侵害に現れます。Base64 は転送用であって、秘密の保存用では 決してありません。

「パスワードを SHA-256 にする」

より近いですが、それでも間違っており、その理由が微妙なので慎重なエンジニアでも 出荷します。SHA-256 は不可逆で、これはパスワード保存が求めるまさにそれのように 感じられます。問題は、それが 速い ことです。現代の GPU は毎秒数十億の SHA-256 ハッシュを計算します。ハッシュテーブルを盗んだ攻撃者は、辞書攻撃や総当たりを オフラインで途方もない速度で走らせ、弱いパスワードは数秒で崩れます。

パスワード保存には、遅く、意図的に高価な鍵導出関数 が必要です。bcrypt、 scrypt、または Argon2 です。これらは調整可能です。コスト係数を設定してハッシュ 1 つにたとえば 100ms かかるようにすると、ログインでは気づかれませんが、 オフラインのクラッキングは数千倍高価になります。

そしてすべてのパスワードには ユーザーごとのソルト が必要です。ハッシュと 一緒に保存され、その中に混ぜ込まれるランダムな値です。ソルトがなければ、同じ パスワードを使う 2 人のユーザーが同じダイジェストを得て、攻撃者はよくある パスワードのハッシュ表 (レインボーテーブル) を一度だけ事前計算し、漏れた あらゆるデータベースに対して永遠に照合します。ユーザーごとに固有のソルトは 事前計算を完全に無力化します。攻撃者は各エントリを別々に破る必要があります。 現代の KDF はソルトを生成して一緒に埋め込んでくれます。bcrypt は出力文字列に それを保存します。

ペッパー は関連するが別の考えです。すべてのパスワードにハッシュ前に加える 秘密値で、ハッシュの中ではなくデータベースと分離して (アプリの設定や KMS に) 保存します。ソルトはデータベースが漏れたときに事前計算を防ぎ、ペッパーは データベースだけの漏洩では露出しない第 2 の要素を加えます。これは多層防御の 手段であって、まともな KDF とソルトの代わりではありません。

「API トークンを暗号化する」

正しいこともあれば、間違った道具のこともよくあります。後でトークンの平文を 復元 する必要があるなら、ユーザーに代わって上流の API へ転送するためなら、 暗号化が正しく、鍵管理がそのコストです。しかし、入ってくるトークンを保存済みの ものと 照合 するだけなら、何も復元する必要はなく、暗号化は守るべき鍵を増やす 過剰です。トークンのハッシュを保存し、ハッシュを比較してください。そして問いが 「このメッセージは名乗るとおりの相手から、改ざんなしで来たか?」なら、答えは たいてい HMAC です。Webhook の ペイロードのようなメッセージを認証するために作られた鍵付きハッシュであって、 暗号化ではまったくありません。

Web アプリでそれぞれが使われる場所

  • エンコーディング — JSON や URL 用のバイナリの直列化、data URI の構築、 ダイジェストの表示用 16 進レンダリング。Base64 は暗号化ではない という区別は身につける価値があります。トークンやペイロードのいたるところに あり、何も守りません。
  • 暗号化 — 読み返す必要のある保存データ (PII の列、暗号化されたバックアップ)、 転送中のデータ (TLS)、平文で復元する必要のあるトークン。常に鍵管理の話と 対になります。
  • ハッシュ — パスワード保存 (生の速いハッシュではなく遅い KDF + ソルトで)、 完全性チェック、重複排除、参照キー、そしてメッセージ認証用の HMAC。

頭の中の近道です。鍵がなければエンコーディングかハッシュであり、何の機密性も 守りません。元が戻ってくる必要があればエンコーディングか暗号化であって、決して ハッシュではありません。そしてパスワードを保存するなら、汎用の道具はどれも 当てはまりません。目的に作られた KDF に手を伸ばしてください。

デバッグ中にダイジェストを計算したり比較したりする必要があるとき、私たちの ハッシュ生成器 は SHA-256 やその他のダイジェストを ブラウザ内で生成するので、シェルに何かをパイプせずにチェックサムを検証したり、 2 つの値が一致するかを確認したりできます。