web-dev-qa-db-ja.com

暗号化キーを保存する場所

サーバー上のPostgresqlデータベースにメッセージを保存する安全なシステムを構築しています。メッセージは、PHPの暗号化された openssl_encrypt() 関数とAES-256-CBC 方法。

現時点では、この関数(ApplicationKeyと呼びます)に使用されるキーは、www-dataユーザー(0400)の読み取りアクセスのみが可能なサーバーにファイルとして保存されています。

しかし、ファイルはサーバー上に存在します...私が実際に望まないもの。そこで、UserHashを介してローカルでキーを取得するセキュリティの別のレイヤーを構築しました。

新しいレイヤーの定義:

サーバーのファイルシステムにキーをプレーンテキストで保存する代わりに、ユーザーごとにApplicationKeyの暗号化バージョンを作成し、データベースに保存します。

UserCipherKey = encrypt(ApplicationKeywithUserHash
storeInDb(UserId、UserCipherKey)

このテーブルには、すべてのユーザーのApplicationKeyからの暗号化された暗号が含まれています。暗号化に使用されるこのUserHashは、次のように構築されます。

UserHash = sha256(UserPassword + UserName + Blowfish)

UserPasswordUserNameの両方がクライアントによって入力される場合、Blowfishサーバーから来ます。このUserHashは、暗号化されたメッセージのロックを解除するApplicationKeyのキーです。

ユーザーがページをリクエストするたびにパスワードを入力してほしくないので、UserHashをどこかに、できれば時間制限を付けて、たとえば30分間の非アクティブな使用で保存したいと考えています。

PHPセッションはクライアントにバインドされているため、クライアントがウィンドウを閉じるとセッションが破棄されます。非アクティブになると、セッションが破棄されるなど...このセッションでは、サーバーのファイルシステムにも保存されているため、1に戻ります。ファイルシステムが危険にさらされると、sess_ *ファイルを含むディレクトリが公開され、ハッシュも変更されます。 ..私の懸念がわかりますか?

私の問題/懸念
1-UserHashをローカルに保存したい場合は、php-sessionを使用する必要があります。 HTML5の Local Storage より良い/より安全なphp-sessionsを使用していますか?

2-誰かが自分のパスワードを紛失したため、リセットする必要がある場合。これを行うための最良の方法は何ですか?

3-または、私はひどく間違っていることをしていますか?

12
stUrb

私はあなたの状況を理解しています:

  • ユーザーが読み取ることができる必要があるデータ(「メッセージ」)をサーバーに保存する必要があります。
  • サーバーは、一時的な場合に限り、ある時点でデータを読み取ることができる場合があります。
  • しかし、サーバーはこの能力を永久に維持すべきではありません。攻撃者がサーバーのハードディスクの完全なコピー(フルバックアップなど)を盗んだ場合、データを読み取ることはできません。
  • ユーザーであるユーザーは、自分のストレージ容量がほとんどありません。せいぜい、彼はpasswordおよび/またはおそらくユーザーが自宅でいくつかの紙に書き留めた回復コードを覚えているかもしれません(ただし、ユーザーは自分のパスワードの入力を受け入れることができます)日常的に、彼は巨大で太った回復コードを入力することを拒否します。「回復コード」は、忘れられたパスワードから回復する方法でなければなりません)。

これらの条件下で、only可能な解決策は、パスワードベースの暗号化が行われることです。実際、サーバー側に保存されたデータとユーザーパスワードの組み合わせは、データを回復するのに十分でなければなりません。私たちはWebについて話しているので、クライアントは計算タスクに関しては微弱です。したがって、暗号化と復号化はサーバーで実行する必要があります。とにかく、サーバーで復号化を実行して、サーバーでデータの書式設定(メッセージをHTMLに変換)も行われるようにします。

