次のシナリオが表示されます。
ユーザーのテーブルを含むMySQL-Databaseがあります。テーブルには2つのフィールドがあります:username
とpassword
。パスワードは無塩ハッシュとして保存されます。
15年以上前のアプリケーションは、このデータベースを使用して、サービスへのアクセスを認証します。内部からのみアクセスできます。
私たちのチームは、過去15年間に学んだベストプラクティスと教訓に従って新しいアプリケーションを提供することにより、これらのサービスを最新化することを求められています(元のアプリケーションでは無視された可能性があります)。このアプリケーションは、広く開かれたインターネットからアクセスできるものとします。
現在のデータベースを認証の目的で再利用して、両方のアプリケーションを基礎となる同じ認証データベース上で並行して実行できるようにすることになっています。
このアプリケーションは同僚や私に関する個人情報の取り扱いに関するものであるため、セキュリティに関していくつかの懸念を表明しました。
最新化の一環として、パスワードポリシーを導入し、パスワードをソルトとペッパーの両方で保存することにしました。
これは、データベーステーブルが3番目のフィールドsalt
を取得し、password
フィールドがソルトおよびペッパーハッシュを格納することを意味します。
ここでの問題は、これが古いアプリケーションの認証を破壊することです。コードは非常にレガシーであり、動作するコンパイラもないため、元のアプリケーションのコードを変更することは問題外です。
したがって、私の質問は次のとおりです。
ここで競合する要件があります。互換性の要件により、古いハッシュを保持する必要があります。セキュリティ要件により、それらを削除する必要があります。ここでは、どの要件を満たす必要があるかを選択する必要があります。
下位互換性を維持する場合は、悪い状況を最大限に活用してください。
または、少し混乱することを恐れない場合:
古いアプリケーションのユーザー認証機能を維持しながら、ソルト(およびペッパー)を認証データベースに追加する安全な方法はありますか?
はい、できます。以下は、高レベルの実装手順です。基本的な手法は、すべてのパスワードをハッシュしてから、クライアントとレガシーサーバー間の接続をMITMして、ハッシュされていないパスワードをハッシュされたパスワードに置き換えることです。展開計画を立てる必要があることに注意してください。本番で盲目的にステップ1を実行すると、すべてが壊れます。
ステップ1:既存のすべてのパスワードをソルトし、ソルトをどこかに保存します。レガシーデータベースのパスワードフィールドをソルトパスワードで上書きします。
ステップ2:シムを作成します。シムは、レガシーAPIと同じパラメーターを受け入れます。
つまり、レガシーAPIが次のように実装されている場合:
LegacyDoStuff(username,password,argument)
{
if(!VerifyCredentials(username,password)) return AuthenticationError();
result = DoStuff(argument);
return result;
}
新しいAPIは次のように実装されます。
NewDoStuff(username,password,argument)
{
hashedpassword = DoHash(password+getSalt(username))
return LegacyDoStuff(username,hashedpassword,argument);
}
手順3:レガシークライアントをメインサーバーではなくシムに向けます(または、同等に、レガシーサーバーを新しいIP/DNSに移動し、シムを古いIP/DNSに置きます)。
このアプローチでは、レガシーコードの内部をブラックボックスとして扱うことができますが、シムはクライアントとクライアントの間でリクエスト/レスポンスを送信する必要があるため、レガシーコードのパブリックサーフェス領域を認識する必要があります。レガシーサーバー。
このアプローチは、他の回答で説明されているアプローチとは異なり、古いパスワードの保存を完全に回避します。ただし、このアプローチは実行がはるかに困難であり、バグが発生する可能性がはるかに高くなります。
古いアプリケーションのユーザー認証機能を維持しながら、ソルト(およびペッパー)を認証データベースに追加する安全な方法はありますか?
いいえ。パスワードをソルトおよびハッシュする理由は、ユーザーデータベースがハッキング/リーク/侵害された場合、攻撃者がユーザーのパスワードにアクセスできないようにするためです( を参照してください)ハッシュのポイントとはパスワード? )。説明するソリューションでは、ユーザーのパスワードは引き続き、ユーザーデータベースの隣接する列にプレーンテキストで格納されます。これは、パスワードをソルトおよびハッシュする目的を完全に無効にします。
明らかに、古いアプリケーションが機能できるように、現在のテーブルを残す必要があります。
新しいアプリケーションで使用するために、salted + pepperedハッシュを含む新しいテーブルを追加します。
これは、パスワード制御システムがいずれかのコンテンツシステムから分離されていることを前提としています。
これは、新しいシステムの脆弱性が(移行後)古いハッシュにアクセスできないことを意味します。データベースに脆弱性が存在する可能性があるため、新しいシステムのデータベース認証情報がアクセスできないテーブルにアクセスできますが、新しいアプリケーションに脆弱性が存在する可能性ははるかに低くなります。
最終的には、全員が少なくとも1回ログインしてハッシュを透過的に更新したか(テーブルにNULLソルトが残っていない)、またはまったくログインしていない人だけが残っています。
無塩ハッシュと塩漬けハッシュを表のすぐ隣に置くことは、レガシーシステムが引き続き使用されている限り、新しいシステムでのベストプラクティスの使用はセキュリティの観点からは無関係であることを示しているにすぎません。
はい、これは可能です。
新しいアプリケーションで、ハッシュのハッシュダブルハッシュアルゴリズムを使用します。例:step1:ソルトされたパスワードをハッシュします。step2:そのハッシュをハッシュします(ソルトなし)。
アプローチ#1:
パスワードを取得してユーザーパスワードのソルトハッシュを生成し、画面に表示するか、クリップボードに無音で表示するシンプルなスタンドアロンアプリを開発します。
すべての内部ユーザーに、古いアプリケーションのパスワードをスタンドアロンアプリの出力にリセットするように依頼します。これから、ログイン時にスタンドアロンアプリにパスワードを入力するプロセスを使用して、ログインに入力されるパスワードハッシュを生成します。画面。視覚的な手がかりを避けるために、出力をクリップボードに生成できます。
アプローチ#2
どういうわけかdbを使用できる場合は、データベースでハッシュのハッシュを実行します。ステップ2をソルトハッシュにして、それだけを保存します。
明確にするために編集します。例:カスタム演算子をサポートするDBを使用している場合、newsaltedhash(oldhash)をカスタムタイプとして格納する列でカスタム等価演算子を宣言します。アプリが一致するユーザー名とoldhash、カスタムオペレーターは受信ハッシュに対してnewsaltedhashを実行して比較を行います。パスワードのリセットは挿入トリガーによって処理されます。
概要:どちらのシナリオでも、アプリは最初に設計されたとおりに機能し、保存されているパスワードハッシュはハッシュ+ソルトダブルハッシュです(Pepperはオプションでいずれかの実装に追加されます)。
この答えは ブライアンの答え と フランクのアプローチ#1 に触発され、どちらも基本的に古いアプリケーションのパスワードとして新しいソルトハッシュを使用します。これには、アプリケーションがデータベースからソルトを取得してハッシュを生成する必要があるため、そのアプリケーションでユーザーを認証し、完全に別のランダムトークンを古いシステムのパスワードとして使用する方が簡単で安全です。
安全なソルトの生成を処理する必要のある、選択した言語のサポートされているパスワードハッシュライブラリを選択してください。おそらく、パスワードを検証するために必要なハッシュ、ソルト、およびその他のパラメータを含む単一の文字列を出力し、将来、より強力なアルゴリズムを段階的に展開できるようにします。たとえば、PHPの場合は、 組み込みのパスワードハッシュ関数 を使用します。
データベースに新しい列を作成して、この単一の文字列を格納します。これを最初に入力する1つの方法は、NewHashFunction(OldStoredHash)
を計算してから、NewVerifyFunction(NewStoredHash, OldHashFunction(UserEnteredPassword))
を実行することです。これに関する既存の議論はたくさんありますが、この回答の残りの部分には違いはありません。
安全でない古いハッシュをすべて完全にワイプしますが、列は落とさないでください。この時点では、誰も古いアプリケーションにログインできないため、修正する必要があります...
新しいアプリケーション(ユーザーが既に認証されている場合)またはスタンドアロンページ(新しいパスワードライブラリを使用してユーザーを認証する場合)でページを作成します。
このランダムなパスワードは、どこに保存する必要もないことに注意してください。ユーザーが再度ログインする場合は、新しいパスワードを生成するだけです。
ランダムなパスワードは、パスワードマネージャーを使用しない限り、ほとんどのユーザーが設定するよりもはるかに強力なものになりますが、ソルトなしで、おそらく高速ハッシュを使用して保存されます。また、ユーザーに表示されたときにパスワードがどこかにコピーされるリスクもあります。ランダムなパスワードを短期間だけ有効にすることで、両方の攻撃をさらに困難にすることができます。
「ランダムパスワードの有効期限」の列をDBに追加します。これは、自動的にログインする場合は非常に短く、ユーザーが自分でパスワードを入力する必要がある場合は数分です。その後、スケジュールされたジョブは1分に1回実行され、期限切れになったすべての行の古いパスワードフィールドを空白にします。