すべてのデータ侵害の開示は同じ弧を描きます:「私たちはセキュリティを真剣に受け止めています。」その後、詳細を読み、パスワードが平文で保存されていたり、MD5でハッシュされていたり、同じデータベースにあったキーで暗号化されていたりすることが判明します。パスワード保存の問題は解決されています — アルゴリズムが存在し、ライブラリが存在し、ドキュメントが存在する — それなのに開発者はまだ間違えています。
この記事では、何を保存すべきか、何を保存すべきでないか、ハッシュ化と暗号化の違い、そしてSHA-256(password)がパスワードハッシュではない理由を扱います。
ルール:絶対に平文を保存しない
データベースが侵害され、平文パスワードがある場合、他のサイトでパスワードを再利用しているすべてのユーザーが侵害されます。あなたのサイトだけでなく — 彼らのメール、銀行、ソーシャルメディア。平文パスワード保存はセキュリティの問題ではありません。道徳的な問題です。
ハッシュを保存してください。常に。
ハッシュ化 vs 暗号化
ハッシュ化は一方通行の変換です。元に戻すことはできません。hashpasswordが与えられた場合、passwordを復元することはできません。確認のみ可能です:入力をハッシュして比較します。
暗号化は双方向の変換です。encrypt(password, key)が与えられた場合、キーを使ってpasswordを復元できます。キーが侵害された場合、すべてのパスワードが侵害されます。
パスワードをハッシュしてください。絶対に暗号化しないでください。平文を復元する必要がある場合(必要ありません)、間違った問題を解決しています。
SHA-256が不十分な理由
一般的な間違い:hash = SHA-256(password)。速く、広く知られており、パスワードには間違っています。
問題は速度です。モダンGPU上のSHA-256は毎秒数十億のハッシュを処理します。SHA-256パスワードハッシュのデータベースを持つ攻撃者は、すべての一般的なパスワード、すべての辞書の単語、すべての順列を数分で試すことができます。
パスワードハッシュ化アルゴリズムは意図的に遅いです。 ブルートフォース攻撃を非実用的にするために、キーストレッチング — ハッシュ関数の数千回の反復 — を使用します:
- bcrypt — 1999年以来の基準。調整可能なコスト因子。パスワード + ソルト → ハッシュ。すべての主要言語にライブラリがあります。
- scrypt — メモリーハード。大量のRAMを必要とし、GPU攻撃を困難にします。
- Argon2 — パスワードハッシュコンペティション(2015)の勝者。メモリーハードで、時間とメモリコストを設定可能。現在のベストプラクティス。
- PBKDF2 — NIST推NIST推奨、広く利用可能、HMAC反復を使用。Argon2ほどメモリーハードではありませんが、十分にテストされています。
bcrypt、scrypt、またはArgon2を使用してください。 独自のスキームを発明しないでください。SHA-256で一度ハッシュして完了としないでください。
ソルト:なぜすべてのパスワードにユニークなものが必要か
ソルトはハッシュ化前にパスワードに追加されるランダムなデータです。ソルトがないと、同じパスワードを持つ2人のユーザーは同じハッシュを生成します。攻撃者は一般的なパスワードのハッシュを事前計算し(レインボーテーブル)、データベースと照合できます。
ユーザーごとにユニークなソルトがあると、パスワードが同じでもハッシュが異なります。レインボーテーブルは機能しません。攻撃者は各パスワードを個別にブルートフォースする必要があります。
モダンなパスワードハッシュ関数(bcrypt、Argon2)は自動的にランダムなソルトを生成します。ソルトを個別に管理する必要はありません。パスワードをハッシュ関数に渡すと、残りを処理します。
何を保存すべきか
各ユーザーについて、以下を保存します:
- パスワードハッシュ(bcrypt/Argon2の出力、ソルトとコストパラメータを含む)
- ハッシュアルゴリズム(bcrypt、argon2など)
- コストパラメータ(bcryptラウンド、Argon2メモリ/時間)
保存しないもの:
- 平文パスワード
- ソルトを別途(ハッシュ関数の出力に含まれています)
- パスワードを復号化できる可能性のある「暗号化キー」
- パスワードのヒント(情報を漏洩します)
bcryptのハッシュ出力は次のようになります:$2b$12$LJ3m4ys3GzF4VQ.YOUR.HASH.HERE。$2b$はアルゴリズムを識別し、12はコスト因子で、残りはソルト + ハッシュです。ログイン試行を確認するには、同じパラメータで送信されたパスワードをハッシュし、比較します。
コスト因子
bcryptのコスト因子はハッシュの速度を決定します。各増加は計算時間を2倍にします:
- コスト10:ハッシュあたり約10ms(ユーザーには速く、攻撃者にも速い)
- コスト12:ハッシュあたり約40ms(合理的)
- コスト14:ハッシュあたり約160ms(攻撃者には遅く、ユーザーには気づく)
- コスト16:ハッシュあたり約640ms(ほとんどのログインフローには遅すぎる)
2026年のベストスポット:bcryptコスト12-14、64MB以上のメモリと3以上の反復を持つArgon2。 目標は各ハッシュ試行を遅くし、数十億のパスワードのブルートフォースに数年かかりますが、ユーザーがログイン時に遅延を感じない程度に速くすることです。
レート制限
ハッシュ化だけでは不十分です。攻撃がログインエンドポイントに対して毎秒数百万のパスワードを試せる場合、bcryptでさえ救えません。
ログイン試行を制限 同じIPまたはアカウントからの5-10回の失敗後、アカウントを一時的にロックするかCAPTCHAを要求します。これによりブルートフォース攻撃は最小限に抑えられます。
ユーザー名またはパスワードが間違っているかどうかを明かさない 「無効なユーザー名またはパスワード」は「そのメールのアカウントが見つかりません」より安全です。後者は攻撃者に有効なユーザー名を伝えます。
持続的な失敗後のアカウントロックを追加 10-20回の失敗後、アカウントを15分間ロックするか、ロック解除にメール確認を要求します。
パスワード生成
パスワードを生成する場合(ユーザー、APIキー、トークン用):
- 最低12文字 モダンなハードウェアに対して8文字では不十分です。
- 大文字、小文字、数字、記号を含める しかし長さは複雑さよりも重要です。
- CSPRNGを使用(暗号学的に安全な擬似乱数生成器)。
Math.random()は安全ではありません。 - 予測可能なパターンを絶対に生成しない
Password1!は複雑に見えますが、簡単に推測できます。
最良のパスワードはCSPRNGから生成されたランダムな文字列です。パスワードマネージャーを使用して保存してください。ユーザーに複雑なパスワードを覚えさせないでください — 生成されたパスワードを渡し、マネージャーに管理させてください。
よくあるエラー
- SHA-256またはMD5で一度ハッシュ — 速すぎ、ソルトなし、容易にブルートフォース可能。
- 静的ソルトの使用 — すべてのユーザーが同じソルトを共有する場合、レインボーテーブルが機能します。
- ソルトを別途保存 — ソルトが別のテーブルにある場合、データベースを侵害した攻撃者は両方を取得します。
- ハッシュ化の代わりに暗号化 — パスワードを復号化できる場合、キーを持つ攻撃者もできます。
- レート制限なし — bcryptでさえ無制限のログイン試行に脆弱です。
- ユーザー名の存在を明かす — 攻撃者に有効なアカウントの列挙を助けます。
- パスワード生成に
Math.random()を使用 — 予測可能で、暗号学的に安全ではありません。
クイックリファレンス
| やる | やらない |
|---|---|
| bcrypt、Argon2、またはscryptを使用 | SHA-256またはMD5を使用 |
| ライブラリにソルト処理を任せる | ソルトを自分で管理 |
| ログイン試行を制限 | 無制限のパスワード試行を許可 |
| CSPRNGでパスワードを生成 | Math.random()を使用 |
| ハッシュのみを保存 | 平文または暗号化されたパスワードを保存 |
| 「無効な資格情報」と言う | 「間違ったパスワード」と言う |
試してみる
安全なランダムパスワードを生成したり、テスト用に文字列をハッシュする必要がある場合は、ブラウザベースのツールを使用して操作がローカルで行われるようにしてください。パスワードは単純な生成タスクでサーバーに触れるべきではありません。