Python加密模块--盐的正确使用方法是什么?

10
首先,上下文:我正在尝试创建一个基于命令行的工具(Linux),需要登录。此工具上的帐户与系统级帐户无关 - 没有使用 /etc/passwd。
我计划使用与 /etc/passwd 大致相同的格式将用户帐户存储在文本文件中。
尽管不使用系统级密码文件,但使用 crypt 似乎是一种好的做法,而不是以明文形式存储密码。(虽然 crypt 肯定比以明文形式存储密码更好,但我也可以接受其他方法。)
我的 crypt 知识基于这个: https://docs.python.org/2/library/crypt.html 文档似乎要求一些不可能的事情:“建议在检查密码时使用完整的加密密码作为盐。”
什么?如果我正在创建加密密码(例如,在创建用户记录时),如何使用加密密码作为盐?它还不存在。(我假设您必须在创建和检查密码时使用相同的盐。)

我尝试使用明文密码作为盐值。这个方法可行,但有两个问题:一个很容易解决,一个比较严重:

1) 密码的前两个字符会被包含在加密后的密码中。你可以通过不将前两个字符写入文件来解决这个问题:

user_record = '%s:%s:%s' % (user_name, crypted_pw[2:], user_type)

2) 通过使用明文密码作为盐值,似乎会降低系统中的熵量。也许我误解了盐的目的。

我能够得出的最佳做法是使用用户名的前两个字符作为盐值。这样做是否合适,还是我忽略了什么问题?

我理解盐的作用是防止从字典中预先计算密码哈希值。我可以为所有密码使用标准盐值(例如我的缩写“JS”),但这似乎对攻击者来说不如使用每个用户用户名的两个字符那样困难。

8个回答

7

Python的crypt()是系统crypt()函数的包装器。来自Linux crypt() man页面:

char *crypt(const char *key, const char *salt);
key是用户键入的密码。 盐是从集合[a-zA-Z0-9./]中选择的两个字符的字符串。 该字符串用于以4096种不同的方式扰动算法。

重点在于“ two-character string”。 现在,如果您查看Python中crypt()的行为:

>>> crypt.crypt("Hello", "World")
'Wo5pEi/H5/mxU'
>>> crypt.crypt("Hello", "ABCDE")
'AB/uOsC7P93EI'

你发现结果的前两个字符总是与原始盐值的前两个字符相同,这确实形成了真正的两个字符盐本身。 也就是说,crypt()的结果具有2个字符盐+加密密码的形式。 因此,如果传递两个字符盐或原始多字符盐的整个加密密码,结果没有区别。
注意:集合[a-zA-Z0-9./]包含64个字符,64*64=4096。以下是两个字符与"4096种不同方式"的关系。

5

对于crypt模块的使用:

在生成加密密码时,您需要提供盐。只要满足列出的条件,它可以是随机的,这样可以增加抗暴力破解的能力。在检查密码时,应该提供从getpwname获取的值,以防您所在的系统支持更大的盐大小并且您没有自己生成它。

一般评论:

如果这与实际系统登录无关,则没有什么阻止您使用比crypt更强的方法。您可以随机生成每个用户盐的N个字符,并将其与用户的密码组合在SHA-1哈希中。

string_to_hash = user.stored_salt + entered_password
successful_login = (sha1(string_to_hash) == user.stored_password_hash)

更新:虽然这种方法比彩虹表更安全,但上述方法仍存在密码学上的弱点。正确应用HMAC算法可以进一步增加您的安全性,但这超出了我的专业范围。


2
优先选择PBKDF2来保存密码:使用HMAC和迭代应用盐。 - orip

3
你误解了文档的意思;它说由于盐的长度可能取决于底层的crypt()实现,所以在检查密码时应提供整个加密后的密码作为盐值。也就是说,不要取前两个字符作为盐,而是将整个字符串都用作盐。
你的想法是根据用户名生成初始盐似乎可以。

3
以下是关于密码加盐的一些通用建议:
  1. 通常,盐用于使彩虹表计算成本过高。因此,您应该向所有密码哈希添加一些随机化的盐,并将其仅以明文形式存储在散列密码值旁边。
  2. 使用HMAC-它是一个很好的标准,比将密码和盐连接更安全。
  3. 使用SHA1:MD5已经被破解了。如果你知道这个,我不是有意冒犯,只是想说得全面一些。;)

不会让盐成为密码的函数。攻击者必须生成彩虹表才能拥有密码的即时查找数据库,但他们只需执行一次即可。如果您选择一个随机的32位整数,他们必须生成2 ^ 32张表,这(与确定性盐不同)需要太多的内存(和时间)。


3
为了增加强度,您可以通过使用格式中的盐来使crypt模块使用md5。
$1$ABCDEFGH$

其中ABCDEFGH是您的盐字符串。

>>> p = crypt.crypt('password', '$1$s8Ty3/f$')
>>> p
Out: '$1$s8Ty3/f$0H/M0JswK9pl3X/e.n55G1'
>>> p == crypt.crypt('password', p)
Out: True

请注意,这是GNU对crypt的扩展,可以在Linux系统上使用“man crypt”查看。MD5(现在甚至包括SHA1)可能已经被“破解”,但它们仍然相对较好地用于密码哈希,而md5仍然是Linux本地密码的标准。


1
请注意,如果您的系统允许,您甚至可以执行以下操作:crypt.crypt("some_password","$5$some_salt")crypt.crypt("some_password","$6$some_salt"),以使用 sha-256 或 sha-512(如果您不信任 MD5)。还要注意,crypt 使用多轮 DES/MD5/SHA* 使其比仅使用单轮更加安全。因此,MD5 被破解并不意味着带有 $1$ 的 crypt 也被破解。 - Maarten Derickx

1

密码或从密码派生的任何内容都不应该用作盐。特定密码的盐应该是不可预测的。

用户名或用户名的一部分是可以容忍的,但更好的选择是使用加密随机数生成器生成的随机字节。


1

使用PBKDF2,参见此评论在另一个线程中(包括Python实现)。


-2

请查看Björn Edström的文章TrueCrypt explained。它包含易于理解的TrueCrypt工作原理的解释,以及一些TrueCrypt功能的简单Python实现,包括密码管理

他谈论的是Python crypt()模块,而不是Python中的TrueCrypt

Python 2中的默认crypt.crypt()并不是非常安全,该文章解释了如何使用更安全的替代方案。


2
他在谈论Python的crypt()模块,而不是Python中的TrueCrypt。 - Kitchi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接