web-dev-qa-db-ja.com

データベース内のプレーンテキストパスワードの暗号化/ハッシュ

inherited発見したばかりのWebアプリで、SQL Serverデータベースに300,000を超えるユーザー名/パスワードがプレーンテキストで格納されています。これはVery Bad Thing™であることを認識しています。

暗号化/復号化のためにログインとパスワードの更新プロセスを更新する必要があることを知っており、システムの他の部分への影響を最小限に抑えながら、データベースからプレーンテキストパスワードを削除する最良の方法として何をお勧めしますか?

どんな助けも大歓迎です。

編集:不明な場合は申し訳ありませんが、特定の暗号化/ハッシュ方式ではなく、パスワードの暗号化/ハッシュ化の手順を尋ねるつもりでした。

私はただ:

  1. DBのバックアップを作成します
  2. ログインの更新/パスワードコードの更新
  3. 数時間後、ユーザーテーブルのすべてのレコードを調べて、パスワードをハッシュし、それぞれを置き換えます
  4. ユーザーが引き続きパスワードをログイン/更新できることを確認するためのテスト

私の懸念はユーザーの数が多いからだと思うので、これを正しく行っていることを確認したいと思います。

62
Jonathan S.

暗号化されたパスワードのデータベースに列を追加してから、現在のパスワードを取得して暗号化するすべてのレコードに対してバッチジョブを実行する必要があると思います(md5はかなり標準的なハッシュ編集:しかし、単独で使用するべきではありません-良い議論については他の回答を参照してください)、新しい列に保存し、すべてがスムーズに行われたことを確認してください。

次に、ログイン時にユーザーが入力したパスワードをハッシュするようにフロントエンドを更新し、plaintext-vs-plaintextをチェックするのではなく、保存されたハッシュと比較する必要があります。

最終的にプレーンテキストのパスワードをすべて削除する前に、両方の列を少しの間そのままにして、何も変わっていないことを確認するのが賢明なようです。

また、パスワードの変更/リマインダーのリクエストなど、パスワードにアクセスするたびにコードを変更する必要があることも忘れないでください。もちろん、忘れたパスワードをメールで送信する機能は失われますが、これは悪いことではありません。代わりにパスワードリセットシステムを使用する必要があります。

編集:最後に、テストベッドの安全なログインWebサイトでの最初の試行で私が犯したエラーを回避することを検討してください。

ユーザーパスワードを処理するとき、ハッシュが行われる場所を考慮してください。私の場合、ハッシュはウェブサーバー上で実行されているPHPコードによって計算されましたが、パスワードはユーザーのマシンから平文でページに送信されました!とにかくhttpsシステム内(uniネットワーク)であったため、実際に動作していましたが、実際には、javascriptなどを使用してユーザーシステムを離れる前にパスワードをハッシュしてからハッシュを送信したいと思います。地点。

19
xan

編集(2016): Argon2scryptbcrypt 、または PBKDF2 を優先順に使用します。状況に応じて可能な限り大きな減速係数を使用してください。吟味された既存の実装を使用します。適切なソルトを使用していることを確認してください(ただし、使用しているライブラリはこれを確認する必要があります)。


パスワードをハッシュするときは、PLAIN MD5を使用しないでください。

PBKDF2 を使用します。これは、基本的に、ランダムなソルトを使用して Rainbow table 攻撃を防ぎ、ハッシュを遅くするのに十分な回数(繰り返し)反復することを意味します。アプリケーションに時間がかかりすぎますが、攻撃者が大量の異なるパスワードを強引に強制することに気付くには十分です

ドキュメントから:

  • 少なくとも1000回、できればそれ以上-実装の時間をかけて、実行可能な反復回数を確認してください。
  • 8バイト(64ビット)のソルトで十分であり、ランダムはセキュアである必要はありません(ソルトは暗号化されていないため、誰かが推測することを心配していません)。
  • ハッシュ時にソルトを適用する良い方法は、HMACをお気に入りのハッシュアルゴリズムで使用し、パスワードをHMACキーとして使用し、ソルトをハッシュするテキストとして使用することです(ドキュメントの このセクション を参照)。

