既存のユーザーに新しいパスワードを強制せずにパスワードハッシュを更新する


32

確立されたユーザーベースで既存のアプリケーションを保守します。時間が経つにつれて、現在のパスワードハッシュ手法は時代遅れであり、アップグレードする必要があると判断されています。さらに、UXの理由により、既存のユーザーにパスワードの更新を強制することは望ましくありません。パスワードハッシュ更新全体は、画面の背後で行われる必要があります。

以下を含むユーザー用の「単純な」データベースモデルを想定します。

  1. ID
  2. Eメール
  3. パスワード

このような要件を解決するにはどうすればいいですか?


私の現在の考えは次のとおりです。

  • 適切なクラスに新しいハッシュメソッドを作成します
  • データベース内のユーザーテーブルを更新して、追加のパスワードフィールドを保持する
  • ユーザーが古いパスワードハッシュを使用して正常にログインしたら、2番目のパスワードフィールドに更新されたハッシュを入力します

これにより、パスワードハッシュを持っているユーザーとパスワードハッシュを更新していないユーザーを合理的に区別できないため、両方のチェックを強制されるという問題が残ります。これは恐ろしく欠陥があるようです。

さらに、これは基本的に、すべてのユーザーがパスワードを更新するまで、古いハッシュ手法を無期限に保持することを意味します。その時点でのみ、古いハッシュチェックの削除を開始し、不要なデータベースフィールドを削除できました。

私の現在の「ソリューション」は汚れていて不完全であり、そうではないので、ここでいくつかのデザインのヒントを探していますが、可能なソリューションを記述するために実際のコードが必要な場合は、自由に言語を使用してください。


4
セカンダリハッシュとセットセカンダリハッシュを区別できないのはなぜですか?データベース列をヌル可能にして、ヌルをチェックするだけです。
キリアンフォス

6
新しいハッシュのフィールドではなく、ハッシュ列を新しい列として使用します。ハッシュを更新するときに、タイプフィールドを変更します。そうすれば、これが将来再び発生した場合、あなたはすでに対処する立場にあり、古い(おそらく安全性の低い)ハッシュを保持する可能性はありません。
マイケルコーン

1
あなたは正しい軌道に乗っていると思います。6か月後または1年後、残りのユーザーにパスワードの変更を強制できます。1年後、または何であれ、古いフィールドを取り除くことができます。
グレンペターソン

3
なぜ古いハッシュをハッシュしないのですか?
Siyuanレン

あなたがしたいので、パスワードはありませんし、「dehashed」にする必要があることを別の値- (ユーザーによって与えられたハッシュされた1と比較することすなわち)それをunhashingはあなたを与えるように(ない古いハッシュ)ハッシュ化されるように...パスワードを古い方法で。可能だがクリーナーではない。
マイケルデュラント

回答:


25

新しいフィールド "hash_method"を追加することをお勧めします。おそらく1は古いメソッドを示し、2は新しいメソッドを示します。

合理的に言えば、この種のことを気にし、アプリケーションが比較的長命である場合(明らかにそれはすでにそうです)、暗号化と情報セキュリティは進化している、かなり予測不可能な分野であるため、これはおそらく再び起こるでしょう。ハッシュがまったく使用された場合、MD5を介した単純な実行が標準であった時代がありました!次に、SHA1を使用する必要があると思うかもしれませんが、今ではソルティング、グローバルソルト+個々のランダムソルト、SHA3、暗号対応の乱数生成のさまざまな方法があります...これは単に「停止」するわけではないので、これを拡張可能で繰り返し可能な方法で修正します。

だから、今あなたは次のようなものを持っているとしましょう(簡単にするために擬似javascriptで、私は願っています):

var user = getUserByID(id);
var tryPassword = hashPassword(getInputPassword());


if (user.getPasswordHash() == tryPassword)
{
    // Authenticated!
}

function hashPassword(clearPassword)
{
    // TODO: Learn what "hash" means
    return clearPassword + "H@$I-I";
}

