ユーザーのログインを提供するWebサイトを構築しています。そのために、私は現在、認証を処理するための優れた戦略を研究しています。
私の現在のコンセプトは、今のところ一般的なコンセンサスと思われるものをモデルにしています。パスワードは/dev/urandom
の64バイトでソルトされ、100ラウンドのSHA-512でハッシュされます。すべてのラウンドの後、元のパスワードが結果に連結され、次のラウンドに送られます。ユーザーがログインする場合、ユーザーは資格情報をサーバーに送信します。ここで、説明されている手順が繰り返され(同じソルトを使用して)、データベース内のハッシュと比較されます。
この戦略は十分に安全だと思われます(私が間違っている場合は修正してください。基本的には、多くのオンラインガイドを読んだり、YouTubeビデオを視聴したりした結果です)。しかし、私はそれがクライアントに平文でパスワードをサーバーに送信しなければならないという大きな欠陥があると思います。はい、当然HTTPSを使用しますが、何らかの理由で接続が何らかの理由で危険にさらされた場合は、パスワードも同様です。
したがって、私はPGP鍵を使用するというまったく異なるアプローチを考えました。ユーザーがサインアップすると、PGPキーペアが生成され、選択したパスワードを使用して秘密キーが暗号化され、サーバーに送信されます。再度ログインする場合、サーバーはランダムな文字列を生成し、公開鍵を使用して暗号化します。暗号化されたランダムな文字列とキーのペアは、クライアントに送信されます。クライアントは、秘密キーのパスワードを持っていることを証明するために、再度解読する必要があります。
この方法では、パスワードがネットワークを介して送信されるのを防ぎ、ユーザー間のエンドツーエンドの暗号化されたチャットなどの優れた機能を実現できます。私が見つけることができる唯一の欠点は、サーバーが暗号化された秘密鍵を基本的にそれらを要求するすべての人に配布しなければならず、総当たり攻撃がはるかに簡単になることです。クライアント側で計算コストの高いキー拡張アルゴリズムを実行し、その結果を使用して秘密キーを暗号化することで、これを軽減できます。
しかし、私はまだすべてを本当に信頼しているわけではないので、これが良いアイデアであるかどうか、または私が今それをやっている方法に固執する必要があるかどうかについてのフィードバックをお待ちしています。
いくつかの回答に基づいて、私の質問は少し誤解を招くと思います。私の要件は、ユーザーが認証を成功させるために提供する必要があるのは、使用しているデバイスや以前にそのデバイスにログインしたことがあるかどうかに関係なく、ユーザー名/パスワードだけであるということです。
自分で考案したスキームで「実際の」Webアプリケーションを保護しようとするべきではありません。そのため、このようなメソッドを実際に実装または使用する方法の実用性については説明しないでください。
あなたのスキームは、ネットワークを介してパスワードを送信しません。すぐに飛び出すのは、秘密鍵をサーバーに送信することです。サーバーはそれを使用して何もしないが、理由はありません。はい、おそらくパスワードでまだ保護されていますが、なぜですか?
他の人が指摘したように、秘密鍵は所有者に残されることになっています。
秘密鍵のパスワード暗号化を考慮しても、少なくとも次の問題があります。
少し変更すると、スキームは非常に単純な形式の公開鍵認証になります。
このようなことは実際にはhttpsで クライアント証明書 を使用することですでに可能です。同じことがsshにも使用されます。
ThoriumBRで言及されているチャレンジ/レスポンスメカニズムは、認証の別の方法ですが、そこで説明されている実装では、サーバーがパスワードをクリアテキストで保存していると想定しています。
いいえ防御したいシナリオは、誰かが暗号化されたhttps接続内に侵入することです。これが事実であると想定すると、パスワードを読み取るだけでなく、セッションCookieとセッションに表示されるすべてのデータを盗むこともできます。その場合、攻撃者はパスワードを読み取ったかどうかに関係なく、すでに勝利しています。
もちろん、独自の暗号化と認証を導入することもできますが、問題は、これがhttps接続よりも優れていると思う理由です。
現実的なシナリオは、誰かがhttps接続を破壊することではなく、誰かがサーバーのデータベースに侵入して、オフライン攻撃でパスワードを「回復」しようとすることです。これは、ほとんどの認証スキームが防御しようとするものです。
パスワードの危険性は、パスワードが傍受可能であることではなく、推測される可能性があることです。
「実際の」Webアプリケーションの場合は、確立されたフレームワークとよく知られた認証ライブラリを使用します。間違えやすく、正しく理解するのが非常に難しいため、自分で記述しないでください。
パスワードのハッシュにSHAを使用しないでください。専用のパスワードハッシュ関数( bcrypt や PBKDF2 など)を使用して、攻撃をより困難にします( this答え も)。アプリに [〜#〜] totp [〜#〜] メカニズムを使用または追加することもできます。これは単なるパスワードよりも優れています。
私が答えを書いた後、あなたは以下を追加しました:
私の要件は、ユーザーが認証を成功させるために提供する必要があるのは、ユーザーが使用しているデバイスや以前にそのデバイスにログインしたかどうかに関係なく、ユーザー名/パスワードのみであるということです。
これは、秘密鍵をサーバーに送り返す理由を説明していますが、それが悪い考えである理由は変わりません。
ThoriumBRが提案するように、チャレンジ/レスポンスメカニズムを使用することもできます。 [〜#〜] srp [〜#〜] のような高度なチャレンジ/レスポンスメカニズムもあり、サーバーにパスワードを保存しないように巧妙な計算を使用しています。
ただし、これらすべてのメカニズム、およびクライアント側のハッシュ( この答え を参照)には、1つの共通点があります。
クライアント側でコードを実行する必要があります。
Webアプリでは、これはサーバーから送信されるJavaScriptのみです。クライアント側の実装は問題ではありません( OpenPGPライブラリ もある)が、https接続のセキュリティについて心配しているため、別の問題があります。
したがって、真ん中の人は、悪意のあるクライアント側のコードを注入してパスワードを盗み、巧妙なメカニズム全体を時代遅れにする可能性があります。
他の人が言ったように、秘密鍵は共有されません。フルストップ。それ以外の場合、それらは公開鍵と呼ばれます。
しかし:暗号によるサインオンはかなり標準的です。 HTTPSは以下をサポートします:
HTTPSクライアント証明書を使用した認証 ;そして、最近のすべてのブラウザには、要求されたときに必要なキーを生成する組み込みの手段があります。
あなたの問題はすでに解決されています。ユーザーがキーペアを生成し、公開キーを提供し、秘密キーを保持し、接続時にサーバー クライアントに尋ねる は、ユーザーが秘密キーを所有していることを証明します(本質的に、何かに署名させることにより) 。
できた!
他の暗号的に確立された方法には、私が知っている少なくとも1つの方法が含まれます。
Kerberosチケットによるシングルサインオン–これは、Active Directoryを使用するイントラネットで非常に広く普及しています。繰り返しますが、最近のブラウザー(少なくともEdge、Internet Explorer、Firefox)は、WindowsとLinux(OS XまたはFreeBSDで試していない)の両方で、すぐにこれをサポートしています。
ログイン用のPGPのアイデアは気に入っていますが、これは動作するはずの方法ではありません。 privateキーはクライアント側に残しておく必要がありますalwaysで、誰とも共有しないでください。この設定は、悪い習慣を奨励し、PGPに関する誤った教育を提供します。
あなたができることは、ユーザーにpublicキーをサーバーにアップロードしてもらい、ログイン時にchallenge(基本的には暗号化された文字列)を提示することです。秘密鍵と関連するパスフレーズ。
問題は、PGPを知っている人や、PGPを使いたいという深刻な傾向がある人がほとんどいないことです。できればそれのために行きなさい。
匿名が言ったように、秘密鍵mustは常に秘密です。秘密鍵を任意のエンティティに送信すると、ログインによって提供されるセキュリティが無効になります。
基本的に、設計しているものはチャレンジレスポンス認証と呼ばれ、キーペアがなくても実行できます。
一方の当事者(サーバー)がランダムなトークンを生成し、それを他方に送信します
もう1人(クライアント)は、自分のパスワードをトークンと連結し、ハッシュして、返送します。
サーバーはトークンとパスワードの両方を認識し、それらの値を連結してハッシュし、クライアントが送信したものと比較します
キーペアを使用する場合は、キーペアを使用して署名できます。
ランダムな値R1を生成してユーザーに送信する
ユーザーはR1を受け取り、秘密キーを使用して署名
サーバーはユーザーの公開鍵に対して署名をチェックします
これは技術的には簡単ですが、ほとんどのユーザーがキーペアを生成するために必要なノウハウを持ち、キーペアを使用して署名することはできないと思います。
同じ概念を実装する別のアプローチは [〜#〜] srp [〜#〜] です。プロトコルでの計算を無視すると、ユーザーがサインアップすると、クライアントは資格情報を検証するのに十分なパスワード検証を生成しますが、資格情報として機能せず、サーバーに送信して保存します。
ログイン時に関係者はランダムな値に基づいてメッセージを交換し、最終的にはサーバーが格納されたベリファイアを使用してクライアントが正しいパスワードを持っていることを確認します。クライアントがサーバーに送信するメッセージは、パスワードとランダムな値から一方向に導出されるため、パスワードがネットワーク上で送信されることはありません。サーバーからのランダムなメッセージは、クライアントからのメッセージが現在のログイン試行に対してのみ有効であることを保証します(HTTPSが失敗した場合にMITMがログイン試行を再生できないようにします)。
プロトコルの実装は困難ですが、すぐに使用できる実装があります。 AWS CognitoとそのクライアントのJS SDKなので、どのWebアプリにも実装できます。
質問で説明されているスキームは、パスワードに対するブルートフォース攻撃をオフラインで有効にする際に重大な問題を抱えていますが、この脆弱性なしでパスワードを再設計することは可能です。
たとえば、 パスワードから直接秘密鍵を生成する ができます。これにより、秘密鍵を保存する必要がまったくなくなります。基本的にはパスワードが秘密鍵になります。
ただし、実際のWebアプリケーションの実装では、クライアント側のJavaScriptで暗号化を行う必要があります。また、トランスポートとしてのHTTPSが壊れている場合、そのJavaScriptの一部を危険にさらして、プレーンテキストでパスワードを提供することも可能です。
このように正しく実装されたスキームは、サーバー側でのhttps +ハッシュを介したプレーンテキストの現在の標準的なプラクティスよりも少し安全です。しかし、徹底的なレビューがなければ、暗号化コードに微妙だが重大な間違いを犯しやすく、アプリケーションの他の部分を保護するための努力がより効果的になります。
これはすでに存在しています。
OpenPGPには、可能なキーの使用法として「認証」があり、gpg-agentがssh-agentモードで使用されている場合、PGPキーを使用してSSHセッションを認証できます。私はスマートカードを使用したシングルサインオンソリューションとして毎日使用していますが、これが通常のキーでは機能しない理由はありません。
Gpg-agentを使用してTLSハンドシェイク中にメッセージに署名できるさまざまなプラグインもあり、ユーザーはSSLクライアント証明書認証にPGPキーを使用できます。
キーがユーザーのデバイスを離れることはないため、新しいキーを生成する必要はなく、ユーザーは既存のキーで登録するだけで済みます。
どのキーがどのユーザーに有効かを決定するには、サーバー側に十分なPKIインフラストラクチャが必要です。ほとんどのサービス(gitlab、github、launchpadなど)では、ユーザープロファイルエディターのフォームに公開鍵をアップロードできます。