パスワード保護の「現在地」:なぜ今、Argon2への移行が不可欠なのか
現場でインシデント対応をしていると、「パスワードをSHA-256でハッシュ化しています」という言葉を聞いて背筋が凍ることがよくある。2024年の今、それをやるのは「家の鍵を粘土で作る」のと同義だ。GPUの演算能力をなめてはいけない。
今日は、理論的な綺麗事ではなく、攻撃者に「割るコストが見合わない」と諦めさせるための、泥臭くも堅牢なパスワード保護の鉄則を話そう。
1. なぜ「単なるハッシュ」が地獄への入り口なのか
攻撃者は、流出したデータベースを手にすると、レインボーテーブル攻撃やGPUを用いた総当たり攻撃を仕掛けてくる。もしあなたが「塩(ソルト)」を振らずにハッシュ化していれば、攻撃者は一度の計算で同じパスワードを持つ全ユーザーを一網打尽にできる。
ソルトは「各ユーザー固有のランダム値」を加えることで、同じパスワードでもハッシュ値を全くの別物にする。さらにストレッチング(計算を意図的に遅延させる手法)を組み合わせることで、攻撃者が1秒間に試行できるパスワードの数を劇的に減らす。これが現代の防御の基本だ。
2. 選ぶべきアルゴリズム:bcrypt か Argon2id か
現在、推奨される選択肢は二つしかない。
- bcrypt: 長年の実績があり、現在も十分セキュア。
- Argon2id: 現時点での勝者。 メモリ耐性(サイドチャネル攻撃への強さ)が設計段階から組み込まれており、GPUによる高速クラックに対して最も強力な耐性を持つ。
迷ったら Argon2id を選べ。これがセキュリティの最前線の回答だ。
—
3. 実装コード:明日から現場で使える実装例
ライブラリを自作してはいけない。「暗号学的に正しい実装」はプロでも嵌る地雷原だ。各言語の標準的・推奨ライブラリを使え。
PHP (password_hash 関数を使う)
PHPは標準関数が非常に優秀だ。PASSWORD_ARGON2ID を指定するだけでいい。
// パスワードのハッシュ化
$password = ‘user_password_input’;
// 適切なコスト設定(サーバーのスペックに合わせて要調整)
$options = [
‘memory_cost’ => 65536, // 64MB
‘time_cost’ => 4, // 反復回数
‘threads’ => 2, // 並列処理数
];
$hash = password_hash($password, PASSWORD_ARGON2ID, $options);
// 検証時
if (password_verify($password, $hash)) {
// ログイン成功
}
Python (passlib を使用)
Pythonでは passlib を使って設定をカプセル化するのが定石だ。
from passlib.hash import argon2
ハッシュ化
運用環境では .using(rounds=…) でコストを調整すること
hash_value = argon2.hash(“user_password_input”)
検証
if argon2.verify(“user_password_input”, hash_value):
print(“ログイン成功”)
—
4. 「Work Factor(計算コスト)」の匙加減
ここが一番の悩みどころだろう。「強くしすぎるとサーバーが落ちる、弱すぎるとクラックされる」。
黄金律は「ログイン処理に 100ms〜300ms かかる設定」だ。
ユーザーにとっては無視できる遅延だが、攻撃者にとっては、1秒間に数回しか試行できないという致命的な足枷になる。
- 運用上のコツ: サーバーのスペックをアップグレードした際は、必ずこのコスト設定も上げろ。古いハッシュ値を検知して、ログイン時に新しいコストで再ハッシュ化するロジックを組むのが、プロの運用の流儀だ。
5. 最後に:インフラ屋がやるべき「もう一つの盾」
アプリケーションの堅牢化は必須だが、それだけで満足してはいけない。
- WAFの活用: CloudflareやAWS WAFで、同一IPからのログイン試行回数を制限(Rate Limiting)せよ。
- ブルートフォース検知: 失敗回数が閾値を超えたらアカウントを一時ロックし、かつ管理者へアラートを飛ばすこと。
「完璧なセキュリティ」など存在しない。あるのは「攻撃のコストを、攻撃者が諦めるレベルまで引き上げる努力」だけだ。
今日紹介したコードは、今すぐコードベースに組み込めるレベルに調整してある。もし、あなたのプロジェクトでまだMD5やSHA-1が生き残っているなら、今すぐ修正チケットを切るんだ。それが、エンジニアとしての誠実さというものだぞ。
コメント