より良い方法があることに気付いたら、ちょっとしたリファクタリングをするだけです。

var user = getUserByID(id);
var tryPassword = hashPassword(getInputPassword(), user.getHashingMethod());

if (user.getPasswordHash() == tryPassword)
{
    // Authenticated!
}

function hashPassword(clearPassword, hashMethod)
{
    // Note: Hash doesn't mean what we thought it did. Oops...

    var hash;
    if (hashMethod == 1)
    {
        hash = clearPassword + "H@$I-I";
    }
    else if (hashMethod == 2)
    {
        // Totally gonna get it right this time.
        hash = SuperMethodTheNSASaidWasAwesome(clearPassword);
    }
    return hash;
}

この回答の作成において、秘密のエージェントやプログラマーに害はありませんでした。


+1これは非常に賢明な抽象化のように思えます、ありがとう。ただし、これを実装すると、hashフィールドとhashmethodフィールドを別のテーブルに移動することになります。
ウィレム

1
この手法の問題は、アカウントがログインしないと、ハッシュを更新できないことです。私の経験では、ほとんどのユーザーは(非アクティブなユーザーを削除しない限り)何年もログインしません。塩分を考慮していないため、抽象化も実際には機能しません。標準の抽象化には、検証用と作成用の2つの機能があります。
CodesInChaos

パスワードハッシュは、過去15年間でほとんど進化していません。Bcryptは1999年に公開され、現在でも推奨されているハッシュです。それ以降、重要な改善点が1つだけ発生しました。scryptによって開拓された連続メモリハードハッシュです。
CodesInChaos