安全なハッシュとしてSHA-256を使用したPythonでの実装例:

[〜#〜] edit [〜#〜]:Eli Collinsが述べたように、これはPBKDF2の実装ではありません。 PassLib などの標準に準拠した実装を優先する必要があります。

from hashlib import sha256
from hmac import HMAC
import random

def random_bytes(num_bytes):
  return "".join(chr(random.randrange(256)) for i in xrange(num_bytes))

def pbkdf_sha256(password, salt, iterations):
  result = password
  for i in xrange(iterations):
    result = HMAC(result, salt, sha256).digest() # use HMAC to apply the salt
  return result

NUM_ITERATIONS = 5000
def hash_password(plain_password):
  salt = random_bytes(8) # 64 bits

  hashed_password = pbkdf_sha256(plain_password, salt, NUM_ITERATIONS)

  # return the salt and hashed password, encoded in base64 and split with ","
  return salt.encode("base64").strip() + "," + hashed_password.encode("base64").strip()

def check_password(saved_password_entry, plain_password):
  salt, hashed_password = saved_password_entry.split(",")
  salt = salt.decode("base64")
  hashed_password = hashed_password.decode("base64")

  return hashed_password == pbkdf_sha256(plain_password, salt, NUM_ITERATIONS)

password_entry = hash_password("mysecret")
print password_entry # will print, for example: 8Y1ZO8Y1pi4=,r7Acg5iRiZ/x4QwFLhPMjASESxesoIcdJRSDkqWYfaA=
check_password(password_entry, "mysecret") # returns True
49
orip

基本的な戦略は、キー派生関数を使用して、パスワードを「ハッシュ」することです。ソルトとハッシュの結果はデー​​タベースに保存されます。ユーザーがパスワードを入力すると、ソルトとその入力が同じ方法でハッシュされ、保存されている値と比較されます。一致する場合、ユーザーは認証されます。

悪魔は細部に宿っています。まず、選択されるハッシュアルゴリズムに大きく依存します。ハッシュベースのメッセージ認証コードに基づいたPBKDF2のようなキー派生アルゴリズムにより、特定の出力(攻撃者がデータベースで見つけたもの)を生成する入力(この場合はパスワード)を「計算上実行不可能」に見つける)。

事前計算済み辞書攻撃では、ハッシュ出力からパスワードまでの事前計算済みインデックスまたは辞書を使用します。ハッシュは遅い(とにかくそうであるはずです)ので、攻撃者は可能性のあるすべてのパスワードを一度ハッシュし、ハッシュを与えられて、対応するパスワードを検索できるように結果にインデックスを付けます。これは、時間に対するスペースの古典的なトレードオフです。パスワードリストは膨大なものになる可能性があるため、トレードオフを調整する方法(レインボーテーブルなど)があり、攻撃者は少しの速度をあきらめて多くのスペースを節約できます。

計算前の攻撃は、「暗号化ソルト」を使用することにより阻止されます。これはパスワードでハッシュされたデータです。 秘密である必要はありません指定されたパスワードに対して予測不可能である必要があります。塩の値ごとに、攻撃者は新しい辞書を必要とします。 1バイトのソルトを使用する場合、攻撃者はそれぞれ異なるソルトで生成された辞書の256コピーを必要とします。まず、ソルトを使用して正しい辞書を検索し、次にハッシュ出力を使用して使用可能なパスワードを検索します。しかし、4バイトを追加するとどうなりますか?今、彼は辞書の40億のコピーを必要としています。十分な大きさのソルトを使用することにより、辞書攻撃が防止されます。実際には、暗号品質の乱数ジェネレーターからの8〜16バイトのデータが適切なソルトになります。

