我想在我的登录系统中实现一个盐,但我对它应该如何工作有点困惑。我无法理解它背后的逻辑。我理解md5是一种单向算法,我遇到的所有函数似乎都将所有内容混合在一起。如果是这种情况,如何获取密码进行比较?我最大的问题是,如何使用户密码更加安全,而不仅仅是对密码进行哈希处理?如果数据库曾被泄露,则散列与salt一起位于数据库中。这不就是黑客所需要的吗?
我还在SO上发现了另一篇帖子,其他开发人员说:
“确保你的盐和算法 存储分开数据库“
我想将salt存储在数据库中。如果我这样做,这真的是个问题吗?
我正在寻求一些帮助,以了解它是如何工作的,以及最佳实践可能是什么。非常感谢任何帮助。
编辑: 我要感谢大家的回应和想法。尽管我现在可能更加困惑,但对我来说这肯定是一种学习经历。再次感谢你们。
答案 0 :(得分:31)
盐的目的是防止攻击者通过预先计算的rainbow tables分摊跨站点的暴力攻击的成本(或者更好的是,当为每个用户使用不同的盐:站点的所有用户时) 。
使用普通哈希,攻击者可以计算一次这样的表(一个非常长的,昂贵的操作),然后使用它来快速查找任何站点的密码。当站点使用一个固定盐时,攻击者必须专门为该站点计算一个新表。当一个站点为每个用户使用不同的盐时,攻击者可以停止使用彩虹表 - 他必须分别对每个密码进行暴力破解。
单独存储盐不是获得此优势所必需的。理论上它会更安全,因为它可以抵消字典或短密码的弱点。在实践中,它不值得打扰,因为在一天结束时,您需要访问salt 某处来检查密码。此外,尝试将它们分开会导致更复杂的系统 - 系统越复杂,安全漏洞就越多。
修改:我的具体建议:
答案 1 :(得分:18)
哈希函数始终为同一输入字符串返回相同的值。假设我的用户(Alice)有密码secret
。使用md5()
哈希secret
会导致以下哈希值
5ebe2294ecd0e0f08eab7690d2a6ee69
使用字典(常用字词和密码列表)或为您提供该服务的各种网站之一,攻击者(Mallory)在他的字典中{{1}时可以轻松找到密码是秘密的}}
在散列之前进行腌制的过程使得在不知道盐的情况下更难使用字典攻击。请考虑以下事项:
5ebe2294ecd0e0f08eab7690d2a6ee69 = secret
结果哈希现在是<?php
$salt = '@!#%$@#$@SADLkwod,sdaDwqksjaoidjwq@#@!';
$hash = md5($salt . 'secret');
,但Alice的密码仍为b58ad809eece17322de5024d79299f8a
。现在,如果马洛里手上拿着咸味哈希,她很可能在她的字典中找不到答案。如果她这样做,字典会给她错误的答案。
永远不要在数据库中存储静态salt。最好将其与您的应用程序配置一起存储(顺便提一下,不应该从网上获得)。
如果要使用动态盐,则需要使用数据库。使用现有有效数据的非空列来构建您的salt(基于秘密加密密钥的blowfish加密的用户名字符串通常是加密安全的)。盐不要使用单独的色谱柱。如果您不能使用现有列,请将您的salt合并到与散列相同的列中。例如,对于128位盐使用前32个字符,然后对160位哈希使用最后40个字符。以下函数将生成这样的哈希:
secret
如果攻击者使用SQL注入进入您的数据库,至少他/她检索的哈希将没有用,因为他/她将无法访问您的应用程序配置。如果你的服务器扎根,无论你做什么,它都会被游戏结束。
注意: md5()
可能存在其他类型的攻击,这就是您使用更安全的哈希算法sha1()
的原因。或者,更好的是,使用Portable PHP password hashing framework,它在设计时考虑了安全性,并且几乎可以与任何PHP版本兼容。
function seeded_sha1($string, $seed_bits) {
if(($seed_bits % 8) != 0) {
throw new Exception('bits must be divisible by 8');
}
$salt = '';
for($i = 0; $i < $seed_bits; $i+=8) {
$salt .= pack('c', mt_rand());
}
$hexsalt = unpack('h*hex', $salt);
return $hexsalt['hex'] . sha1($salt . $string);
}
function compare_seeded_sha1($plain, $hash) {
$sha1 = substr($hash, -40);
$salt = pack('h*', substr($hash, 0, -40));
$plain_hash = sha1($salt . $plain);
return ($plain_hash == $sha1);
}
答案 2 :(得分:14)
忘记使用盐(部分是因为你提到的原因),请改用bcrypt:
有关详细说明,请参阅:http://codahale.com/how-to-safely-store-a-password/
答案 3 :(得分:7)
其他答案都很好,所以我只是提出一个其他人没有提及的小问题。您不希望为每个密码使用相同的salt,因为如果两个人拥有相同的密码,它们将具有相同的哈希值。这暴露了攻击者可以利用的信息。
您可以为每个用户使用相同的盐,同时Juraj的好主意是将密码与其他不变的数据库字段(用户独有)结合起来。但要小心,因为这些信息与密码有关。如果您要将用户名+密码一起散列以保证唯一的哈希值,则无法在不创建新用户且要求他们设置新密码的情况下更改用户名。
作为每个用户拥有一个唯一salt并将其与密码哈希一起存储的示例,我将在典型的Linux系统上指出/etc/shadow。
root@linux:/root# cat /etc/shadow | grep root
root:$1$oL5TTZxL$RhfGUZSbFwQN6jnX5D.Ck/:12139:0:99999:7:::
这里, oL5TTZxL 是盐, RhfGUZSbFwQN6jnX5D.Ck / 是哈希值。在这种情况下,纯文本密码是 root ,我的系统使用的哈希算法是基于MD5的BSD密码算法。 (比我更新的系统有更好的哈希算法)
答案 4 :(得分:5)
您没有输入密码进行比较。您在尝试登录时对密码进行加密,并将存储的值与新加密的值进行比较。
答案 5 :(得分:2)
正如您所提到的,散列算法只能单向工作(或者只有在足够强大的情况下才能工作:-D)
对于有关盐析的问题,我建议使用静态盐字符串和一些来自数据库的动态数据散列密码,这应该不会更改创建后
这是一种非常安全的密码存储方式,因为即使数据库遭到破坏,黑客/破解者仍然需要获取静态字符串哈希,并且需要猜测你是如何应用所有密码的。
例如,假设您有一个包含以下列的users
表:
id
username
password
created_at
一旦填充,列 id 和 created_at 永远不应该被更改..
因此,当您使用用户密码进行哈希处理时,您可以这样做:
<?php
$staticSalt = '!241@kadl;ap][';
$userPass = 'my new pass';
// assuming $user variable is already populated with DB data
// we will generate new hash from columns and static salt:
$genPass = sha1($user['id'] . $userPass . $user['created_at'] . $staticSalt);
?>
我希望这个帮助:)欢呼
答案 6 :(得分:2)
哈希密码旨在使这些密码对您自己的管理员保密。
1)在您的数据库中保留纯文本密码是正常的,但管理员可以使用您的密码来访问其他系统。
2)您可以使用单个全局盐,它与密码组合(通过预先或异或),然后进行散列以存储在数据库中。但这很容易受到恶意管理员和为这一个盐设计的彩虹表的影响。
3)每个用户可以拥有一个单独的salt:数据库将用于存储salt,以及从密码/ salt组合派生的哈希。这样可以防止彩虹攻击,但仍然可以进行暴力攻击。
4)最后,通过使用速度受限的硬件散列解决方案,您可以保持散列函数的安全。
这就像你能做的一样好。由于人性,密码具有有限的域并且易受暴力攻击。我们试图阻止管理员获取用户密码,然后在他们无法访问的其他系统上使用它们。
其他一些说明:
a)您可以在密码/ salt组合上使用bcrypt来减缓攻击者的强力攻击。但由于我们正在接受管理员,他们可以耐心等待。
b)将盐与密码哈希分开不是一种有效的防御,我们毕竟是假设管理员。
c)使用现有数据作为盐稍微好一点,但我怀疑现有数据的随机盐含有多少熵。
答案 7 :(得分:1)
对用户密码进行salting可能比仅对密码进行哈希更安全,因为它可以防止预计算攻击。
例如,如果黑客可以访问您的数据库,并且密码没有被腌制,那么他可以在他的哈希数据库中查找哈希值(请参阅http://en.wikipedia.org/wiki/Rainbow_table)以获取原始密码。