確立されたユーザーベースで既存のアプリケーションを維持します。時間の経過とともに、現在のパスワードハッシュ手法は古くなっており、アップグレードする必要があると判断されています。さらに、UXの理由から、既存のユーザーにパスワードの更新を強制する必要はありません。パスワードハッシュの更新全体を画面の背後で行う必要があります。
以下を含むユーザー向けの「単純な」データベースモデルを想定します。
このような要件を解決するにはどうすればよいですか?
私の現在の考えは:
このため、パスワードハッシュを持っているユーザーと更新していないユーザーを区別することができないため、両方をチェックする必要があります。これはひどく欠陥があるようです。
さらに、これは基本的に、すべてのユーザーがパスワードを更新するまで、古いハッシュ手法が無期限に強制される可能性があることを意味します。そのときだけ、古いハッシュチェックを削除して、余分なデータベースフィールドを削除できました。
現在の 'ソリューション'はダーティで不完全であるため、主にここでいくつかの設計のヒントを探していますが、可能なソリューションを説明するために実際のコードが必要な場合は、自由に言語を使用してください。
新しいフィールド「hash_method」を追加することをお勧めします。1は古いメソッドを示し、2は新しいメソッドを示します。
合理的に言えば、この種のことを気にかけ、アプリケーションが比較的長命(明らかにすでにそうである)である場合、暗号化と情報セキュリティは非常に進化しており、予測できない分野であるため、これはおそらく再び発生します。ハッシュが使用された場合、MD5を介した単純な実行が標準であった時代がありました。次に、SHA1を使用する必要があると思うかもしれませんが、ソルト、グローバルソルト+個別のランダムソルト、SHA3、暗号対応の乱数生成のさまざまな方法があります...これは単に「停止」するだけではなく、これを拡張可能で繰り返し可能な方法でうまく修正します。
だから、今あなたが次のようなものを持っているとしましょう(単純化のためにpseudo-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;
}
秘密のエージェントやプログラマーは、この回答の作成に危害を加えませんでした。
新しいハッシュスキームをできるだけ早くすべてのユーザーにロールアウトすることに十分気を付けている場合(たとえば、古いハッシュスキームは本当に安全でないため)、everyパスワード。
アイデアは基本的にハッシュハッシュです。ユーザーが次のログイン時に既存のパスワード(p
)を提供するのを待つのではなく、すでに作成された既存のハッシュで新しいハッシュアルゴリズム(H2
)をすぐに使用します古いアルゴリズム(H1
):
hash = H2(hash) # where hash was previously equal to H1(p)
その変換を行った後でも、パスワード検証を完全に実行できます。ユーザーがパスワードp'
を使用してログインしようとするたびに、以前のH2(H1(p'))
ではなくH1(p')
を計算するだけで済みます。
理論的には、この手法は複数のマイグレーション(H3
、H4
など)に適用できます。実際には、パフォーマンスと読みやすさの両方の理由から、古いハッシュ関数を削除する必要があります。幸い、これは非常に簡単です。次のログインに成功したら、ユーザーのパスワードの新しいハッシュを計算し、既存のハッシュのハッシュをそれに置き換えます。
hash = H2(p)
また、保存しているハッシュを覚えておくために、追加の列が必要になります。パスワードまたは古いハッシュの1つです。残念ながらデータベースリークが発生した場合、このコラムによってクラッカーの仕事が簡単になることはありません。その値に関係なく、攻撃者は古いH2
ではなく、安全なH1
アルゴリズムを逆にする必要があります。
ソリューション(データベースの追加の列)は完全に許容できます。それに関する唯一の問題は、あなたがすでに述べたものです:古いハッシュは変更以来認証されていないユーザーのためにまだ使われているということです。
この状況を回避するには、最もアクティブなユーザーが新しいハッシュアルゴリズムに切り替えるのを待ってから、次のようにします。
認証が長すぎるユーザーのアカウントを削除します。 3年間Webサイトにアクセスしたことがないユーザーのアカウントを削除しても問題はありません。
しばらく認証されていないユーザーの残りのユーザーに、新しいことに興味がある可能性があることを知らせるメールを送信します。古いハッシュを使用するアカウントの数をさらに減らすのに役立ちます。
注意:スパム対策オプションがある場合、ユーザーがWebサイトで確認できる場合は、確認したユーザーにメールを送信しないでください。
最後に、ステップ2の数週間後、まだ古い手法を使用しているユーザーのパスワードを破棄します。認証しようとすると、パスワードが無効であることがわかります。彼らがまだあなたのサービスに興味があるなら、彼らはそれをリセットすることができます。
おそらく2つ以上のハッシュタイプが存在することはないので、データベースを拡張せずにこれを解決できます。
メソッドのハッシュの長さが異なり、各メソッドのハッシュの長さが一定である場合(たとえば、md5からhmac-sha1への遷移)、ハッシュの長さからメソッドを判別できます。同じ長さの場合、最初に新しいメソッドを使用してハッシュを計算し、最初のテストが失敗した場合は古いメソッドを使用してハッシュを計算できます。ユーザーが最後に更新/作成された日時を示すフィールド(図には示されていません)がある場合は、それを使用するメソッドのインジケーターとして使用できます。
私はこのようなことをしました、そしてそれが新しいハッシュであるかどうかを確認し、それをさらに安全にする方法があります。ハッシュの更新に時間をかけている場合は、より安全であることが良いと思います;-)
「salt」という新しい列をDBテーブルに追加し、ユーザーごとにランダムに生成したソルトとして使用します。 (かなりレインボーテーブルの攻撃を防ぐ)
そうすれば、私のパスワードが「pass123」でランダムなsaltが3333の場合、新しいパスワードは「pass1233333」のハッシュになります。
ユーザーがソルトを持っている場合は、それが新しいハッシュであることがわかります。ユーザーのソルトがnullの場合、古いハッシュであることがわかります。
移行戦略がどれほど優れていても、データベースがすでに盗まれている場合(そして、これが起こらなかったことを否定できない場合)、安全でないハッシュ関数で保存されたパスワードはすでに侵害されている可能性があります。これを緩和する唯一の方法は、ユーザーにパスワードの変更を要求することです。
これは、コードを書くことで(のみ)解決できる問題ではありません。解決策は、ユーザーと顧客に、彼らがさらされてきたリスクについて通知することです。それについてオープンにしたいと思ったら、すべてのユーザーのパスワードをリセットするだけで移行を行うことができます。これが最も簡単で安全なソリューションだからです。すべての選択肢は、本当にあなたが台無しにしたという事実を隠すことです。