12

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

一旦凭据被加密,它们需要在可用之前被解密。我的应用程序有两个用例:

  • 一种是交互式的——在这种情况下,用户将提供密码来解锁凭据。
  • 另一种是自动同步 - 这是由 cron 作业或类似作业启动的。为了最大程度地降低此处的漏洞利用风险,我应该将密码保存在哪里?

或者我应该采取不同的方法来解决这个问题?

4

3 回答 3

12

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

关于算法层

首先,有一个新的密码学库,恰如其分地称为Cryptography。使用这个库而不是 PyCrypto 有很多理由,但吸引我的主要理由是:

  • 一个核心目标是让你无法自爆。例如,它没有像 MD2 这样严重过时的哈希算法
  • 有强大的机构支持
  • 在各种平台上持续集成的 500,000 次测试!
  • 他们的文档网站有更好的 SSL 配置(接近完美的 A+ 分数而不是平庸的 B 等级
  • 他们有漏洞披露政策。

您可以阅读更多关于在 LWN 上创建新库的原因

其次,另一个答案建议使用 SHA1 作为加密密钥。SHA1 非常脆弱并且越来越弱。SHA1 的替代品是 SHA2,最重要的是,您应该真正使用bcryptPBKDF2对哈希进行加盐并拉伸它。盐渍对于防止彩虹表很重要,而拉伸是防止暴力破解的重要保护。

(Bcrypt 测试较少,但设计用于使用大量内存,而 PBKDF2 设计速度较慢,NIST 推荐使用。在我的实现中,我使用 PBKDF2。如果您想了解更多差异,请阅读此内容。)

对于 CBC 模式下的加密,应该使用 128 位密钥的 AES,如上所述——这并没有改变,尽管它现在被汇总到一个名为 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 之类的东西。我不能对这些实现做出任何承诺,但也许你会了解其他人是如何做到这一点的。

于 2015-05-31T19:01:16.783 回答
1

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

您应该只存储执行任务所需的凭据(即文件同步)。对于服务器,您应该考虑使用像RSync这样的同步服务器,对于 Google,使用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与加密数据一起存储。

当您需要解密时,只需取出S创建K并使用相同的 IV 解密。

对于哈希,我建议使用 SHA1,对于加密——AES。散列和对称密码足够快,因此使用更大的密钥大小不会受到伤害。

这个方案在某些地方有点过头了,但这不会有什么坏处。

但请再次记住,存储凭据的最佳方式不是存储凭据,当您必须时,请使用允许您完成任务的最低权限的凭据。

于 2012-10-16T07:12:10.663 回答
-2

也许您可以通过创建以下内容来依赖多用户方案:

  • 运行 Django 的用户(例如django没有访问凭证的权限
  • 具有这些权限的用户(例如sync)。

他们都可以在django组中,以允许他们访问应用程序。之后,制作一个manage.py sync-external同步您想要的内容的脚本(例如 Django 命令,例如 )。

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

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

于 2012-10-15T22:10:51.243 回答