私は現在、saltを使用してSHA256
ハッシュにキーを格納する独自の小さなパスワードマネージャーを作成しています。次のようにしてハッシュを作成します。
def sha256_rounds(raw, rounds=100001):
obj = hashlib.sha256()
for _ in xrange(rounds):
obj.update(raw)
raw = obj.digest()
return obj.digest()
作成された後、次のように保存されます。
key = base64.urlsafe_b64encode(provided_key)
length = len(key)
with open(key_file, "a+") as key_:
front_salt, back_salt = os.urandom(16), os.urandom(16)
key_.write("{}{}{}:{}".format(front_salt, key, back_salt, length))
私が持っている質問/懸念は:
これがハッシュ化されたパスワード/キーを保存するのに受け入れられない方法である場合、これをより安全にするために他にどのような手順を実行できますか?
更新:
私はあなたの男の助言の多くを取りました、そしてあなたがあなたがそれを見つけることができる私の小さなパスワードマネージャーの結果を見たいのであれば、これまで、- ここ 。皆様からのアドバイスをお待ちしておりますので、これからもよろしくお願いいたします。 (そのリンクが機能しない場合は、これを使用してください https://github.com/Ekultek/letmein )
私は現在自分の小さなパスワードマネージャーを書いています
それがあなたの最初の間違いです。この複雑なものには、専門家でさえも陥る多くの微妙な落とし穴があります。この分野での豊富な経験がなければ、何かを安全に近づけることはできません。
キーをSHA256ハッシュに格納します
ええとああ...
これは必ずしもとは限りませんは、何か間違っていることを示していますが、正しく実行することを強く疑っています。ここでハッシュされているマスターパスワードについて話していると思いますか?マスターパスワードは、PBKDF2、bcrypt、またはArgon2のように [〜#〜] kdf [〜#〜] を使用してキーに変換する必要があります。その後、このキーを使用して、保存されているパスワードを暗号化します。
パスワードが正しいことを確認する方法が必要な場合は、キーのハッシュを保存しても問題ありませんが、キー自体は保存しないでください...ストレージにアクセスするすべての人がすべてのパスワードを復号化するために必要なすべてを持っているキーを保存してください!
マスターパスワードのハッシュについて話しておらず、実際にランダムに生成されたキーを意味している場合、ここで何を達成しようとしているのかわかりませんが、多数の反復で低速のKDFを使用するべきではありません。 。
または、マスターパスワードを2回ハッシュすることもできます。1回目はハッシュとして保存して、後でユーザーが入力したパスワードが正しいことを確認し、もう一度暗号化のキーとして使用します。これがどのように行われるかに応じて、それは設計上の欠陥から完全にキーを渡すことまで及ぶ可能性があります。
編集:コード全体を確認すると、4番目のオプションのようです。入力したパスワードが正しいかどうかを後で確認するためにパスワードのハッシュを保存し、ハッシュthisにハッシュしますキーとして使用します。これは、キー自体を格納するだけの場合とほぼ同じです。
次のようにしてハッシュを作成します。
def sha256_rounds(raw, rounds=100001): obj = hashlib.sha256() for _ in xrange(rounds): obj.update(raw) raw = obj.digest() return obj.digest()
ここでraw
が何であるかは明確ではありませんが、それがパスワードであると想定しています。あなたがしているのは、SHA256を使用した無塩ハッシュです。独自のKDFを作成しようとしないでください。
作成された後、次のように保存されます。
key = base64.urlsafe_b64encode(provided_key) length = len(key) with open(key_file, "a+") as key_: front_salt, back_salt = os.urandom(16), os.urandom(16) key_.write("{}{}{}:{}".format(front_salt, key, back_salt, length))
それで、あなたはパスワードをハッシュしてキーを作成し、そしてランダムなソルトをフロントとバックに追加しますか? 2つの異なるソルトを前後の非標準に連結するだけでなく、KDFが既に終了した後で行われるため、ここでは何も達成されません!それらをそこに置くために、いくつかのランダムな値を追加しているだけです。
これがどれほど悪いかを示すために(コミット609fdb5ce976c7e5aa1832670505da60012b73bcの時点で)、マスターパスワードを必要とせずに、保存されているすべてのパスワードをダンプする必要はありません。
from encryption.aes_encryption import AESCipher
from lib.settings import store_key, MAIN_DIR, DATABASE_FILE, display_formatted_list_output
from sql.sql import create_connection, select_all_data
conn, cursor = create_connection(DATABASE_FILE)
display_formatted_list_output(select_all_data(cursor, "encrypted_data"), store_key(MAIN_DIR))
パスワードマネージャーを作成してみるのは良い学習経験かもしれませんが、くださいくださいリモートで重要なものに使用しないでください。 @Xenosが示唆しているように、自分のパスワードマネージャーを作成することが本当に有益であるという十分な経験がないように思われます。既存のオープンソースパスワードマネージャーを調べることは、より良い学習機会になるでしょう。
最初に、私がパスワードマネージャーである1Passwordで働いていることを開示します。それは利己的であるように見えるかもしれませんが、安全なパスワードマネージャーを書くことは最初に現れるより難しいことを伝える人に私の声を追加する必要があります。一方、あなたがそれを公の場で試み、尋ねることは良いことです。これは、最初に表示されるよりも難しいことを知るのに良い方法です。
アップデートでリンクしているソースを一目見たところ、これまでに提供されてきたsomeのアドバイスを受けられたことをうれしく思います。しかし、コードを誤解していない限り、コードを根本的に設計エラーにして、安全性が大幅に低下します。
マスターパスワードエントリを暗号化キーの派生ではなくauthenticationの質問として扱っているようです。つまり、入力したパスワードを(ハッシュ後に)保存されているものでチェックし、そのチェックに合格した場合は、その保存されているキーを使用してデータを復号化します。
すべきことは、暗号化された暗号化キーを格納することです。それをマスターキーと呼びましょう。これは、最初に新しいインスタンスを設定するときにランダムに生成されます。このマスターキーは、実際のデータを暗号化および復号化するために使用するものです。
マスターキーは暗号化されずに保存されることはありません。これは、キー暗号化キー(KEK)と呼ばれることもあります。このKEKは、ユーザーのマスターパスワードとソルトからキー導出関数(KDF)を介して導出されます。
したがって、PBKDF2の使用の出力(ハッシュを繰り返すだけでなく、それを使用していただきありがとうございます)はKEKとなり、KEKを使用してマスターキーを復号化し、次に復号化されたマスターキーを使用してデータを復号化します。
たぶんそれがあなたのやっていることであり、私はあなたのコードを誤解しました。しかし、KDFを介して導出したものと保存されているものを比較し、復号化するかどうかを決定するだけの場合、システムは非常に安全ではありません。
プロジェクトの最も太い最大の最初の行を作成してくださいREADMEそれは非常に安全でないという事実を叫びます。そしてそれをすべてのソースファイルにも追加してください。
ここで、ソースをちらっと見たときに気付いた他のいくつかのことを示します
CBCの代わりにGCMなどの認証された暗号化モードを使用します。
全体を暗号化するというアプローチは、少量のデータに対しては機能しますが、より大きなデータセットがあると、拡張できません。
レコード(の一部)を個別に暗号化するときは、レコードごとに一意のナンス(GCMの場合)または一意の初期化ベクトル(CBCを無理に使用している場合)が必要になることに注意してください。
3回失敗した後のデータ破壊は危険であり、セキュリティは追加されません。
高度な攻撃者は、データファイルをコピーし、パスワードを解読するための独自のスクリプトを作成するだけです。また、キャプチャしたデータの独自のコピーを作成します。あなたのデータ破壊は誰かがあなたのデータを偶然または悪意を持って破壊することを簡単にするだけです。
だからあなたの元の質問に。答えは、場合によります。一方では、マスターパスワードの強度と、攻撃者が問題に投げかけるリソースの種類によって異なります。また、防御側がそれに投入できるリソースにも依存します。たとえば、バッテリー容量が限られているモバイルデバイスでKDFを実行しますか?
しかし、たまたま1Passwordは、100,000ラウンドのPBKDF2-HMAC-SHA256でハッシュされた42.5ビットのパスワードをクラックする人やグループに対して、 特典を提供する でクラックのコストを測定しようとしています。
しかし、これらすべてを比較検討する方法を理解することよりも重要なことは、低速ハッシュはパスワードマネージャーKDFにとって絶対に不可欠であるが、十分に高く調整されると限界利益の減少が得られることを理解することです。
100Kラウンドを使用していて、マスターパスワード[〜#〜] p [〜#〜]があるとします。ランダムな数字(0〜9)を選択し、それを[~~~~ p [〜#〜]に追加すると、クラック耐性が10倍になります。 PBKDF2のラウンドを増やすことによって同じ増加を得るには、100万ラウンドまで行く必要があります。ポイントは、適切な量の遅いハッシュを取得すると、パスワードの強度を上げることで、ラウンド数を増やすよりもはるかに強力な防御者の努力を得ることができるということです。
私はこれについて数年前に書きました Bcryptは素晴らしいですが、パスワードの解読は不可能ですか?
Raw SHA256は使用しないでください。GPUを使用して高速化できるため、ブルートフォースが発生しやすくなります。壊れることはありません。ストレッチしても、もはやベストプラクティスではありません。 PBKDF2はある程度これを打ち消すように機能しますが、bcryptまたはscryptを優先的に使用する必要があります。
ホイールを再発明すること(PBKDF2-SHA256、bcryptまたはscryptを使用していない場合は、ほとんど何をしているのか)は、cryptoでは決して良い考えではありません。
あなたが投稿したリンクのコードを見て(以下に一部を再現)、私は少し混乱しています。
正しく読み取ると、main()
はstore_key()
を呼び出してディスク上のファイルからキーをロードし、次にcompare()
を使用してユーザーから与えられたキーとそれを比較します。 1。どちらもsha256_rounds()
を実行し、PBKDF2を実行します。
次に、同じ_stored_key
_、つまりファイルからロードされたものを使用して暗号化を行いますか?
それは完全かつ完全に逆です。キーファイルにアクセスできる人は誰でも、格納されたキーをロードして、それを使用して格納されたデータを復号化したり、同じキーで暗号化された新しいデータを格納したりできますスクリプトを実行する必要なし to "パスワードを確認してください。
せいぜい、ユーザーが指定したパスワード/パスフレーズを使用して暗号化用のキーを生成し、保存されたパスワードハッシュに対してユーザーを認証するのは混乱していると思います。同じハッシュで両方を実行することはできません。暗号化データと一緒に暗号化キーを保存した場合、暗号化は完全に無関係です。
確かに、パスワードは、認証に使用される場合と暗号化キーを生成するために使用される場合の両方で、何らかのハッシュ/ KDFを使用して処理されます。しかし、それは人間が256ビットのキーを記憶できないために必要な前処理のステップのようなものであり、代わりに低エントロピーパスワードに依存する必要があります。取得したハッシュ/キーを使って行うことは、2つのユースケースで異なります。
次に、ハードコードされたソルトがsha256_rounds()
にあり、これは一般的にソルトの目的をかなり損なうものです。私はより一般的にコードにコメントすることもできますが、これはcodereview.SEではありません。
コードを間違って読んだと教えてください。
私が調べたコードの部分:
_def main():
stored_key = store_key(MAIN_DIR)
if not compare(stored_key):
...
else:
... # later
password = Prompt("enter the password to store: ", hide=True)
encrypted_password = AESCipher(stored_key).encrypt(password)
def store_key(path):
key_file = "{}/.key".format(path)
if not os.path.exists(key_file):
...
else:
retval = open(key_file).read()
amount = retval.split(":")[-1]
edited = retval[16:]
edited = edited[:int(amount)]
return edited
_
あなたのために決定し、時間の経過とともにデフォルトの数またはアルゴリズムさえも増加させる保守されたツールを使用します。 libsodiumをお勧めしますが、他にもあります。 Libsodiumには、 "インタラクティブ" パスワードハッシュ と対称暗号化のデフォルトがあります。必要に応じて、ソフトウェアがいつか設定を「アップグレード」できることを確認してください。 libsodiumのようなツールは、デフォルトを時々変更するため、ユーザーはそれらを追跡できます。
私はオープンソースの開発を行っています password manager とlibsodiumを使用しています。また、Mitroの セキュリティドキュメント も読む価値があります。