あなたが説明することは真実から遠くない(「真実」は「あなたが望むことができる最高のもの」です)。つまり、次のことを行う必要があります。

  • ユーザーごとに、ユーザー固有の対称キーがありますKあなた。そのキーは、ユーザーアカウントが作成されたときにサーバー側で生成され、強力なPRNGで適切な長さになります(たとえば128ビット-256ビットはやり過ぎですが、投資家を引き付けるために大きな数が必要な場合は256ビットを使用してください) )。
  • ユーザーごとに、「回復コード」RあなたKとともに生成されますあなた、同様の特性を持つ。これは、ユーザーがパスワードを忘れた場合に使用されます。 Kの暗号化あなたRを使用あなたキーとして、サーバーに格納されます(これをと呼びましょう)Fあなた)。 Rあなたは、印刷するか書き留めて安全に保管するための指示とともにユーザーに送信されます。
  • 各ユーザーにはパスワードPがありますあなた。パスワードのハッシュは、確かにbcryptのような優れた パスワードハッシュ関数 を使用してサーバー上で計算されます。この操作には、ユーザー固有のランダムソルトが必要ですSあなた、サーバーに保存されます。ハッシュ値は キー導出関数 でいくつかのビット(たとえば256ビット)に拡張されます。 256ビットだけが必要な場合、SHA-256はKDFとしてうまく適合します。

    ハッシュ出力のみを拡張したいことに注意してください。ほとんどのbcryptライブラリは、ソルトおよびハッシュ出力をエンコードするstringとして出力を生成します。ソルトを保存する必要があるため、両方の値を個別に回復する必要があります。また、KDFへの入力用にハッシュ出力のみを使用する必要があります。 bcryptをPBKDF2に置き換える方が簡単な場合があります。PBKDF2は独自のKDF(出力サイズは構成可能)であり、ほとんどのPBKDF2実装はすでにソルトと出力を別々に処理しています。

  • KDF出力はsplitを2つの半分に分割します(たとえば、2つの128ビット値)。前半はVあなた、およびパスワード検証トークンです。サーバーはそれを保管します。後半はKを暗号化するためのキーとして使用されますあなた;その暗号化された値(それをと呼びましょう)あなた)はサーバーに保存されます。

