我有同样的问题,过去几天一直在研究这个问题。 @Rostislav 提出的解决方案相当不错,但它不完整且有点过时。
关于算法层
首先,有一个新的密码学库,恰如其分地称为Cryptography。使用这个库而不是 PyCrypto 的原因有很多,但吸引我的主要原因是:
您可以阅读有关the reasons for creating the new library on LWN 的更多信息。
其次,另一个答案建议使用 SHA1 作为加密密钥。 SHA1 is dangerously weak and getting weaker。 SHA1 的替代品是 SHA2,最重要的是,您应该使用bcrypt 或PBKDF2 对哈希进行加盐并拉伸它。加盐作为防止rainbow tables 的保护很重要,而拉伸是防止暴力破解的重要保护。
(Bcrypt 测试较少,但旨在使用大量内存,而 PBKDF2 设计速度较慢,NIST 推荐。在我的实现中,我使用 PBKDF2。如果您想了解更多差异,read this。 )
对于 CBC 模式下的 AES 加密,应该使用 128 位密钥,如上所述 - 这没有改变,尽管它现在已汇总到规范 called Fernet。初始化向量将在此库中自动为您生成,因此您可以放心地忘记它。
关于密钥生成和存储层
其他答案非常正确,建议您需要仔细考虑密钥处理并尽可能选择 OAuth 之类的东西。但是假设这是不可能的(它不在我的实现中),您有两个用例:Cron 作业和交互式。
cron 作业用例归结为您需要将密钥保存在安全的地方并使用它来运行 cron 作业。我没有研究过这个,所以我不会在这里发表意见。我认为有很多好方法可以做到这一点,但我不知道最简单的方法。
对于交互式用例,您需要做的是收集用户密码,使用该密码生成密钥,然后使用该密钥解密存储的凭据。
带回家
我将使用 Cryptography 库执行上述所有操作:
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)
攻击?
假设您的数据库已泄露到 Internet。攻击者能做什么?好吧,我们用于加密的密钥采用了用户加盐密码的第 100,000 个 SHA256 哈希值。我们将盐和我们的加密算法存储在您的数据库中。因此,攻击者必须:
- 尝试暴力破解哈希:将盐与所有可能的密码结合起来,并对其进行 100,000 次哈希。取该哈希并尝试将其作为解密密钥。攻击者必须进行 100,000 次哈希才能尝试一个密码。这基本上是不可能的。
- 直接尝试所有可能的哈希作为解密密钥。这基本上是不可能的。
- 尝试使用预计算哈希的彩虹表?不,在涉及随机盐时不会。
我认为这非常可靠。
不过,还有一件事需要考虑。 PBKDF2 的设计速度很慢。它需要大量的 CPU 时间。这意味着如果用户有办法生成 PBKDF2 哈希值,那么您将面临 DDOS 攻击。做好准备。
后记
所有这一切,我认为有些图书馆会为你做一些事情。谷歌搜索django encrypted field之类的东西。我不能对这些实现做出任何承诺,但也许你会了解其他人是如何做到这一点的。