これにより、古いハッシュが脆弱になります(たとえば、誰かがDBを盗む場合)。これは、ある程度新しい、より安全な方法を緩和するとともに、各古いハッシュをハッシュ(あなたはまだ区別するためにハッシュの種類が必要になりますnewHash(oldHash, salt)newHash(password, salt)
dbkk

42

新しいハッシュスキームをすべてのユーザーに可能な限り迅速に展開することに十分に注意を払う場合(たとえば、古いハッシュスキームは非常に安全ではないため)、実際にはすべてのパスワードを瞬時に「移行」する方法があります。

基本的には、ハッシュハッシュするという考え方です。ユーザーpが次のログイン時に既存のパスワード()を提供するのを待つのではなく、古いアルゴリズム()によってすでに生成された既存のハッシュH2に対して新しいハッシュアルゴリズム()をすぐに使用します。H1

hash = H2(hash)  # where hash was previously equal to H1(p) 

その変換を行った後でも、パスワード検証を完全に実行できます。ユーザーがpasswordでログインしようとするたびH2(H1(p'))に、前のものではなく計算する必要がありH1(p')ますp'

理論的には、この技術は、(複数の移行に適用することができるH3H4など)。実際には、パフォーマンスと読みやすさの両方の理由から、古いハッシュ関数を削除する必要があります。幸いなことに、これは非常に簡単です。次回のログイン成功時に、単にユーザーのパスワードの新しいハッシュを計算し、既存のハッシュハッシュをそれに置き換えます。

hash = H2(p)

また、保存しているハッシュ(パスワードまたは古いハッシュのいずれか)を記憶するために、追加の列が必要です。残念ながらデータベースリークが発生した場合、このコラムでクラッカーの仕事を簡単にすることはできません。その値に関係なく、攻撃者はH2古いアルゴリズムではなく安全なアルゴリズムを逆にする必要がありますH1


3
ジャイアント+1:H2(H1(p))は通常、H1がひどい場合でも、H2(p)を直接使用するのと同じくらい安全です。なぜなら、(1)H2によって塩とストレッチが処理されるため、(2)問題MD5のような「壊れた」ハッシュは、パスワードハッシュに影響しません。関連:crypto.stackexchange.com/questions/2945/...
oriPを

興味深いことに、古いハッシュをハッシュすると、ある種のセキュリティの「問題」が発生すると考えていました。間違っていたようです。
ウィレム

4
H1が本当にひどい場合、これは安全ではありません。この文脈で恐ろしいのは、入力から多くのエントロピーを失うことです。MD4とMD5でさえ、この点でほぼ完璧であるため、このアプローチは、一部の自作ハッシュまたは出力が非常に短い(80ビット未満)場合にのみ安全ではありません。
CodesInChaos

1
その場合、おそらく唯一の選択肢は、あなたが一生懸命失敗したことを認めて、先に進み、すべてのユーザーのパスワードをリセットすることです。その時点での移行ではなく、ダメージ制御についてです。
-Xion

8

解決策(データベースの追加列)は完全に受け入れられます。唯一の問題は、既に述べた問題です。変更後に認証されていないユーザーには古いハッシュが引き続き使用されるということです。

この状況を回避するには、最もアクティブなユーザーが新しいハッシュアルゴリズムに切り替えるのを待ってから、次のようにします。

  1. あまり長く認証していないユーザーのアカウントを削除します。3年間Webサイトにアクセスしたことがないユーザーのアカウントを削除しても問題はありません。

  2. しばらく認証されていないユーザーのうち、残りのユーザーにメールを送り、何か新しいことに興味があるかもしれないことを伝えます。古いハッシュを使用するアカウントの数をさらに減らすのに役立ちます。

    注意:ユーザーがWebサイトでチェックできるスパム対策オプションがない場合は、チェックしたユーザーにメールを送信しないでください。

  3. 最後に、ステップ2の数週間後に、まだ古い手法を使用しているユーザーのパスワードを破棄します。認証しようとすると、パスワードが無効であることがわかります。彼らがあなたのサービスにまだ興味があるなら、彼らはそれをリセットすることができました。


1

おそらく、2つ以上のハッシュタイプを持つことはないので、データベースを拡張せずにこれを解決できます。

メソッドのハッシュ長が異なり、各メソッドのハッシュ長が一定の場合(たとえば、md5からhmac-sha1に移行する場合)、ハッシュ長からメソッドを特定できます。同じ長さの場合、最初に新しいメソッドを使用してハッシュを計算し、最初のテストが失敗した場合は古いメソッドを使用してハッシュを計算できます。ユーザーが最後に更新/作成された日時を示すフィールド(表示されていない)がある場合、それを使用する方法の指標として使用できます。


1

私はこのようなことをしましたが、それが新しいハッシュであるかどうかを判断し、さらに安全にする方法があります。あなたがハッシュを更新するのに時間をかけているなら、より安全であることがあなたにとって良いことだと思う;-)

DBテーブルに「salt」という新しい列を追加し、ユーザーごとにランダムに生成されるソルトとして使用します。(レインボーテーブル攻撃をほとんど防ぎます)

そうすれば、私のパスワードが「pass123」でランダムソルトが3333の場合、私の新しいパスワードは「pass1233333」のハッシュになります。

ユーザーにソルトがある場合、それが新しいハッシュであることがわかります。ユーザーのソルトがnullの場合、古いハッシュであることがわかります。


0

移行戦略がどれほど優れていても、データベースが既に盗まれている場合(そして、これが起こったことがないことを否定することはできません)、安全でないハッシュ関数で保存されたパスワードが既に侵害されている可能性があります。これに対する唯一の緩和策は、ユーザーにパスワードの変更を要求することです。

これは、コードを記述することで(のみ)解決できる問題ではありません。解決策は、ユーザーと顧客にさらされているリスクについて知らせることです。それについてオープンになれば、最も簡単で安全なソリューションであるため、すべてのユーザーのパスワードをリセットするだけで移行を行うことができます。すべての選択肢は、実際にあなたが台無しにしたという事実を隠すことです。


1
私が過去に行った1つの妥協は、古いパスワードを一定期間保持し、ログイン時にそれらを再ハッシュすることですが、その時間が経過するとログインしていない人にパスワードのリセットを強制しますその時。したがって、まだ安全性の低いパスワードを保持している期間がありますが、時間は制限されており、定期的にログインするユーザーの混乱も最小限に抑えられます。
ショーンバートン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.