web-dev-qa-db-ja.com

ユーザーのパスワードハッシュをAESキーとして使用してワンタイムトークンを作成できますか?

ユーザーがパスワードのリセットに使用するワンタイムトークンを作成したい。トークンは一度使用したら無効にする必要があります。現在ハッシュされているパスワードをAESキーとして使用してトークンを暗号化すると、パスワードが変更されるとトークンを復号化できなくなりますか?このトークンはサーバーでのみ暗号化/復号化されます。

このアプローチの欠点/利点は何ですか?

4
JF6IX

あなたが遭遇する問題は、有効なトークンを生成するために必要なものがすべてパスワードハッシュである場合、データベースダンプを持つ攻撃者がすべてのユーザーに対してリセットトークンを生成できることです(これは、最初にパスワードをハッシュする目的を無効にします)。この問題は解決できますが、リセットトークンにランダムな値を使用してハッシュをテーブルに格納するよりもはるかに複雑になります。


重要な注意:これ以上検討しなければ自分で実装することはできません。おもしろくするために、頭の一番上からこれを思いついただけです。私が間違ったことを指摘する機会を他の人に与えてください。真剣に、私はすでに一度それを台無しにしました。


パスワードリセットトークンにはどのプロパティが必要ですか?

  1. 特定のユーザーを識別し、他のユーザーには無効です
  2. パスワードを変更すると無効になります
  3. しばらくすると期限切れ
  4. 秘密で推測は非常に難しい
  5. データベースダンプは、攻撃者に既存のトークンを与えたり、自由にトークンを生成させたりすることはありません

トークンをデータベースに保存せずにリセットしたいので、生成用にこの擬似コードをお勧めします。

token = {
    "user": [username],
    "expiry": [unix timestamp]
}

// Very important!!!
// Password hash should be bcrypt or argon2 with good cost
// Secret application "pepper" is used to salt the key with HKDF, so a database dump won't allow generating reset tokens
key = hkdfsha256(user_password_hash, pepper)

return base64encode(token + hmacsha256(token, key))

確認するには:

data = base64decode(data)
token = data[:-32]
submitted_hmac = data[-32:] // last 32 bytes are hmacsha256
if (token['expiry'] > now()) {
    return false
}
// [retrieve password hash here]
key = hkdfsha256(user_password_hash, pepper)
return hmacsha256(token, key) == submitted_hmac

検査

  1. ユーザー名は、トークンの対象となるユーザーを識別します
  2. パスワードハッシュからHMACキーを取得することは、パスワードが変更されるとトークンが無効になることを意味します
  3. 有効期限チェックにより、未使用のトークンが有効な状態になりすぎるのを防ぎます
  4. パスワードのハッシュとペッパーは、HMAC出力(および完全なトークン)を推測不可能にします
  5. コショウを使用してHMACキーを導出すると、データベースダンプを持つ攻撃者が全員のトークンを生成するのを防ぎます

長所

  • データベースストレージなし
  • トークンの有効性は本質的に現在のパスワードに関連付けられています
  • 現在有効なトークンはすべて、ペッパーを変更することで簡単に無効化できます

短所

  • トークンサイズはやや大きい(15文字のユーザー名が与えられた108 base64文字)。ただし、電子メールのURLには問題ありません。
  • トークンは完全に不透明ではありません。ユーザーはユーザーと有効期限の情報を見ることができます。これはセキュリティには影響しませんが、問題だと思われる人からの迷惑な質問を避けたい場合は暗号化することもできますが、これはIVを追加することを意味します(これを行う場合は暗号化する必要がありますbeforeHMAC)。
  • 攻撃者がデータベースダンプを取得した場合、攻撃者は自由にトークンを生成できます。
  • 一度にいくつの有効なトークンが存在するか、またはどのユーザーに対して存在するかを知る方法はありません。

いくつかの考え

  • パスワードハッシュ全体を使用することは、ソルトがHMACキーの一部になることを意味します。そのため、パスワードがひどく、唐辛子が露出しても、すでに大量のエントロピーが含まれているはずです。このスキームを攻撃するには、ペッパーデータベースとデータベースの両方を公開する必要があります。
  • 本当にトークンを小さくしたい場合は、HMAC出力を16バイトに切り捨て、ユーザー名をnullで終了し、username + 5 byte unix timestamp + 16 byte HMAC。これにより、タイムスタンプによる2038の問題が回避され、ユーザー名が15文字の場合、base64文字が52文字になります。 5バイトのタイムスタンプを実行することはお勧めしませんが、3バイト追加する価値はないようです。
  • 保存しているスペースの量は、すでに保存しているusersテーブルよりも厳密に小さいので、いずれにしてもそれだけの価値があるとは思いません。 たぶんデータベーススキーマを変更できず、リセットトークンを保存する方法がまだない場合。
2
AndrolGenhald

重要な欠点の1つは、時間を制限できないことです。

ユーザーがリセットを要求したが、パスワードを覚えているのでそれを使用しないと想像してください。この時点で、パスワードが変更されるまでの存続期間を持つトークンがあります。

安全な手段を使用して、リセット試行ごとに一意のトークンを生成することをお勧めします。これを、有効期限のある別のデータベーステーブルに配置します。

古いリセットトークンが使用されているかどうかを監視するためのボーナスポイント。これは、疑わしいアクティビティを示している可能性があるため(他の信号と組み合わせた場合)。

1
David

あなたが説明するものには固有の有効期限が含まれていません-したがって、パスワード(および/またはソルト)が変更されるまで有効です。もちろん、リセット要求に関するその他の興味深い事実とともに、暗号化するクリアテキストに有効期限を埋め込むことができます。

AES暗号化とパスワードハッシュの両方が破られる可能性は非常に低いですが、この非常に小さなリスクが、これをキーとして生成することのコストよりも優れているかどうかを検討するのに少し時間を費やす必要があります。他の何らかの方法(ランダムなキーなど)でキーを保存し、それを格納/インデックス付けし、もちろん、使用済み/置き換え済みとしてフラグを立てます。私の最初の腸の反応はノーでした、しかし、私がそれについて考えるほど、それはよりエレガントに見えます。半分以上のパスワードハッシュを使用している場合、Oracle攻撃を埋め込むリスクはないはずです。

属性に格納されている他のデータではなく、必ずパスワードハッシュを使用してください。初期化ベクトルが必要かどうかに関して私よりも暗号法についてよく知っている人からの応答が必要ですが、ペイロードが暗号化ブロックサイズ以下の場合は、私はしません考える何か利点がある。ユーザーが完全な暗号文を入力することを期待している場合(可変プレーンテキストで切り捨てることはできません)、まだ20文字以上の意味不明な文字(IVなし)が表示されていることに注意してください。

1
symcbean

これは、PBKDF2やRFC2898のようなシステムが、キーラッピングと組み合わせて発明されたものです。これらは、ユーザー入力を取得し、パスワードチェックだけでなく、キーマテリアルとしての以降の操作にもそのまま使用できる強力なハッシュベースの文字列を返すように設計されています。これが唯一のオプションではなく、これを行うためのより多くの(そして異なる)システムがこのシナリオで使用されています。

詳細については、パスワードベースの鍵の導出と鍵のラッピングを調べてください。そしていつものようにcrypto:自分でロールしないでください代わりに十分にテストされたライブラリを使用してください。

特定のケース(トークンを使用してアカウントをリセット)の場合:代わりにランダムトークンを生成することに反対するケースはありますか? AESのようなものを使用する理由は、暗号化に使用されたのと同じキーを使用した可逆暗号化であるため、ここではすぐにはユースケースのようには見えません。

1
John Keates