設問とスコープ
10,000,000件のアカウントを持つあるSaaSプロダクトが、パスワード認証を再構築しています。データベースのレコードのうち、約7,000,000件がコスト10のbcryptを使用し、2,000,000件が反復回数210,000回のPBKDF2-HMAC-SHA256を使用し、1,000,000件がレコードごとのソルトなしのSHA-256を使用しています。既存の形式には一貫性がなく、一部の行ではアルゴリズムやパラメータが直接特定できません。ピーク時、認証サービスは毎秒1,500件のパスワード検証を処理する必要があります。各インスタンスで許可される高負荷なハッシュ処理の同時実行数は最大24件です。
Argon2idへの完全な移行を設計してください。脅威モデル、パラメータ選定とキャパシティ測定、拡張可能なストレージ形式、複数アルゴリズム混在環境での検証、ログイン時アップグレード、並行パスワード変更、長期非アクティブアカウント、ペッパー管理、データベースや鍵の漏洩への対応、アカウント列挙およびリソース枯渇への防御、ならびにロールアウトと完了基準を説明してください。アカウント数、アルゴリズムの構成、レガシーパラメータ、並行処理制限は面接上の制約条件であり、普遍的なセキュリティ基準ではありません。
これはバックエンドセキュリティおよび認証エンジニアリングに関する設問です。認証情報の検証機能と移行ステートマシンが焦点となります。完全な新規登録、パスワードリカバリ、MFA、セッション設計は、それらのフローが認証情報の移行や侵害対応に影響を与える場合を除き、スコープ外とします。
面接官が評価しているポイント
第1に、候補者がオンラインでの推測攻撃とオフラインクラッキングを区別できているかです。レート制限はログインエンドポイントを保護しますが、ハッシュデータベースを取得した攻撃者を制限することはできません。パスワードはエントロピーの低い人間由来の秘密情報です。SHA-256のような高速ダイジェストは、攻撃者に候補の素早い列挙を許してしまいます。パスワード専用のハッシュは、レコードごとのソルト、調整可能な計算量、メモリコストによって各試行のコストを引き上げます。
第2に、パラメータを実際のキャパシティに基づいて測定・決定しなければならないことを候補者が理解しているかです。「Argon2idを使用する」だけではアルゴリズムを選んだに過ぎず、設計として不完全です。完全な回答では、メモリ m、反復回数 t、並列度 p、ソルト長、出力長を選定し、並行検証がメモリ、CPU、レイテンシ、サービス運用妨害(DoS)耐性にどう影響するかを算出します。パラメータを高めれば無条件に安全になるわけではありません。攻撃トラフィックによってサービスのメモリが枯渇した場合、認証の可用性が最初に損なわれます。
第3に、候補者が不可逆なデータを移行できるかです。システムは各ユーザーの平文パスワードを保持していないため、bcryptの結果をオフラインで復号してArgon2idに変換することはできません。通常のアプローチは、レガシーレコードの検証に成功した際、そのリクエストで提供された平文を使用して最新のレコードを生成することです。長期非アクティブアカウントについては、レガシーアルゴリズムのリスクや漏洩履歴に応じて、制御されたレガシー検証器の維持、一時的なラッピング、またはリセットが必要になります。
最後に、優れた回答は競合状態(レースコンディション)や運用の境界を適切に処理します。ログイン時のアップグレードによって、並行して行われたパスワードリセットを上書きしてはなりません。ペッパーをハッシュデータベースと同居させるべきではありません。アルゴリズムのバージョン、パラメータ、移行状態にはオブザーバビリティが必要ですが、ログにパスワード、完全なハッシュ、ペッパーを含めてはなりません。「新しいコードがデプロイされた」ことは完了基準ではありません。
回答前の確認事項
- 既存のすべての形式を確実に特定できますか? アルゴリズム、パラメータ、文字エンコーディング、ソルトの配置場所、過去のライブラリバージョンを確定します。特定不能な行に対して形式を推測し、複数の検証器を順に試すようなことは避けてください。
- レガシーのbcryptは72バイトを超える入力をどのように処理していましたか? 移行によってユーザーの有効な認証情報が暗黙的に変更されないよう、過去の実装のエンコーディングと切り捨て(truncation)のセマンティクスを再現します。
- データベース、バックアップ、または過去のハッシュセットが漏洩したことはありますか? 現在のデータベースをラッピングしても、すでに漏洩したソルトなしの高速ダイジェストを取り消すことはできません。強制リセットやセッションの無効化が必要になる場合があります。
- 認証インスタンスの実際のリソースバジェットはどのくらいですか? ベースラインメモリ、CPUクォータ、ハッシュ同時実行制限、オートスケーリング速度、目標とするp95/p99レイテンシ、許容失敗率を把握します。
- FIPSその他のコンプライアンス上の制約は適用されますか? これらはアルゴリズムや実装を制限する可能性がありますが、コンプライアンス準拠というラベルだけでパラメータ測定や移行設計を省略できるわけではありません。
- ペッパーはすでに存在しますか? その保存場所、呼び出しの依存関係、バージョン、ローテーション機能、監査境界、および鍵サービスが利用不能になった際の挙動を確認します。
- パスワード変更とセッション発行のトランザクション境界はどうなっていますか? 移行、並行リセット、セッション発行の順序を明示しないと、リセット後に古いパスワードで新しいセッションが取得されてしまうリスクがあります。
- ビジネス価値とリスクの観点から、長期非アクティブアカウントをどのように分類すべきですか? 特権アカウント、最近アクティブなアカウント、長年非アクティブなアカウントで、異なる期限や再検証要件を設定できます。
30秒の回答フレームワーク
「主な目標をハッシュデータベース漏洩後のオフライン推測に対する耐性とし、オンライン認証のキャパシティ保護は別個に行います。新規パスワードには独立したランダムソルトを付与したArgon2idを使用します。レコードにはアルゴリズム、バージョン、m/t/p、ソルト、出力を保持させます。ペッパーを使用する場合、データベース外の鍵管理システムで管理します。単一の値をコピーするのではなく、OWASPの現行の最小推奨値を候補の起点とし、本番同等のインスタンス上で並行メモリ、CPU、p99レイテンシの負荷テストを実施してパラメータを決定します。
ログイン時、宣言されたレコードバージョンに基づいて単一のレガシー検証器を選択します。検証成功後、最新のArgon2idレコードを計算し、古いハッシュに対してCompare-And-Swap(CAS)を実行します。並行変更によりCASが失敗した場合は、パスワードリセットを上書きしないよう、最新のレコードを再読み込みして再検証します。新規登録およびリセットされたパスワードには即座に最新形式を適用します。ソルトなしSHA-256のアカウントには短い移行期限を設定し、安全なラッピングが不可能な場合や漏洩が発生している場合はリセットを必須とします。
エンドポイントでは、統一されたエラー応答、ダミーハッシュ処理、アカウント別および送信元別の独立したレート制限、ハッシュ同時実行数の制限を用いて、列挙攻撃とメモリ枯渇を防ぎます。リリース後は、アルゴリズムバージョン、レイテンシ、メモリ、エラー、CAS競合、リセット完了率ごとに移行状況を追跡します。高リスクなレガシー形式がゼロになり、競合・ピーク負荷テストに合格し、鍵ローテーションの訓練が成功して初めて完了と宣言します。」
ステップごとの解説
ステップ1:オンラインとオフラインの攻撃対象領域を分離する
通常のログインはオンライン経路です。エンドポイントのスループット、アカウントごとの制御、送信元ごとの制御、リスク検知システム、モニタリングによって攻撃者が制限されます。データベース漏洩後の推測はオフライン経路です。攻撃者はアプリケーションのレート制限を受けることなく、自身のGPU、ASIC、クラウドインスタンス上で検証器を実行します。パスワード保存の第一の目的は、この2つ目の経路における各候補試行のコストを引き上げることです。
ソルトなしSHA-256には2つの問題があります。処理が高速であること、そして同じパスワードが同一のダイジェストを生成するため、攻撃者が事前計算結果を再利用でき、共通のパスワードを使い回しているグループを特定できてしまうことです。レコードごとに独立したランダムソルトを使用することで、同一パスワードでも異なる出力が生成され、レコードごとの個別計算を強制できます。ソルトはハッシュと一緒に保存可能で、秘密にする必要はありません。ただし、ソルトを付与しても高速なSHA-256が適切なパスワードハッシュになるわけではありません。意味のある耐性は、専用の調整可能でメモリハード(memory-hard)なアルゴリズムによってもたらされます。
ペッパーは別の独立したセキュリティ統制です。これはレコード間で共有される、またはバージョンごとに管理されるサーバー側の秘密情報であり、パスワードデータベースとは切り離して、通常は鍵管理サービス(KMS)、HSM、または保護された実行環境に保存する必要があります。データベース単体の漏洩による影響を軽減できますが、レコードごとのソルトや低速なハッシュ処理を代替するものではありません。データベースとペッパーが両方とも漏洩した場合、サービス側はユーザーの平文パスワードを保持していないため、データベースの一括バッチ処理で影響を受けたレコードの鍵を安全に再生成することはできません。
ステップ2:パラメータを選定し、認証キャパシティを算出する
新規レコードには、メンテナンスされているArgon2id実装を使用します。現行のOWASPの最小推奨候補の1つは、m=19456 KiB、t=2、p=1 です。RFC 9106では、メモリ制約がある場合の選択肢として 64 MiB、3パス、4レーンなど、より大きなメモリを前提とした一般的な推奨事項が示されています。各ドキュメントは異なる運用制約を想定しているため、特定のパラメータタプルがWebサービス全体の普遍的な定数になるわけではありません。
より妥当性の高い選定プロセスは以下の通りです。
- 本番環境と同じCPU、メモリ制限、ランタイム上で、検証済みの候補値から開始する。
- 単一検証における中央値、p95、p99、実常駐メモリ(RSS)、CPU時間を測定する。
- 通常のピークトラフィック、ログインバースト、誤ったパスワード、存在しないアカウントを組み合わせた負荷テストを実施する。
- 同時実行数を制限し、キュー滞留時間、コンテナのOOM、CPUスロットリング、アップストリームのタイムアウトを観測する。
- 可用性バジェットの範囲内で持続可能な最も高い攻撃者コストを選択し、ハードウェア、ライブラリバージョン、測定日を記録する。
19 MiB の場合、24件の同時処理に必要な理論上のハッシュ作業メモリは、プロセスのベースライン、ライブラリのオーバーヘッド、リクエストオブジェクト、安全マージンを除いてもすでに 456 MiB に達します。対象インスタンスで1回の検証に250ミリ秒かかる場合、1インスタンスあたりの最大処理能力はおよそ毎秒96回となります。毎秒1,500回を処理するには、テールレイテンシ、スパイク、オートスケーリングの遅延に対する追加ヘッドルームを考慮すると、常時利用可能な同等のインスタンスが少なくとも約16台必要です。これらの計算はキャパシティのオーダー感を明らかにするものであり、最終的な値は負荷テストによって決定します。
レコードごとに新規で独立した128ビットのランダムソルトと、例えば256ビットの出力を使用します。暗号フィールドを手動で組み立てるのではなく、成熟したライブラリに標準エンコーディングの生成とパースを任せてください。パスワード入力は、ハッシュ化の前に安定的で文書化されたバイトエンコーディングが必要です。Unicodeを受け入れる場合は正規化形式を定義し、登録、ログイン、移行の各処理で一貫して適用します。
ステップ3:認証情報レコードを自己記述的かつ拡張可能にする
レコードには、検証に必要な非秘密情報を保持させる必要があります。Argon2のエンコーディングは次のようになります。
$argon2id$v=19$m=19456,t=2,p=1$SALT_BASE64$TAG_BASE64レガシー形式にも {bcrypt}、{pbkdf2-sha256}、{sha256-legacy} などの決定論的なマッピングが必要です。識別ラベルはパース用プロトコルであり、安全性を保証するものではありません。未知のラベル、欠落したフィールド、不正なBase64値、ポリシー外のパラメータは拒否し、そのアカウントを制御されたリカバリキューに送ります。一致するものが見つかるまで順にアルゴリズムを試すような実装は避けてください。
パラメータのパースには上限値の設定が必要です。攻撃者がレコードを改ざんできた場合、極端なメモリや反復回数を設定してログインリクエスト経由でサービスのリソースを枯渇させる可能性があります。検証器はデプロイ済みのアルゴリズムと許容パラメータ範囲のみを受け入れます。範囲外のレコードは機密情報を含まないセキュリティイベントを発行し、アカウントリカバリを要求します。
認証情報テーブルには、少なくとも現在のエンコーディング、認証情報の更新日時、セキュリティ状態のステータスが必要です。移行レポートは、エンコーディングのプレフィックスやカーディナリティの低い scheme_id から集計できます。完全なハッシュ、ソルト、候補パスワード、ペッパーの秘密情報、検証の中間状態をログ、メトリクスラベル、分析システムに出力してはなりません。
ステップ4:ログイン成功時に安全にアップグレードする
移行関数はレコードをパースし、一致する単一の検証器を呼び出します。レガシー認証情報の検証に成功して初めて、サービスはそのリクエストから正しい平文を取得でき、最新のArgon2idレコードを計算できます。負荷の高いハッシュ計算は短いデータベーストランザクションの外側で行い、古いレコードを条件として書き込みを実行します。
record = loadCredential(userId)
ok = verifyByDeclaredScheme(record.hash, submittedPassword)
if !ok: rejectWithGenericError()
if needsRehash(record.hash):
upgraded = hashWithCurrentPolicy(submittedPassword)
changed = compareAndSwap(userId, expected=record.hash, replacement=upgraded)
if !changed:
latest = loadCredential(userId)
if !verifyByDeclaredScheme(latest.hash, submittedPassword):
rejectAndAskForFreshLogin()
issueSessionAfterCredentialStateIsConfirmed()Compare-And-Swap(CAS)により、ログイン時のアップグレードが並行して実行されたパスワードリセットを上書きすることを防ぎます。条件付き更新の失敗は、別のログインが同じ移行を完了したか、ユーザーが別のパスワードに変更したことを意味します。古いレコードが一度一致したからといって無条件にセッションを発行するのではなく、最新のレコードを再読み込みして現在の入力を再検証してください。新規登録、自発的なパスワード変更、パスワードリカバリでは、最初から最新形式を直接書き込み、レガシーレコードを新たに作成しないようにします。
アップグレードの書き込みが失敗した際にログインを継続させるかどうかはリスクポリシーで決定します。リトライ可能なデータベースエラーの場合は、許容可能なレガシーレコードを一時的に維持したまま移行失敗イベントを発行できます。高リスクな形式の場合は、セッション発行前にアップグレードの成功を必須とすることも可能です。いずれの場合も、1回のログインで負荷の高いハッシュ処理が無制限に何度も実行されないよう、リトライ回数に上限を設けます。
ステップ5:許容可能なレガシー形式と高リスクな形式を区別して扱う
bcryptおよび十分な強度を持つPBKDF2は、管理された移行期間中に読み取り専用の検証を維持し、ログイン成功時にアップグレードできます。レガシー検証器の露出は最小限に抑えます。これらは既存レコードの検証のみを担当し、新規認証情報の作成には使用できません。形式ごとに残存アカウント数、アクティビティ、移行速度を追跡します。期限を迎えて検証器を削除する前に、その検証器に依存しているすべてのアカウントを解決します。
ソルトなしSHA-256はリスクが高くなります。漏洩が発生しておらず、即時の全ユーザーリセットが現実的でない場合、サービスはデータベースの一時的な強化策として、既存のダイジェストを新規ソルト付きの低速な外部アルゴリズムへの入力として扱う(ラッピングする)ことができます。検証時はまず過去のSHA-256処理を再現し、その後に外部レイヤーを検証します。ログイン成功後は、送信されたパスワードから計算された標準のArgon2idレコードに置き換えます。
このラッパーは Argon2id(password) と同等ではありません。すでに漏洩した内部ダイジェストを取り消すことはできず、過去のエンコーディング、切り捨て、脆弱なパスワードの問題を修正することもできません。レガシーハッシュやバックアップが漏洩した場合、アカウントが特権アカウントである場合、または形式のセマンティクスが不明確な場合は、リセットを強制し、関連セッションを無効化し、独立した検証フローを通じてリカバリを行います。長年非アクティブな一般アカウントも、期限到達時にパスワードログインを凍結し、最も脆弱な検証器を永久に保持し続ける代わりに復帰時のリカバリを利用させることができます。
ステップ6:ペッパーのバージョン管理と侵害対応を設計する
ペッパーを使用する場合、パスワードハッシュの前にレビュー済みの鍵付き前処理ステップを実行するか、HMACで出力を保護します。正確な構成は成熟した設計とセキュリティレビューに基づいて決定する必要があります。データベースには非秘密のペッパーバージョン識別子のみを保存し、実際の鍵はデータベースおよびそのバックアップの外で保持します。認証サービスは最小権限アクセスを使用し、キャッシュの有効期間や鍵サービス障害時の動作を明示的に定義します。
定期的なローテーションでは、現行バージョンと直前バージョンの鍵を短期間併用できます。古いバージョンで検証に成功した後、送信された平文と現行のペッパーから完全なレコードを再計算します。古い鍵を無期限に残すことはできません。ローテーション計画では、旧バージョンに残っているアカウント数をカウントし、移行しなかったアカウントにはリセットまたは凍結のパスを割り当てます。
証拠に基づいて侵害対応を段階的に行います。
- データベースのみの漏洩:証拠を保全し、侵入経路を遮断し、モニタリングを強化し、アルゴリズムとパラメータを評価した上でリセットの要否を決定します。秘密のペッパーは多層防御となりますが、脆弱なパスワードを安全にするものではありません。
- ペッパーのみの漏洩:鍵をローテーションし、アクセスログを調査して、攻撃者がハッシュデータベースも取得した可能性があるかどうかを判断します。
- データベースと該当ペッパーの両方の漏洩:パスワードハッシュの漏洩として扱い、影響を受けたアカウントにリセットを強制し、関連セッションを無効化または有効期間を短縮し、パスワードを使い回しているユーザーに変更を促す警告を発行します。
ローテーションの成功によって過去の漏洩事実が消えるわけではありません。インシデント記録には、影響を受けたバージョン、対象アカウントの範囲、バックアップ、セッション、通知状況、完了率、残存する例外事項を網羅する必要があります。
ステップ7:アカウント列挙とリソース枯渇への防御を両立させる
存在しないアカウント、誤ったパスワード、無効化されたアカウント、移行失敗は、一貫したステータスとレスポンス形状を含む同一の外部エラーセマンティクスを返します。存在しないアカウントに対しては、即時返却と高負荷検証の間の明確な時間差を減らすため、現行ポリシー下で制御されたダミーハッシュを1回実行します。複数混在するレガシーアルゴリズム間では依然として処理時間の差が生じる可能性があります。移行、ランタイムの堅牢化、統計的測定によって悪用可能な差を低減させる必要がありますが、すべてのネットワークレスポンスが完全に一定時間(constant-time)になると約束してはなりません。
高負荷なハッシュ処理自体がサービス運用妨害(DoS)の攻撃対象領域となります。ハッシュキューの手前で、アカウントの存在有無を明らかにしない大まかな送信元スロットリングとリクエスト妥当性チェックを実施します。受け入れられたリクエストに対しては、アカウント別および送信元別の独立したクォータ、グローバルな制限付きキュー、インスタンスあたり24件の同時実行許可(permits)を強制します。IPとユーザー名のペアのみをキーにした単一バケットでは、攻撃者が一方のディメンションを変更し続けることで全体の制限をすり抜けてしまいます。
キューが満杯になった場合、無制限にリクエストを蓄積するのではなく、統一された一時的エラーレスポンスでフェイルファストさせます。スケーリングのトリガーには、リクエスト数だけでなく、キュー滞留時間、アクティブなハッシュ数、メモリの空き容量、CPUスロットリングを含める必要があります。ログには不可逆な内部アカウント識別子、カーディナリティの低いスキームバージョン、結果クラス、スロットリングディメンション、レイテンシバケットを記録します。送信されたパスワード、完全な認証情報レコード、鍵情報は一切含めません。
ステップ8:段階的にロールアウトし、客観的証拠をもって完了とする
読み取り専用のインベントリ調査から開始します。すべてのレガシーレコードが正常にパースできることを確認し、分離された環境で既知のテストベクターを用いて過去の実装を検証します。次に、サポートされるすべてのレガシー形式を読み取れるが、最新形式のみを書き込むコードをデプロイします。新規登録およびパスワード変更では、まずArgon2idで書き込むようにします。小規模なコホートに対してログイン時アップグレードを有効化し、CAS競合、検証失敗、リソース曲線を観測しながら徐々に拡大します。
全面リリース前に、少なくとも以下を検証します。
- すべての過去の形式における、正しいパスワード、誤ったパスワード、境界長、Unicode、不正形式レコードの挙動。
- ログイン時アップグレードと並行パスワードリセットの間の競合状態を検証し、古いログイン処理が新しいパスワードを上書きしたり不正なセッションを発行したりしないことの証明。
- 現行、過去、未知のペッパーバージョンにおけるローテーションおよび障害訓練。
- 攻撃トラフィックが混在する中での、目標値である毎秒1,500件の検証時におけるレイテンシ、メモリ、CPU、キュー滞留、スケーリング。
- 存在しないアカウントおよび各エラークラスにおけるメッセージ、ステータス、サイズ、レイテンシの分布。
- データベース、バックアップ、ログ、メトリクス、エラートラッキングにおける機密フィールドのスキャン。
移行ダッシュボードでは、アルゴリズムとリスク層ごとにグループ化して可視化します(総数、直近のアクティブ数、日次アップグレード成功数、失敗理由、強制リセット完了率、期限)。技術的な完了基準とは、すべての新規書き込みが最新ポリシーを使用していること、ソルトなしSHA-256および漏洩バージョンがゼロであること、不要なレガシー検証器が削除されていること、残存する許容レガシー形式に文書化された例外規定があること、ピーク負荷テストおよび競合テストに合格していること、ペッパーローテーションとリカバリ訓練の証跡が存在すること、認証SLOが合意範囲内に維持されていることです。
質の高い模範回答
「まず2つの攻撃対象領域を分離して考えます。エンドポイントの保護統制はオンライン攻撃を制限しますが、ハッシュデータベース漏洩後のオフライン攻撃を制限することはできません。したがって、パスワードには独立したランダムソルトと調整可能なコストを備えた専用のメモリハードハッシュが必要です。新規レコードには成熟したArgon2id実装を使用し、アルゴリズムバージョン、m/t/p、ソルト、出力を自己記述形式で保持します。ペッパーを有効にする場合は、データベースやそのバックアップの外側にある鍵システムでバージョン管理します。
ブログのパラメータをそのままコピーすることはしません。OWASPの現行の 19 MiB, t=2, p=1 を1つの最小候補とし、本番同等のインスタンス上で単一および並行実行時のp50、p95、p99、CPU、実メモリを測定した上で、通常のピークトラフィックと攻撃トラフィックの負荷テストを実施します。19 MiBで24件並行処理すると作業メモリだけで456 MiBに達するため、サービスには制限付きキュー、同時実行許可、十分なインスタンスヘッドルームが必要です。成熟したライブラリによってレコードごとにランダムソルトを生成し、将来のアップグレードに備えてレコード内にパラメータを保持します。
ログイン時、宣言された形式に基づいて単一の検証器を選択します。レガシーパスワードの検証に成功した後、データベーストランザクションの外部で最新のArgon2idレコードを計算し、古いハッシュが一致している場合のみ更新します。CASが失敗した場合は最新の認証情報を再読み込みし、入力を再検証します。これにより並行ログインを収束させつつ、古いログイン処理が並行パスワードリセットを上書きするのを防ぎます。現在の認証情報状態を確認した後にのみセッションを発行します。新規登録、自発的変更、リカバリでは初日から新しい形式のみを書き込みます。
bcryptおよび許容可能なPBKDF2には、検証のみをサポートする明確な移行期間を設定します。ソルトなしSHA-256はよりリスクの高いキューに分類します。漏洩したことがなければ、新規ソルト付きの低速な外部ラッパーによって短期的な封じ込めが可能ですが、実際のパスワードの再ハッシュ化の代替にはなりません。漏洩したアカウント、特権アカウント、長期非アクティブアカウントは期限までにリセットまたは凍結します。レガシー検証器を永久に残すことはしません。
エンドポイントでは、統一されたエラーと最新ポリシーのダミーハッシュを使用してアカウント列挙の手がかりを減らし、送信元別・アカウント別の独立した制限、グローバルキュー、メモリ同時実行制限を組み合わせてハッシュ増幅DoS攻撃を防ぎます。完了の証跡には、バージョン分布、高リスクなレガシーレコードのゼロ化、移行失敗とCAS競合の推移、ピーク負荷および競合テスト、機密ログのスキャン、ペッパーローテーションと侵害対応訓練を含めます。これらの検証に合格し、予定されていたレガシー検証器が廃止されて初めて完了と判断します。」
よくある間違いと改善策
- パスワードをソルト付きSHA-256で保存する → ソルトはアカウント間での計算の使い回しを防ぎますが、単一レコードに対する高速な推測は防げません → 専用の調整可能でメモリハードなパスワードハッシュを使用する。
- ハッシュは解読不能であると主張する → 候補の推測によって脆弱なパスワードは依然として破られます → 漏洩パスワードのブロックやMFAと並び、オフラインコストを引き上げることが目的であると説明する。
- OWASPのパラメータを永続的に最適であるとみなす → ハードウェア、ライブラリ、インスタンスリソース、トラフィックは変化します → ベンチマーク環境を記録し、再測定を行い、バージョン管理に基づいてアップグレードする。
- すべてのbcryptレコードをオフラインでArgon2idに一括変換する → 平文がなければ標準的な新しいハッシュを導出することはできません → 検証成功時に再ハッシュ化し、残りはリスクに応じてラッピング、リセット、または凍結する。
- 古い値の条件チェックなしに移行更新を行う → ログイン処理が完了済みのパスワードリセットを上書きしてしまう可能性があります → Compare-And-Swapを使用し、失敗時は再読み込みして再検証する。
- ハッシュ処理中にユーザー行のロックを保持し続ける → 高負荷な処理によってロック保持時間と接続プールの占有が増大します → トランザクション外で計算し、短時間の条件付き書き込みを実行する。
- ペッパーを同じデータベースの設定テーブルに保存する → 1回のデータベース漏洩で両方のレイヤーが奪われます → 独立して保護されたシステムに鍵を保存し、バージョンごとに監査する。
- 単一の環境変数を変更してペッパーをローテーションする → 古いレコードを新しい鍵で直接検証できなくなります → 短期間2つのバージョンを保持し、ログイン成功時に再計算するかリセットを要求する。
- 存在しないアカウントに対して即座に応答を返す → 応答時間によってアカウントの存在が露呈します → 統一されたエラー、ダミーハッシュ、実測に基づくレイテンシ分布テストを使用する。
- 同時実行制限なしでArgon2idを実行する → 攻撃者によってメモリとCPUの消費が増幅されるリスクがあります → 二次元のレート制限を適用し、制限付きキューと同時実行許可を設ける。
- bcryptの入力長制限を無視する → 過去の72バイト制限の挙動により、移行中に有効な認証情報が変わってしまう可能性があります → レガシー検証器の挙動を再現し、影響を受けるアカウントには明示的なパスワード変更を要求する。
- 新規登録がArgon2idを使用していることだけを確認する → アクティブまたは高リスクなレガシーレコードが放置されたままになります → アルゴリズム、リスク、アクティビティごとにインベントリを追跡し、ゼロ化および例外基準を設定する。
フォローアップ質問
フォローアップ1:なぜソルトは平文で保存でき、ペッパーは秘密に保たなければならないのですか?
ソルトは各レコードの入力を一意にし、同一のパスワードが同じ出力を生成したり事前計算結果を共有したりするのを防ぎます。ソルトを知っている攻撃者であっても、各レコードを個別に推測しなければなりません。ペッパーは、データベースのみを取得した攻撃者がサーバー側の秘密情報を持たないという点に価値があるため、ハッシュデータベースから分離しておく必要があります。これら2つの統制は役割が異なり、ペッパーが独立したレコードごとのソルトの代わりになるわけではありません。
フォローアップ2:データベース側でArgon2idの反復回数を直接引き上げることはできますか?
ユーザーの平文なしに、古い出力から標準的な高パラメータのパスワードハッシュを導出することはできません。外部ラッパーによって古い出力を一時的に強化することは可能ですが、レコードのセマンティクスが変化し、すでに漏洩した可能性のある出力のリスクを帳消しにすることはできません。標準的なアップグレードには、パスワード検証成功後の再計算か、リセットの要求が必要です。
フォローアップ3:CASが失敗した際、なぜ即座にログインを許可してはいけないのですか?
失敗の理由は、別のリクエストが同一の移行を完了したことである場合もあれば、ユーザーがリカバリによって同時に別のパスワードを設定したことである場合もあります。後者の場合、古いパスワードは無効です。最新レコードを再読み込みして送信された入力を再検証することでこれらを判別できます。検証に失敗したリクエストにセッションを発行してはなりません。
フォローアップ4:Argon2idのパラメータは常に高い方が良いですか?
メモリや時間のコストを高くすると、オフライン攻撃のコストが高まる一方で、正規ログインのリソース消費も増加します。過剰なパラメータは、テールレイテンシの悪化、キューの滞留、インスタンスのOOMを引き起こし、低負荷なリクエストに対するDoS攻撃の増幅装置となってしまいます。適切なポリシーとは、実際のハードウェア、同時実行数、スケーリング、SLOの制約下で測定された持続可能な最高コストであり、ハードウェアの進化や脅威の変化に応じて再評価する必要があります。
フォローアップ5:5年間非アクティブで現在もbcryptを使用しているアカウントはどう処理すべきですか?
権限、漏洩履歴、レガシーパラメータに基づいて分類します。通常の低リスクアカウントは、移行期限後にパスワードログインを凍結し、ユーザーが復帰した際に制御されたリカバリを通じて新しいパスワードを設定させることができます。特権アカウントや漏洩の影響を受けたアカウントは、より早い段階でリセットし、セッションを無効化すべきです。レガシー検証器を永久に残していては、移行が完了することはありません。