bcryptの仕組みと実務におけるコスト設定の重要性
はじめに
プロトでログイン機能を実装する機会があったので実装中に浮かんだ素朴な疑問たちを解消。
bcryptハッシュ値の構造
パスワードのハッシュ化にはbcryptを使用。
以下のような文字列で構成されている。
$2a$12$XXXXXXXXXXXXXXXXXXXXXXYYY...YYY
実装時点では「ソルトやらが付与されているのでなんか復号できないらしい」程度の認識。
素朴な疑問たち
疑問 1: コストファクターってどちら様?
A: 計算処理を意図的に遅延させる設定値のこと。
bcryptなどの鍵導出関数(KDF)が持つストレッチングという仕組みでパスワードを何度も繰り返しハッシュ化する。この回数を増やすのがコスト設定である。
高性能なGPUや専用ハードウェアによる総当たり攻撃(ブルートフォースアタック)に対して1回あたりの試行時間を延ばし、クラックを実質的に不可能なレベルにすることで防御する。
疑問 2: ソルト(Salt)の存在意義とは
A: ソルトは一意性を持たせる役割がある。総当たり攻撃の効率を低下させるために不可欠。
- ソルトはパスワードごとにランダムに生成され、ハッシュ値に付与される
- これにより、同じパスワードでもユーザーごとに異なるハッシュ値が生成される
- これはレインボーテーブル攻撃(辞書攻撃)を防ぐために有効。ソルトのおかげで攻撃者は辞書をユーザーの数だけ作り直す必要が生じ、効率的な一括攻撃ができなくなる。
疑問 3: 単純なハッシュ部分だけを抜いて復号できるのでは?
A: できない。ハッシュ関数は一方向性の原理に基づき不可逆である。
bcryptのハッシュ値は「平文パスワード」単体をハッシュ化したものではなく、「平文パスワード + ソルト + コスト設定」という3つの要素から計算される。
仮に純粋なハッシュ部分だけを切り出し、その部分に対してソルトなしで総当たりを試行しても計算の前提が異なるため、正しいハッシュ値と一致しない。
疑問 4: ハッシュ値を照合できるのはなぜか
A: 照合時にはデータベースに保存されているソルトを再利用している。(盲点)
ログイン処理内でパスワードを照合する場合
- ユーザーが入力したパスワードとユーザーIDをサーバーが受け取る
- サーバーはDBからハッシュ値全体を取得する
- bcryptライブラリはハッシュ値からソルトとコストを抽出する
- 入力されたパスワードを、抽出したソルトとコストを使って再ハッシュ化する
- 再ハッシュ化の結果が、DBに保存されている最終ハッシュ値と一致するかを確認する
ソルトが埋め込まれているのはランダムなソルトの安全性を維持しつつ、正しい照合を可能にするため。
疑問 5: DB引っこ抜かれたら終わりじゃね?
A: それはそう。
bcryptの最大の価値はコストファクターによる計算遅延。
攻撃者に正しいソルトとコストを知られても、その情報を使って一人ひとりのパスワードを総当たりするには、設定されたコスト分だけ時間がかかる。
適切なコスト値が設定されている場合クラックされるまでに数十年かかる計算となるため、実質的にクラックできないという話。
コスト設定の重要性
bcryptはコストとソルトの二重障壁でパスワードを守る堅牢な仕組み。
実態は100%の魔法ではなく、攻撃者に諦めさせるための時間稼ぎである。
コスト値によってどれくらいの違いが出るのか検証:
- 実務での推奨: 認証処理に100ms〜300ms程度かかるようにコストファクターを設定すること
- 運用上の注意: サーバーのCPU性能やGPUの進化に伴ってコスト値は相対的に低下するため、数年ごとにコスト設定を見直す必要がある
Discussion