私は同じ問題を抱えており、過去数日間これを調査しています。@Rostislav によって提示されたソリューションはかなり優れていますが、不完全で少し古くなっています。
アルゴリズム層について
まず、適切にCryptographyと呼ばれる暗号化用の新しいライブラリがあります。PyCrypto の代わりにこのライブラリを使用する理由はたくさんありますが、私が惹かれた主な理由は次のとおりです。
- 中心的な目標は、自分の足を撃たれないようにすることです。たとえば、MD2 のような非常に古いハッシュ アルゴリズムはありません。
- それは強力な制度的サポートを持っています
- さまざまなプラットフォームでの継続的な統合による 500,000 のテスト!
- 彼らのドキュメンテーション Web サイトには、より優れた SSL 構成があります (平凡な B 評価ではなく、ほぼ完璧な A+ スコア) 。
- 脆弱性の開示ポリシーがあります。
LWN で新しいライブラリを作成する理由について詳しく読むことができます。
次に、もう 1 つの回答では、SHA1 を暗号化キーとして使用することを推奨しています。SHA1 は危険なほど弱く、弱くなっています。SHA1 の代替は SHA2 であり、その上で、実際にハッシュをソルト化し、 bcryptまたはPBKDF2を使用してストレッチする必要があります。ソルティングはレインボー テーブルに対する保護として重要であり、ストレッチはブルート フォーシングに対する重要な保護です。
(Bcrypt はあまりテストされていませんが、大量のメモリを使用するように設計されており、PBKDF2 は低速になるように設計されており、NIST によって推奨されています。私の実装では、PBKDF2 を使用しています。違いについて詳しく知りたい場合は、こちらをお読みください。)
暗号化には、前述のように、128 ビット キーを使用した CBC モードの AES を使用する必要があります。現在はFernet と呼ばれる仕様にまとめられていますが、これは変更されていません。初期化ベクトルはこのライブラリで自動的に生成されるため、安全に忘れることができます。
鍵の生成および保管レイヤーについて
他の回答は、キーの処理を慎重に検討し、可能であれば OAuth などを選択する必要があることを示唆するのに非常に適切です。しかし、それが不可能であると仮定すると (私の実装ではそうではありません)、Cron ジョブと対話型の 2 つのユース ケースがあります。
cron ジョブの使用例は、キーを安全な場所に保管し、それを使用して cron ジョブを実行する必要があるという事実に要約されます。私はこれを研究していないので、ここでは意見を述べません。これを行うには多くの良い方法があると思いますが、最も簡単な方法はわかりません。
対話型のユース ケースでは、ユーザーのパスワードを収集し、それを使用してキーを生成し、そのキーを使用して保存されている資格情報を復号化する必要があります。
持ち帰り
暗号化ライブラリを使用して、上記のすべてを行う方法は次のとおりです。
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.backends import default_backend
secret = "Some secret"
# Generate a salt for use in the PBKDF2 hash
salt = base64.b64encode(os.urandom(12)) # Recommended method from cryptography.io
# Set up the hashing algo
kdf = PBKDF2HMAC(
algorithm=SHA256(),
length=32,
salt=str(salt),
iterations=100000, # This stretches the hash against brute forcing
backend=default_backend(), # Typically this is OpenSSL
)
# Derive a binary hash and encode it with base 64 encoding
hashed_pwd = base64.b64encode(kdf.derive(user_pwd))
# Set up AES in CBC mode using the hash as the key
f = Fernet(hashed_pwd)
encrypted_secret = f.encrypt(secret)
# Store the safe inputs in the DB, but do NOT include a hash of the
# user's password, as that is the key to the encryption! Only store
# the salt, the algo and the number of iterations.
db.store(
user='some-user',
secret=encrypted_secret,
algo='pbkdf2_sha256',
iterations='100000',
salt=salt
)
復号化は次のようになります。
# Get the data back from your database
encrypted_secret, algo, iterations, salt = db.get('some-user')
# Set up the Key Derivation Formula (PBKDF2)
kdf = PBKDF2HMAC(
algorithm=SHA256(),
length=32,
salt=str(salt),
iterations=int(iterations),
backend=default_backend(),
)
# Generate the key from the user's password
key = base64.b64encode(kdf.derive(user_pwd))
# Set up the AES encryption again, using the key
f = Fernet(key)
# Decrypt the secret!
secret = f.decrypt(encrypted_secret)
print(" Your secret is: %s" % secret)
攻撃?
DB がインターネットに流出したとします。攻撃者は何ができますか? 暗号化に使用したキーは、ユーザーのソルト化されたパスワードの 100,000 番目の SHA256 ハッシュを取得しました。ソルトと暗号化アルゴリズムをデータベースに保存しました。したがって、攻撃者は次のいずれかを行う必要があります。
- ハッシュのブルート フォースを試す: ソルトと考えられるすべてのパスワードを組み合わせて、100,000 回ハッシュします。そのハッシュを取得して、復号化キーとして試してください。攻撃者は、1 つのパスワードを試すために 100,000 回のハッシュを実行する必要があります。これは基本的に不可能です。
- 可能なすべてのハッシュを復号化キーとして直接試してください。これは基本的に不可能です。
- 事前に計算されたハッシュでレインボー テーブルを試してみませんか? いいえ、ランダムな塩が関係している場合ではありません。
これはかなり固いと思います。
ただし、もう1つ考えるべきことがあります。PBKDF2 は遅くなるように設計されています。多くの CPU 時間を必要とします。これは、ユーザーが PBKDF2 ハッシュを生成する方法がある場合、DDOS 攻撃にさらされていることを意味します。これに備えてください。
あとがき
これはすべて言った、私はあなたのためにこれのいくつかを行うライブラリがあると思います. django encrypted fieldなどについては、Google で検索してください。それらの実装について約束することはできませんが、おそらく他の人がこれをどのように行ったかについて何かを学ぶでしょう.