なぜ「SHA-256でハッシュ化しました」がエンジニアの墓穴になるのか
現場でコードレビューをしていると、未だに「パスワードをSHA-256でハッシュ化して保存しています」という報告を受けることがある。率直に言おう。それはセキュリティ対策ではなく、単なる「データの加工」に過ぎない。
攻撃者はあなたのデータベースを奪った後、GPU(NVIDIA RTX 4090クラス)やFPGAを駆使して、秒間数十億回ものハッシュ計算を試みる。単純なハッシュ関数は「高速に計算できる」ことが設計思想であり、それは攻撃者にとって「高速にクラックできる」という福音でしかない。
今日は、パスワードを守るための「鍵導出関数(KDF)」の正しい実装と、現場で戦うための勘所を叩き込む。
—
1. 攻撃者の視点:なぜ「コスト」が必要なのか
攻撃者が使うツール(Hashcat等)は、辞書攻撃だけでなく「ブルートフォース」を極めて効率的に行う。パスワードハッシュに「計算コスト(Work Factor)」を組み込む真の目的は、サーバー側の認証には0.1秒しかかからないが、攻撃者が1つのハッシュを解読するには数秒〜数分かかるような「意図的な足止め」をすることにある。
採用すべきアルゴリズムの選定基準
- PBKDF2: 枯れた技術だが、GPU攻撃に対しては耐性が低い。今から新規開発するなら避けるべき。
- bcrypt: 長年標準だったが、メモリ消費が少なくGPUでの並列攻撃に弱い。
- Argon2id: 現在、業界のデファクトスタンダード。 メモリ消費量、計算時間、並列化の3軸で調整が可能で、GPUやASICによるクラックに対して極めて高い耐性を持つ。
—
2. 【実践】Argon2idを用いた堅牢な実装
PHPやNode.js(Pythonも同様)では、自前でアルゴリズムを実装してはならない。言語標準のライブラリが提供する「高レベルAPI」を使うのが鉄則だ。
PHPでのセキュアな実装例
PHPでは password_hash() 関数を使うだけで、Argon2idが自動的に適用される。
<?php
// パスワードをハッシュ化する(自動的にソルトが生成される)
// argon2idはPHP 7.3以降で利用可能
$password = 'UserSecretPassword123!';
// オプションで計算コストを調整(本番環境に合わせてベンチマークを取ること)
$options = [
'memory_cost' => 65536, // 64MBのメモリを使用
'time_cost' => 4, // 4回の反復計算
'threads' => 2, // 2スレッドで並列処理
];
$hash = password_hash($password, PASSWORD_ARGON2ID, $options);
// 認証時の検証
if (password_verify($password, $hash)) {
echo 'ログイン成功';
} else {
echo '認証失敗';
}
?>
Pythonでの実装例 (passlibを使用)
Python環境では passlib ライブラリを利用するのが一般的だ。
from passlib.hash import argon2
# ハッシュ生成
hashed_password = argon2.using(rounds=4, memory_cost=65536, parallelism=2).hash("UserSecretPassword123!")
# 検証
is_correct = argon2.verify("UserSecretPassword123!", hashed_password)
if is_correct:
print("認証成功")
—
3. 運用上の「盲点」を突く
コードを書くだけでは不十分だ。以下の項目が守られていなければ、どれだけ強力なアルゴリズムを使っても無意味になる。
1. Work Factorの定期的な見直し:
ハードウェアの性能は毎年向上する。3年前のベンチマーク設定をそのまま使っていないか? サーバーのスペックを上げたタイミングで、memory_cost や time_cost を引き上げる運用フローを組み込め。
2. ソルト(Salt)の管理:
password_hash は内部でランダムなソルトを生成し、ハッシュの中に埋め込む。これをDBにそのまま保存して問題ない。重要なのは「共通のソルト」を使わないことだ。DBのパスワードカラムが VARCHAR(255) 程度で足りない場合は、設計を見直せ。
3. キーの保護(IAMとの組み合わせ):
万が一、DBのパスワードハッシュが流出した際に備え、アプリケーション層とDB層を分離せよ。例えば、AWSの Secrets Manager を使い、データベース接続情報そのものをコードから分離し、環境ごとに権限を厳密に制限しておくことが、多層防御の基本だ。
—
最後に:エンジニアとしての矜持
セキュリティとは、完璧な壁を築くことではない。「攻撃者にとってコストが見合わない」という状態を維持し続けることだ。
もしあなたのチームが、まだ md5() や sha1() を使っているなら、今すぐリファクタリングのチケットを切ってくれ。それが、ユーザーの信頼を守り、深夜のインシデント対応で君の時間を奪われないための「最高のリスク管理」になる。
次回のブログでは、この認証基盤をさらに補強するための「ペッパー(Pepper)」の運用方法と、認証試行回数制限の具体的なNginx設定について踏み込んでいく。現場からは以上だ。
コメント