首先,上下文:我正在尝试创建一个基于命令行的工具(Linux) 需要登录。此工具的帐户与此无关 系统级帐户 - 这看不到/ etc / passwd。
我打算使用与/ etc / passwd相同的格式(大致)将用户帐户存储在文本文件中。
尽管没有使用系统级密码文件,但似乎使用了crypt 使用是一种很好的做法,而不是存储密码 明文。 (虽然crypt肯定比存储密码更好 明确地说,我对其他方式持开放态度。)
我的隐藏知识基于以下内容: https://docs.python.org/2/library/crypt.html
文档似乎要求一些不可能的东西:“它 建议在检查时使用完整的加密密码作为盐 输入密码。“
咦?如果我正在创建加密密码(例如,在创建用户时) 记录)我如何使用加密密码作为盐?它 还不存在。 (我假设您必须使用相同的盐来创建和检查密码。)
我尝试使用明文密码作为盐。这样做 工作,但有两个问题;一个容易克服,一个严重:
1)明文密码的前两个字母包含在 加密密码。你可以通过不写前两个来解决这个问题 文件中的字符:
user_record = '%s:%s:%s' % (user_name, crypted_pw[2:], user_type)
2)通过使用明文密码作为盐,你似乎是 减少系统中的熵量。可能是我 误解了盐的目的。
我能够得出的最佳实践是使用前两个 用户名中的字符作为salt。这是否合适, 或者是否有一些我错过的东西让这个行动变得糟糕?
我对盐的理解是它可以防止预先计算密码 来自字典的哈希。我可以使用标准盐 密码(例如我的首字母缩写,“JS”),但这似乎不是一个 攻击者的负担,而不是使用每个用户的用户名中的两个字符。
答案 0 :(得分:7)
Python的crypt()是系统的crypt()函数的包装器。从Linux crypt()手册页:
char *crypt(const char *key, const char *salt); key is a user’s typed password. salt is a two-character string chosen from the set [a–zA–Z0–9./]. This string is used to perturb the algorithm in one of 4096 different ways.
重点是“双字符字符串”。现在,如果你看看crypt()在Python中的行为:
>>> crypt.crypt("Hello", "World")
'Wo5pEi/H5/mxU'
>>> crypt.crypt("Hello", "ABCDE")
'AB/uOsC7P93EI'
你发现结果的前两个字符总是与原盐的前两个字符重合,这确实形成了真正的双字符盐本身。 也就是说,crypt()的结果具有2char-salt + encrypted-pass形式。 因此,结果没有区别,如果不是传递双字符盐或原始的多字符盐,而是传递整个加密密码。
注意:集[a-zA-Z0-9./]包含64个字符,64 * 64 = 4096。以下是两个字符与“ 4096 不同方式”相关的方式。
答案 1 :(得分:4)
使用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 :(得分:3)
你误解了文档;它表示由于salt的长度可能因底层的crypt()实现而异,因此在检查密码时,应将整个加密密码作为salt值提供。也就是说,不要将前两个字符拉成盐,而只是扔掉整个东西。
你想让初始盐基于用户名的想法似乎没问题。
答案 3 :(得分:3)
以下是关于腌制密码的一般建议:
我不会让salt成为密码的函数。攻击者必须生成彩虹表才能拥有密码的即时查找数据库,但他们只需要这样做一次。如果你选择一个随机的32位整数,它们必须生成2 ^ 32个表,这些(与确定性盐不同)成本方式,太多的内存(和时间)。
答案 4 :(得分:2)
对于某些增加的强度,您可以通过使用格式的salt来使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
(请注意,这是crypt的gnu扩展,请参阅linux系统上的“man crypt”)。 MD5(现在甚至是SHA1)可能会“损坏”,但它们对于密码哈希仍然相对较好,而md5仍然是linux本地密码的标准。
答案 5 :(得分:1)
密码或从密码派生的任何内容都不应该用作盐。特定密码的盐应该是不可预测的。
用户名或用户名的一部分是可以容忍的,但更好的是来自加密RNG的随机字节。
答案 6 :(得分:1)
使用PBKDF2,在另一个线程上查看this comment(包括Python实现)。
答案 7 :(得分:-1)
看看BjörnEdström撰写的文章TrueCrypt explained。它包含有关truecrypt如何工作的易于理解的解释以及一些truecrypt功能的简单Python实现包括密码管理。
他在谈论Python crypt()模块,而不是Python中的TrueCrypt
Python 2中的默认crypt.crypt()
不是很安全,the article解释了更安全的替代方案可能如何运作。