テーブルの事前計算により、攻撃者は各試行でハッシュを計算しました。パスワードを見つけるのにかかる時間は、候補をハッシュするのにかかる時間に完全に依存します。この時間は、ハッシュ関数の反復により増加します。反復回数は一般に、キー派生関数のパラメーターです。今日、多くのモバイルデバイスは10,000〜20,000回の反復を使用しますが、サーバーは100,000以上を使用する場合があります。 (bcryptアルゴリズムは、「コスト係数」という用語を使用します。これは、所要時間の対数尺度です。)

38
erickson

Xanのアドバイス 現在のパスワードの列をしばらく保持するようにしてください。もし事態が悪化した場合は、すぐに簡単にロールバックできます。

パスワードを暗号化する限り:

  • 塩を使う
  • パスワード用のハッシュアルゴリズムを使用します(つまり、-slow

詳細については、Thomas Ptacekの レインボーテーブルで十分:安全なパスワードスキームについて知っておくべきこと を参照してください。

4
Michael Burr

私はあなたが次のことをすべきだと思う:

  1. HASHED_PASSWORDまたは同様の名前の新しい列を作成します。
  2. 両方の列をチェックするようにコードを変更します。
  3. ハッシュされていないテーブルからハッシュされたテーブルにパスワードを徐々に移行します。たとえば、ユーザーがログインすると、パスワードがハッシュされた列に自動的に移行され、ハッシュされていないバージョンが削除されます。新しく登録されたすべてのユーザーは、ハッシュ化されたパスワードを持ちます。
  4. 数時間後、一度にn人のユーザーを移行するスクリプトを実行できます
  5. ハッシュされていないパスワードがなくなったら、古いパスワード列を削除できます(使用できないデータベースによっては削除できない場合があります)。また、古いパスワードを処理するコードを削除できます。
  6. 完了です!
3
Tamas Czinege

それは数週間前の私の問題でした。大規模なMISプロジェクトを975の地理的に異なる場所に展開しました。ここでは、独自のユーザー資格情報ストアが、既に実装され使用中のさまざまなアプリケーションの認証子として使用されます。既にRESTとSOAPベースの認証サービスを提供しましたが、顧客は他のアプリケーションから読み取り専用のDB接続でユーザー資格情報ストアに到達できると主張しました-関連するテーブルまたはビューのビューのみ。ため息... (この高度に結合された悪い設計決定は、別の質問の主題です。)

そのため、ソルトおよび反復ハッシュパスワードストレージスキームを仕様に変換し、簡単に統合できるようにいくつかの異なる言語実装を提供する必要がありました。

かなり安全なハッシュパスワードまたは [〜#〜] fshp [〜#〜] と略しました。 Python、Ruby、PHP5で実装し、パブリックドメインにリリースしました。 http://github.com/bdd/fshp でGitHubで消費、分岐、フレーム化、または吐き出し可能

FSHPは、ソルトされ、反復的にハッシュされたパスワードハッシュ実装です。

設計原理は PBKDF1 RFC 2898の仕様(aka:PKCS#5:Password-Based Cryptography Specification Version 2.0。) FSHPと似ており、ソルトの長さ、反復回数を選択できますおよびSHA-1およびSHA-2(256、384、512)の間の基礎となる暗号化ハッシュ関数。すべての出力の先頭で自己定義メタプレフィックスを使用すると、消費者が独自のパスワードストレージセキュリティベースラインを選択できるようになります。

[〜#〜] security [〜#〜]

デフォルトのFSHP1は8バイトのソルトを使用し、SHA-256ハッシュを4096回繰り返します。 -8バイトのソルトは、必要なスペースに2 ^ 64を掛けることにより、レインボーテーブル攻撃を非実用的にします。 -4096回の反復により、ブルートフォース攻撃はかなり高価になります。 -このリリースの時点では、SHA-256に対する既知の攻撃はありません。2^ 128未満の演算の計算努力で衝突を見つけます。

実装:

  • Python:2.3.5(wash hashlib)、2.5.1、2.6.1でテスト済み
  • Ruby:1.8.6でテスト済み
  • PHP5:5.2.6でテスト済み

不足している言語実装を作成したり、現在の実装を洗練したりすることは誰でも歓迎です。

基本操作(Pythonを使用)

>>> fsh = fshp.crypt('OrpheanBeholderScryDoubt')
>>> print fsh
{FSHP1|8|4096}GVSUFDAjdh0vBosn1GUhzGLHP7BmkbCZVH/3TQqGIjADXpc+6NCg3g==
>>> fshp.validate('OrpheanBeholderScryDoubt', fsh)
True

暗号のカスタマイズ:

パスワードハッシュスキームを弱めましょう。 -ソルトの長さをデフォルトの8から2に減らします。-反復ラウンドをデフォルトの4096から10に減らします。-基になるハッシュアルゴリズムとしてSHA-1を使用してFSHP0を選択します。

>>> fsh = fshp.crypt('ExecuteOrder66', saltlen=2, rounds=10, variant=0)
>>> print fsh
{FSHP0|2|10}Nge7yRT/vueEGVFPIxcDjiaHQGFQaQ==
3
Berk D. Demir

他の人が言及したように、あなたがそれを助けることができるなら、あなたは解読したくありません。標準的なベストプラクティスは、一方向ハッシュを使用して暗号化し、ユーザーがハッシュでログインしたときにパスワードを比較することです。

それ以外の場合は、強力な暗号化を使用して暗号化してから復号化する必要があります。政治的な理由が強い場合にのみこれをお勧めします(たとえば、ユーザーがパスワードを取得するためにヘルプデスクに電話することに慣れており、それを変更しないようにトップから強い圧力がかかっている場合)。その場合は、暗号化から始めてから、ハッシュに移行するビジネスケースの構築を開始します。

2
Cory Foy

認証のために、可逆暗号化を使用したパスワードの保存を避ける必要があります。つまり、パスワードハッシュのみを保存し、保存したハッシュに対してユーザーが指定したパスワードのハッシュを確認する必要があります。ただし、このアプローチには欠点があります。攻撃者がパスワードストアデータベースを取得した場合、 Rainbow table 攻撃に対して脆弱です。

あなたがすべきことは、事前に選択された(そして秘密の)ソルト値のハッシュとパスワードを保存することです。つまり、ソルトとパスワードを連結し、結果をハッシュし、このハッシュを保存します。認証するとき、同じことをします-あなたのソルト値とユーザーが提供したパスワードを連結し、ハッシュし、そして等しいことを確認します。これにより、レインボーテーブル攻撃が実行不可能になります。

もちろん、ユーザーがネットワーク経由でパスワードを送信する場合(たとえば、Webまたはクライアントサーバーアプリケーションで作業している場合)、パスワードをクリアテキストで送信しないでください。そのため、hash(salt +パスワード)を保存してハッシュ(salt + hash(password))と照合し、クライアントにユーザーが指定したパスワードを事前にハッシュ化してネットワーク上で送信する必要があります。ユーザーが(多くの人がそうするように)同じパスワードを複数の目的で再利用する場合、これによりユーザーのパスワードも保護されます。

2
  • MD5のようなものを使用して暗号化し、16進文字列としてエンコードします
  • 塩が必要です。あなたの場合、ユーザー名はソルトとして使用できます(一意である必要があり、ユーザー名は利用可能な最も一意の値である必要があります;-)
  • 古いパスワードフィールドを使用してMD5を保存しますが、古い(プレーンテキスト)パスワードと新しい(MD5)パスワードが共存できるように、MD5にタグを付けます(つまり、「MD5:687A878 ....」)
  • ログイン手順を変更して、MD5がある場合はMD5に対して検証し、そうでない場合はプレーンパスワードに対して検証する
  • 「パスワードの変更」および「新しいユーザー」機能を変更して、MD5化されたパスワードのみを作成する
  • 変換バッチジョブを実行できるようになりました。必要に応じて時間がかかる場合があります
  • 変換の実行後、legacy-supportを削除します
1
mfx

ステップ1:暗号化されたフィールドをデータベースに追加する

ステップ2:パスワードを変更すると、両方のフィールドが更新されるようにコードを変更しますが、ログインは引き続き古いフィールドを使用します。

ステップ3:スクリプトを実行して、すべての新しいフィールドに入力します。

手順4:ログインで新しいフィールドを使用し、パスワードを変更すると古いフィールドの更新が停止するようにコードを変更します。

ステップ5:暗号化されていないパスワードをデータベースから削除します。

これにより、エンドユーザーの作業を中断せずに切り替えを行うことができます。

また、私がすることは、「LastSessionID」などのパスワードとはまったく関係のない新しいデータベースフィールドに何か名前を付けることです。次に、パスワードフィールドを削除する代わりに、ランダムデータのハッシュを入力します。その後、データベースが危険にさらされた場合、「パスワード」フィールドを復号化するために必要なすべての時間を費やすことができます。

これは実際には何も達成しないかもしれませんが、価値のない情報を見つけようとしてそこに座っている誰かについて考えるのは楽しいです

1
Kevin

すべてのセキュリティの決定と同様に、トレードオフがあります。おそらく最も簡単な方法であるパスワードをハッシュすると、元のパスワードを返すパスワード取得機能を提供することも、スタッフがアカウントにアクセスするために人のパスワードを調べることもできません。

対称暗号化を使用できますが、これには独自のセキュリティ上の欠点があります。 (サーバーが危険にさらされると、対称暗号化キーも危険にさらされる可能性があります)。

公開鍵暗号化を使用し、Webアプリケーションから隔離された秘密鍵を保存する別のマシンでパスワード取得/顧客サービスを実行できます。これは最も安全ですが、2マシンアーキテクチャが必要であり、おそらくその間にファイアウォールが必要です。

0
davidcl

偉大なpythonの例 への1つの改善を提案したいと思います。random_bytes関数を次のように再定義します。

def random_bytes(num_bytes):
    return os.urandom(num_bytes)

もちろん、osモジュールをインポートする必要があります。 os.urandom関数は、暗号化アプリケーションで安全に使用できるバイトのランダムシーケンスを提供します。詳細については、 この関数のリファレンスヘルプ を参照してください。

0
Sergio

MD5とSHA1は少し弱点を示しているため(2つの単語が同じハッシュになる可能性があります)、パスワードをハッシュするにはSHA256-SHA512 /反復ハッシュを使用することをお勧めします。

アプリケーションが記述されている言語で小さなプログラムを作成し、各ユーザーに固有のランダムなソルトとパスワードのハッシュを生成します。私が検証と同じ言語を使用する傾向がある理由は、異なる暗号化ライブラリが少し異なる方法で実行できるため(つまり、パディング)、同じライブラリを使用してハッシュを生成し、それがそのリスクを排除することを検証するからです。このアプリケーションは、プレーンテキストのパスワードを知っているので、必要に応じてテーブルが更新された後にログインを確認することもできます。

  1. MD5/SHA1を使用しないでください
  2. 適切なランダムソルトを生成します(多くの暗号ライブラリにはソルトジェネレーターがあります)
  3. Orip推奨の反復ハッシュアルゴリズム
  4. パスワードが平文でネットワーク経由で送信されないようにします
0
Kudos2u2

私はセキュリティの専門家ではありませんが、現在の推奨事項は、MD5/SHA1ではなく、bcrypt/blowfishまたはSHA-2バリアントを使用することです。

おそらく、あなたも完全なセキュリティ監査の観点から考える必要があります

0
Gene T