在django中安全地存储加密的凭证

时间:2012-10-15 21:49:09

标签: python django security encryption credentials

我正在开发一个python / django应用程序,除其他外,它还可以将数据同步到各种其他服务,包括samba共享,ssh(scp)服务器,Google应用程序等。因此,它需要存储凭据才能访问这些服务。我认为将它们存储为未加密的字段将是一个坏主意,因为SQL注入攻击可以检索凭据。所以我需要在存储之前加密信用卡 - 是否有可靠的库来实现这一点?

一旦信用卡被加密,它们就需要在可用之前被解密。我的应用有两种用例:

  • 一个是交互式的 - 在这种情况下,用户将提供解锁凭据的密码。
  • 另一个是自动同步 - 这是由cron作业或类似作业启动的。我会在哪里保留密码以最大限度地降低漏洞风险?

或者我应该采取不同的方法处理这个问题?

3 个答案:

答案 0 :(得分:5)

我有同样的问题,过去几天一直在研究这个问题。 @Rostislav提出的解决方案非常好,但它不完整,有点过时了。

在算法层

首先,有一个新的加密库,恰当地称为Cryptography。使用这个库而不是PyCrypto有很多理由,但吸引我的主要原因是:

  • 一个核心目标是让你无法用脚射击自己。例如,它不会拥有严重过时的哈希算法,如MD2。
  • 它有很强的机构支持
  • 在各种平台上持续集成500,000次测试!
  • 他们的文档网站有更好的SSL配置(near-perfect A+ score而不是a mediocre B rating
  • 他们有针对漏洞的披露政策。

您可以阅读有关the reasons for creating the new library on LWN的更多信息。

其次,另一个答案建议使用SHA1作为加密密钥。 SHA1 is dangerously weak and getting weaker。 SHA1的替代品是SHA2,除此之外,您应该使用bcryptPBKDF2对您的哈希进行盐化并对其进行拉伸。腌制对于rainbow tables的保护很重要,而拉伸是防止蛮力强迫的重要保护。

(Bcrypt测试较少,但设计使用大量内存而且PBKDF2设计得很慢并且是NIST推荐的。在我的实现中,我使用PBKDF2。如果你想要更多的差异,{{3} }。)

对于加密,在CBC模式下使用128位密钥的AES应该使用,如上所述 - 虽然现在已经汇总到规范read this,但没有改变。初始化向量将在此库中自动生成,因此您可以安全地忘记它。

在密钥生成和存储层

其他答案非常正确,建议您需要仔细考虑密钥处理并选择OAuth之类的内容,如果可以的话。但假设不可能(它不在我的实现中),您有两个用例:Cron作业和Interactive。

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哈希值。我们将salt和我们的加密算法存储在您的数据库中。因此,攻击者必须:

  • 尝试哈希的强力:将盐与每个可能的密码组合并将其哈希100,000次。取该哈希并将其作为解密密钥。攻击者只需要尝试一个密码即可进行100,000次哈希。这基本上是不可能的。
  • 直接尝试每个可能的哈希作为解密密钥。这基本上是不可能的。
  • 尝试使用预先计算的哈希值的彩虹表?不,当涉及随机盐时,不是。

我认为这非常可靠。

然而,还有另外一件事要考虑。 PBKDF2设计得很慢。它需要大量的CPU时间。这意味着如果有一种方法可以让用户生成PBKDF2哈希值,那么您就是在开放DDOS攻击。为此做好准备。

后记

所有这些都说,我认为有些图书馆会为你做一些。谷歌周围有called Fernet之类的东西。我不能对这些实现做出任何承诺,但也许你会了解其他人如何做到这一点。

答案 1 :(得分:1)

首先在服务器上存储足以登录多个系统的凭据看起来像是一场噩梦。无论加密如何,服务器上的代码都会泄露它们。

您应该只存储执行任务所需的凭据(即文件同步)。对于服务器,您应该考虑使用RSync之类的同步服务器,对于OAuth等协议,这样如果您的服务器遭到入侵,这只会泄漏数据,而不会泄漏系统。

接下来就是加密这些凭据。对于加密,我建议您使用PYCrypto

对于您在密码学中使用的所有随机数,通过Crypto.Random(或其他一些强有力的方法)生成它们,以确保它们足够强大。

您不应使用相同的密钥加密不同的凭据。我建议的方法是:

  1. 您的服务器应该拥有主密码 M (从/ dev / random派生)。将其存储在root所拥有的文件中,并且只能由root读取。
  2. 当您的服务器以root权限启动时,它会将文件读入内存,然后在服务客户端之前删除它的权限。对于Web服务器和其他恶魔来说,这是正常的做法。
  3. 当您要编写新凭证(或更新现有凭证)时,生成随机数据块 S 。取上半部分并计算哈希 K = H(S 1 ,M)。那将是你的加密密钥。
  4. 使用CBC模式加密您的数据。从 S 2 中取初始化向量(IV)
  5. S 与加密数据一起存储。
  6. 当您需要解密时,只需取出 S 创建 K 并使用相同的IV进行解密。

    对于哈希,我会建议SHA1加密 - AES。哈希和对称密码足够快,因此更大的密钥大小不会受到影响。

    这个方案在某些地方有点过分,但这也不会受到影响。

    但请记住,存储凭据的最佳方法不是存储凭据,必要时,使用允许您完成任务的权限最少的凭据。

答案 2 :(得分:-1)

也许你可以通过创建:

来依赖多用户方案
  • 运行Django的用户(例如django拥有访问凭据的权限
  • 拥有这些权限的用户(例如sync)。

它们都可以在django组中,以允许他们访问该应用。之后,创建一个脚本(例如一个Django命令,例如manage.py sync-external)来同步你想要的东西。

这样,django用户可以访问应用和同步脚本,但凭据,因为只有sync用户可以访问。如果有人试图在没有凭据的情况下运行该脚本,那么它当然会导致错误。

依赖于Linux权限模型在我看来是一个“好主意”,但我不是安全专家,所以记住这一点。如果有人对上述内容有什么要说的话,请不要犹豫!