一言で言うと
bcryptやArgon2のようなパスワードハッシュ化アルゴリズムは、ユーザーのパスワードを、元に戻せない長い文字列に変換します。これは計算に時間がかかり、コストが高くつくように設計されているため、たとえ攻撃者がユーザーデータベースを丸ごと盗んだとしても、パスワードをクラックするのは地獄のように難しくなります。
こいつが解決する問題
デジタルの暗黒時代、開発者たちはユーザーのパスワードを平文(プレーンテキスト)で保存していました。もしハッカーがデータベースに侵入すれば、全ユーザー名とそれに対応するパスワードが書かれたファイルを手に入れてしまうことになります。はい、ゲームオーバー。どんな大惨事になるか、想像つきますよね。
その次のステップが「ハッシュ化」でした。P@ssword123をそのまま保存する代わりに、MD5やSHA-1のようなアルゴリズムで生成された固定長の文字列、つまりそのハッシュ値を保存するようになったのです。ユーザーがログインする時、入力されたパスワードをハッシュ化し、保存されているハッシュ値と一致するかどうかを確認します。これは進歩でしたが、致命的な欠陥がありました。それは「レインボーテーブル」です。ハッシュ関数は同じ入力に対して常に同じ出力を生成する(例えばpasswordはMD5で常に5f4dcc3b5aa765d61d8327deb882cf99になる)ため、攻撃者たちは何十億もの一般的なパスワードのハッシュ値を事前に計算し、巨大なルックアップテーブルに保存しておいたのです。盗まれたハッシュ値は、ミリ秒単位で元のパスワードに逆引きされてしまいました。
次の進化は「ソルティング」でした。パスワードをハッシュ化する前に、「ソルト」と呼ばれるユニークでランダムな文字列を追加するのです。つまり、P@ssword123をハッシュ化する代わりに、P@ssword123 + aBcDeFg12345をハッシュ化するわけです。そして、そのソルトはハッシュ値と一緒にデータベースに保存されます。これにより、たとえ全員が「password」というパスワードを選んだとしても、各ユーザーのハッシュはユニークになるため、レインボーテーブルは完全に無力化されます。攻撃者は、ユーザー一人ひとりのユニークなソルトごとに新しいレインボーテーブルを作成しなければならなくなり、これは計算上、現実的ではありません。
しかし、新たな悪役が登場しました。ゴリゴリの計算能力です。最新のGPUやカスタムハードウェア(ASIC)は、盗まれた単一のハッシュに対して、1秒間に何十億ものソルト付きパスワードの組み合わせを試すことができます。SHA-256のようなハッシュアルゴリズムは、設計上高速です。これはファイルの完全性を検証するには素晴らしいことですが、パスワードを保護するには最悪です。
これこそが、bcryptとArgon2が解決するために生まれた問題です。これらはパスワードハッシュ関数(PHF)という、意図的に、痛々しいほど遅く、リソースを大量に消費する特別なハッシュアルゴリズムなのです。これらはパスワード1つをチェックするコストを非常に高くすることで、盗まれたデータベースをブルートフォース(総当たり)で攻撃することを、攻撃者にとって経済的にも時間的にも悪夢のような作業に変えてしまいます。
内部の仕組み
最新のパスワードハッシュ化の魔法は、単にアルゴリズムそのものにあるのではありません。そのアルゴリズムに設定可能な量の「仕事」をさせることにあります。これはしばしば「キーストレッチング」と呼ばれます。
コアアイデア:適応的なコスト
bcryptとArgon2はどちらも、調整可能な「コスト」または「ワーク」ファクターを持っています。これが彼らのスーパーパワーです。これは、ハッシュ化プロセスがどれだけ遅く、リソースを食うかを決めるために設定する数値です。このコストを、サーバーでパスワードをハッシュ化するのに妥当な時間(例えば100~500ミリ秒)がかかるように調整するのがポイントです。攻撃者をイライラさせるには十分遅く、しかしユーザーをイライラさせない程度には速い、という絶妙なバランスです。
ムーアの法則に従ってコンピューターが高速化するにつれて、数年ごとにコストファクターを上げるだけで、同じレベルのセキュリティを維持できます。攻撃者のハードウェアが強力になるにつれて、あなたのハッシュ化の「筋トレ」もよりタフになるのです。
Bcrypt: 頼れるベテラン
1999年に導入されたbcryptは、Blowfish暗号に基づいています。ブルートフォース攻撃に対するその主な防御策は、計算量が多いだけでなく、メモリをたくさん使うことです。
このアルゴリズムはパスワードとソルトを繰り返し処理しますが、その内部状態の重要な部分が、RAMの塊への頻繁なアクセスを必要とする方法で保存されます。CPUはこれを得意としますが、攻撃者の伝家の宝刀であるGPUは、メモリへのアクセスが最小限の、単純で並列化可能な計算で真価を発揮します。bcryptは、プロセスに常にメモリへアクセスさせることで、GPUベースのクラッキングマシンの歯車にレンチを投げ込むような妨害をするのです。
典型的なbcryptハッシュ文字列は次のようになります。
$2b$12$J9yLsf9dTOZiCEpT1zO97uF2Yp3uG/yG4Q6V.C1rCg8u6d5qO5GqS
分解してみましょう。
| パーツ | 例 | 意味 |
|---|---|---|
| アルゴリズム | $2b |
bcryptアルゴリズムとそのバージョンを識別します。 |
| コストファクター | 12 |
ワークファクター。繰り返し回数は212回です。これを1増やすと時間は2倍になります。 |
| ソルト&ハッシュ | J9yLsf...d5qO5GqS |
最初の22文字はBase64エンコードされたソルトです。残りが実際のハッシュ化されたパスワードダイジェストです。 |
重要なのは、パスワードを検証するために必要なすべての情報(アルゴリズム、コスト、ソルト)が、この便利な1つの文字列にすべて収まっていることです。
Argon2: 現代のチャンピオン
Argon2は、2015年のパスワードハッシュ化コンペティション(PHC)の勝者です。これは、新しい標準的なパスワードハッシュアルゴリズムを見つけるための数年にわたる取り組みでした。bcryptよりもさらに現代のハードウェア攻撃に対して耐性があるように設計されています。
コストを調整するために、3つの異なるレバーを提供します。
- メモリコスト (m): アルゴリズムが使用するメモリ量(KiB単位)。これはハードウェアアクセラレーションに対する主要な防御策であり、単一のGPUコアで利用可能なメモリよりも多くのメモリを要求するように調整できます。
- 時間コスト (t): アルゴリズムがメモリ上で行う繰り返し、つまりパスの回数。これにより、総計算時間が増加します。
- 並列度 (p): 使用できるスレッドまたは「レーン」の数。これにより、アルゴリズムはハッシュ化にマルチコアCPUを活用できますが、攻撃者も同じ並列処理を使用できることを意味します。これはトレードオフのパラメータです。
推奨されるバリアントはArgon2idで、これは他の2つのバージョンのハイブリッドです。サイドチャネル攻撃(攻撃者が消費電力などのシステムへの影響を分析する攻撃)とGPUクラッキング攻撃の両方に対して、バランスの取れた耐性を提供します。
Argon2idのハッシュ文字列も同様に自己完結型です。
$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTIT0T7cKLfgflAIk124
| パーツ | 例 | 意味 |
|---|---|---|
| アルゴリズム | $argon2id |
Argon2idアルゴリズムを識別します。 |
| バージョン | v=19 |
アルゴリズムのバージョン番号。 |
| パラメータ | m=65536,t=3,p=4 |
メモリ、時間、並列度のコスト。 |
| ソルト | c29tZXNhbHQ |
Base64エンコードされたランダムなソルト。(これは "somesalt" とデコードされます)。 |
| ハッシュ | Rdescudv...flAIk124 |
Base64エンコードされたハッシュ化されたパスワードダイジェスト。 |
検証プロセス
ここが美しいところです。ハッシュを復号することは絶対にありません。検証は一方通行なんです。
- ユーザーが
P@ssword123でログインしようとします。 - あなたのアプリケーションは、データベースからそのユーザーの完全なハッシュ文字列(例えば、上の表のArgon2id文字列)を取得します。
- あなたのコードはその文字列を解析し、アルゴリズム(
argon2id)、バージョン(19)、コストファクター(m=65536, t=3, p=4)、そしてソルト(c29tZXNhbHQ)を抽出します。 - 次に、ユーザーが入力したパスワード(
P@ssword123)を、それらと全く同じパラメータを使ってハッシュ化します。 - 最後に、新しく生成されたハッシュを、データベースから取得した文字列のハッシュ部分と比較します。もしそれらが同一であれば、パスワードは正しいです。そうでなければ、アクセスは拒否されます。
実社会での事例
2012年のLinkedIn情報漏洩事件:スピードが教えてくれた教訓
2012年、LinkedInは大規模な情報漏洩に見舞われ、650万件以上のユーザーのパスワードハッシュがロシアのハッカーフォーラムに流出しました。問題は? ハッシュがソルトなしのSHA-1だったことです。数時間のうちに、セキュリティ愛好家もブラックハットも、こぞってクラック競争を始めました。正常にクラックされたパスワードのリストは、分単位で増えていきました。ハッシュが「高速」でソルトなしだったため、巨大なレインボーテーブルが1つあれば事足りたのです。その評判へのダメージは計り知れないものでした。
教訓: 高速でソルトなしのハッシュは、平文よりわずかにマシな程度です。LinkedInの事件は、業界全体への強烈なモーニングコールとなり、無数の企業がbcryptのような遅くてソルト付きのハッシュ化をついに採用するきっかけとなりました。
時間を稼いだECサイトの話
あるスタートアップ「CircuitCraze」の賢い開発者は、新しい顧客データベースにArgon2idを使用することを主張し、ログインごとに約300msかかるようにパラメータを調整しました。1年後、設定ミスされたクラウドストレージバケットがデータベースのバックアップを公開してしまいました。全てのArgon2idハッシュを含むusersテーブルが攻撃者によってダウンロードされました。チームは即座に全ユーザーのパスワードリセットを強制しました。攻撃者は、今や使い物にならなくなったデータセットからほんの数個のパスワードをクラックするためだけに、クラウドコンピューティングに数千ドルを費やす見込みに直面し、あっさりと諦めて、もっと手軽に狙える獲物へと移っていきました。
教訓: 強力なパスワードハッシュ化は、情報漏洩が起きた後の主要な防御策です。漏洩自体は防げませんが、対応するための重要な時間を稼ぐことで、「壊滅的なデータ流出」を「対応可能なインシデント」に変えてくれます。
コストファクターの忍び寄る陳腐化
あるチームは2015年に新しいWebアプリをローンチし、賢明にもコストファクター10のbcryptを選択しました。それは素晴らしい働きをしました。時が経ち、現在。ムーアの法則がその威力を発揮し、コストファクター10は今や弱いと見なされています。セキュリティ監査で高リスクとして指摘されました。チームは今、ジレンマに直面しています。大規模なパスワードリセットを強制することなく、数百万人のユーザーのセキュリティをどうやってアップグレードするか? エレガントな解決策はこうです。次回ユーザーがログインした際、システムは古いコスト10のハッシュに対してパスワードを検証します。もし正しければ、即座に同じパスワードを新しいコスト13で再ハッシュ化し、データベース内の古いハッシュを置き換えます。
教訓: パスワードセキュリティは「一度設定したら終わり」ではありません。コストファクターは「生きた設定」であり、攻撃者の能力に追いつくために定期的に見直し、引き上げる必要があります。
よくある間違いと落とし穴
- 高速なハッシュを使う。 やめましょう。MD5, SHA-1, SHA-256, SHA-512... これらはチェックサム用であり、パスワード用ではありません。これらは高速に設計されており、それはまさにあなたが望んでいないことです。
- 独自実装の暗号化(オレオレ暗号)。
sha512(password + salt)のような、独自のハッシュ化方式を発明しようとしないでください。標準的で、査読され、実戦でテストされたbcryptやArgon2のライブラリを使いましょう。 - 静的または使い回しのソルトを使う。 ソルトは暗号学的にランダムで、保存するすべてのパスワードに対してユニークでなければなりません。各ハッシュと一緒にユニークなソルトを保存するのが、正しく標準的な方法です。
- コストを低く設定しすぎる。 (2024年時点で)12未満のbcryptコストファクターや、弱いArgon2パラメータは、全体の目的を台無しにします。攻撃者に楽をさせているだけです。許容できる限り遅くなるように調整してください。
- コストを高く設定しすぎる。 もしハッシュ化に10秒かかると、ログインフォームはタイムアウトし、壊れているように感じられます。さらに悪いことに、誰かがログインフォームに繰り返しPOSTするだけでサーバーのCPUを停止させることができる、サービス拒否(DoS)攻撃に対して脆弱になります。
- ハッシュダイジェストだけを保存する。 bcryptやArgon2によって生成される文字列には、アルゴリズム、コスト、ソルトが含まれていますが、それには理由があります。将来パスワードを検証できるように、その文字列全体(例えば
VARCHAR(255)カラムに)を保存しなければなりません。
なぜこれがあなたのレーダーにあるべきか
もしあなたの仕事がログインフォームを持つ何かを構築することに関わるなら、これを理解する必要があります。
- バックエンド&フルスタック開発者: これはあなたの直接の責任です。これを間違えれば、あなたのユーザー全員を危険にさらすことになります。
- セキュリティエンジニア&DevOps: あなた方はポリシーを設定し、監査を行い、インフラを管理する人々です。何が「良い」状態なのかを知り、それをテストする方法を知る必要があります。
- フロントエンド開発者: 安全なログインには数百ミリ秒かかるかもしれないと理解することで、ユーザーが「ログイン」ボタンを怒りの連打するのを防ぐために、ローディングスピナーのようなフィードバックを提供する必要がある理由がわかります。
要するに、もし「ユーザー」「アカウント」「パスワード」といった言葉があなたのプロジェクト要件のどこかに現れるなら、パスワードハッシュ化はあなたが無視できない基本的な概念です。
もっと深く知る
- OWASP Password Storage Cheat Sheet — 開発者にとって不可欠な、信頼できるガイド。
- Argon2 Specification — Argon2がどのように機能するかを記述した公式の論文。
- How To Safely Store A Password by Coda Hale — bcryptの「なぜ」を、ある世代の開発者たちに説いた2010年の古典的ブログ記事。
- Bcrypt on Wikipedia — アルゴリズムの歴史と設計に関するしっかりとした概要。
- Argon2 on Wikipedia — パスワードハッシュ化コンペティションとArgon2の設計に関する背景。
- Password Hashing Competition — Argon2を勝者として選出したコンペティションの公式サイト。