したがって、ユーザー[〜#〜] u [〜#〜]の場合、サーバーはSを保存しますあなた(ユーザーソルト)、Fあなた(ユーザーキーの暗号化Kあなた回復コード付き)、Vあなた(パスワード検証トークン)およびEあなた(ユーザーキーの暗号化Kあなたパスワードから派生したキーを使用)。ユーザー[〜#〜] u [〜#〜]のすべての「メッセージ」は、キーKで暗号化されますあなた

ユーザーがログインすると、名前([〜#〜] u [〜#〜])とパスワード(Pを送信しますあなた)。 [〜#〜] u [〜#〜]をインデックスとして使用して、サーバーは保存されたデータを復元します。 PあなたおよびSあなた、サーバーはパスワードハッシュを再計算し、KDFで拡張します。 KDF出力の前半がVに等しくない場合あなたユーザーのパスワードが間違っているため、ユーザーは拒否されます。それ以外の場合、ユーザーはauthenticatedです(サーバーは、クライアントが本当に本物のユーザーであることをある程度保証しています)[〜#〜] u [〜#〜] )、およびKDF出力の後半を使用してEを復号化できますあなた、キーを生成Kあなた。その時点で、サーバーはKを認識していますあなたすべての暗号化/復号化ビジネスを行うことができます。

ユーザーが自分のパスワードを変更したい場合、新しいソルトが生成され、新しいパスワードが入力され、ハッシュ関数が再度計算され、新しい検証トークンと新しいEが生成されますあなた。キーKあなたは変更されないため、保存されたメッセージは一切変更する必要はありません。

ユーザーが自分のパスワードを忘れた場合、回復コードを回復するために一種の「代替パスワード」として使用できますKあなたその時点で、「パスワード変更」の状況に戻ります。


いくつかの重要な注意事項について説明します。

サーバーがユーザーキーを学習するときKあなた、サーバーはそれをRAMにのみ保持する必要があります。したくないKあなたディスクをヒットします(これが練習のポイントです)。したがって、いくつかの点に注意する必要があります。

  • PHPセッション がディスクに書き込まれます。セッション変数に入れたものがファイルになります!しかし、目的地を選択できます。例えば RAMベースのファイルシステム を使用してPHPにfilesとして表示されますが、実際にはRAMです。または、 共有メモリのサポート があるため、ファイルはまったく作成されません(これはより安全な場合があります。攻撃者がライブサーバーをハイジャックした場合、tmpfsを含むすべてのファイルを読み取ることができますが、そのような場合攻撃者はPHPのRAMを直接略奪し、すべてのセッションデータを読み取ることもできます)。

  • 仮想メモリ 、別名「スワップスペース」により、カーネルが通常RAMにあるものをディスクに書き込むように誘導できます。スワップ領域はバックアップされませんが、まだ書き込み中です。勤勉な攻撃者は、古くなったハードディスクを探してゴミ箱をスキャンします。ディスクに障害が発生した場合(電子ボードが揚げられている場合)、その内容を(少なくとも簡単に)消去することはできませんが、攻撃者は(ボードを交換することにより)データを回復できます。

    スワップを完全に無効にすることをお勧めします。十分なRAMがある限り、スワップなしで実行しても問題ありません。とにかく十分なRAMを確保したいのですが、特にPHPのようなGCベースの言語では、スワップ領域にアクセスするとパフォーマンスが大幅に低下するためです。仮想メモリはこれらのアイデアの1つであり、非常に優れていました

  • ある程度、セッション変数に入れたくないものは、Cookieとしてクライアントに保存できます。 Cookieは実際には永続的ではありません(ユーザーが複数のデバイスを使用している場合、ブラウザー間でCookieが共有されない可能性があります)が、ユーザーのコンピューターにアクセスできるのと同じくらい安全なストレージから恩恵を受けることができます。

  • messages...について話しているので、このユーザーがログインしていないときに、ユーザーの一部のデータを暗号化したい場合があります。その場合、サーバーはKあなた。この問題を解決するには、 非対称暗号化 が必要です:makeKあなた公開鍵と秘密鍵のペアの秘密鍵。公開鍵は「現状のまま」(暗号化されずに)データベースに格納されます。 Encryptionは公開鍵を使用しますが、decryptionは秘密鍵を必要とします。

    非対称暗号化を適切に行うことは、一種の複雑です。いくつかの暗号化アルゴリズムのアセンブリが必要であり、テストでは検出できない壊滅的なミスの余地が十分にあります。既存のフォーマットとソフトウェアに依存することを強く推奨します。例えば PHPのGnuPGバインディング

  • クライアントとサーバー間のすべての通信でSSL、つまりHTTPSを使用することを強く希望します。関連するCookieは HttpOnly and Secure とマークされます。

16
Thomas Pornin

HTML5ローカルストレージを使用し、クライアント側でコンテンツを復号化することを検討する必要があります。これにより、サーバーがプレーンテキストデータにアクセスすることはありません。

とはいえ、完璧な解決策はありません。 XSSの脆弱性またはサーバーの侵害および変更されたファイル(および変更されたJavaScript)は、クライアント側のシークレットを取得し、それをサードパーティのサイトに投稿する可能性があります。

「暗号化されたキーをクライアントのCookieに保存するのはどうですか?」 -サーバーは、SSLのみに使用するように構成されていない場合、すべての要求、おそらくHTTP要求(おそらく画像リソース)でそれにアクセスできます。

2
u2702

サーバーがデータにアクセスすることを不可能にする必要はありません(少なくともこの例ではそれが必要なので)、簡単にリークしないことを確認する必要があります(特にディスク上)。

したがって、2つの可能な保存場所は、サーバーのRAM /メモリ(ただし、PHPでそれを行う方法がわからない)または外部プロセス(たとえばmemcached)であり、マテリアルを期限切れにしてサーバーを認証することができます。 。

キーをmemcachedインスタンスに保存する場合、Webサーバーだけが知っているシークレットでキーを暗号化することもできるため、memcached演算子を信頼する必要はありません。 memcachedには適切なPHP APIがあり、一般的には適切なセッションストアである可能性があります。

0
eckes