如何在我的登录密码中实施salt?

Tim*_*may 55 security hash salt password-protection

我想在我的登录系统中实现一个盐,但我对它应该如何工作有点困惑.我无法理解它背后的逻辑.我理解md5是一种单向算法,我遇到的所有函数似乎都将所有内容混合在一起.如果是这种情况,如何获取密码进行比较?我最大的问题是,如何使用户密码更加安全,而不仅仅是对密码进行哈希处理?如果数据库曾被泄露,则散列与salt一起位于数据库中.这不是黑客所需要的吗?

我还在SO上发现了另一篇文章,其他开发人员说:

"确保您的salt和算法与数据库分开存储"

我想将salt存储在数据库中.如果我这样做,这真的是一个问题吗?

我正在寻求一些帮助,以了解它是如何工作的,以及最佳实践可能是什么.任何帮助是极大的赞赏.


编辑:我要感谢大家的回应和想法.尽管我现在可能更加困惑,但对我来说这肯定是一种学习经历.再次感谢你们.

Mic*_*rdt 31

盐的目的是防止攻击者通过预先计算的彩虹表来分摊跨站点的暴力攻击的成本(或者更好的是,当为每个用户使用不同的盐时:站点的所有用户).

使用普通哈希,攻击者可以计算一次这样的表(一个非常长的,昂贵的操作),然后使用它来快速查找任何站点的密码.当站点使用一个固定盐时,攻击者必须专门为该站点计算一个新表.当一个站点为每个用户使用不同的盐时,攻击者可以停止使用彩虹表 - 他必须分别对每个单独的密码进行暴力破解.

单独存储盐不是获得这种优势所必需的.理论上它会更安全,因为它可以抵消字典或短密码的弱点.在实践中,它不值得打扰,因为在一天结束时,您需要访问某处的盐来检查密码.此外,尝试将它们分开会导致更复杂的系统 - 系统越复杂,安全漏洞的机会就越多.

编辑:我的具体建议:

  • 为每个用户生成长伪随机盐并存储在DB中
  • 使用基于bcrypt的哈希
  • 理想情况下,不要自己实现它,而是使用现有的库

  • +1最后的建议.@Timmay这值得大绿色复选标记:) (2认同)

And*_*ore 19

散列函数始终为同一输入字符串返回相同的值.假设我的用户(Alice)有密码secret.哈希secret使用md5()导致以下哈希

5ebe2294ecd0e0f08eab7690d2a6ee69
Run Code Online (Sandbox Code Playgroud)

使用字典(常用字词和密码列表)或为您提供该服务的各个站点之一,攻击者(Mallory)可以在他的字典中看到密码时很容易发现密码是秘密的5ebe2294ecd0e0f08eab7690d2a6ee69 = secret.

在散列之前进行腌制的过程使得在不知道盐的情况下更难使用字典攻击.考虑以下:

<?php
$salt = '@!#%$@#$@SADLkwod,sdaDwqksjaoidjwq@#@!';
$hash = md5($salt . 'secret');
Run Code Online (Sandbox Code Playgroud)

结果哈希现在是,b58ad809eece17322de5024d79299f8a但Alice的密码仍然是secret.现在,如果马洛里手上拿着咸味哈希,她很可能在她的字典中找不到答案.如果她这样做,字典会给她错误的答案.

切勿在数据库中存储静态salt.最好将其与您的应用程序配置一起存储(顺便提一下,不应该从Web上获得).

如果要使用动态salt,则需要使用该数据库.使用现有有效数据的非空列来构建您的salt(基于秘密加密密钥的blowfish加密的用户名字符串通常是加密安全的).盐不要使用单独的色谱柱.如果您不能使用现有列,请将您的salt合并到与散列相同的列中.例如,对于128位盐使用前32个字符,然后对160位哈希使用最后40个字符.以下函数将生成这样的哈希:

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);
}
Run Code Online (Sandbox Code Playgroud)

如果攻击者使用SQL注入进入您的数据库,至少他/她检索的哈希将没有用,因为他/她将无法访问您的应用程序配置.如果您的服务器扎根,那么无论您做什么,它都会被游戏结束.

注意:可能存在其他类型的攻击md5(),sha1()例如,使用更安全的哈希算法的原因.或者,更好的是,使用Portable PHP密码散列框架,该框架在设计时考虑了安全性,并且向后兼容几乎任何PHP版本.

require('PasswordHash.php');

$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hashed stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}
Run Code Online (Sandbox Code Playgroud)

  • -1:答案不好.使用salt的正确方法是为每个用户生成随机盐并将其存储在数据库中.试图将它分开或模糊字段名称是业余的废话.使用人工慢哈希函数是一种公认​​的良好实践. (30认同)
  • -1表示*"切勿将盐存储在数据库中."*没有理由不将盐存储在数据库中; 但是,对于每个用户来说,它们应该是唯一的**,否则就没用了.当然,使用独特的数据库字段也没有问题,因为**盐不被视为秘密**. (11认同)
  • @juraj:随机盐存储(以明文形式)_with_哈希密码.如果每个密码都是唯一的,那么盐并不需要保密. (4认同)
  • @Timmay:看看我的答案,解释一下"静态密钥".@juraj:生成的盐的优势在于您可以更轻松地确保其长度和质量 - 并且它可以防止您向客户解释无法更改某些字段,因为您的*安全概念*取决于它们是否已修复. (4认同)
  • @juraj:错了.安全软件的安全性不依赖于其源代码的保密性.它的安全性来自于首先编码良好.您知道,人们可以对闭源产品的各个方面进行逆向工程; 您认为所有Microsoft安全建议来自哪里?(提示:IDA Pro.) (4认同)
  • 好的,看,这就是搞砸了我...每个人都在说生成**随机盐**并将其存储在数据库中.对我来说似乎很公平.但是,我从你的帖子中理解的是,我应该**不要将哈希值存储在数据库中,而是存储在应用程序中.如果我将salt存储在应用程序中,那么它就不会是动态的.你能看出我的困惑吗? (2认同)
  • -1表示"使用现有有效数据的非空列来构建您的盐".这会将加密的密码绑定到其他一些数据上,这很糟糕.使用安全随机数生成器为每个密码生成salt.或者使用为您执行salting和hashing的库,并将所有数据(包括salt)存储为哈希密码. (2认同)

Ric*_*ich 15

忘记使用盐(部分原因你提到),请改用bcrypt:

有关详细说明,请参阅:http://codahale.com/how-to-safely-store-a-password/

  • -1 bcrypt对于这项工作来说是一个很好的工具,但你仍然应该使用salt(出于Andrew提到的原因) - salt解决了与hash不同的问题.该文章的作者是错误的. (3认同)
  • @BlueRaja,你完全错了!`bcrypt`在最终哈希中包含唯一的每个密码盐.它会处理所有事情,所以一切都是正确的. (3认同)
  • 啊,很酷.我已经在5.3了,所以我猜它已经存在了.:)至少**某事**今天对我有利. (2认同)

ind*_*div 7

其他答案都很好,所以我只是提出一个其他人没有提到过的小问题.您不希望为每个密码使用相同的salt,因为如果两个人拥有相同的密码,它们将具有相同的哈希值.这暴露了攻击者可以利用的信息.

您可以为每个用户使用相同的盐,同时Juraj的好主意是将密码与其他不变的数据库字段(用户独有)结合起来.但要小心,因为这些信息与密码有关.如果您要将用户名+密码一起散列以保证唯一哈希,则无法在不创建新用户且要求他们设置新密码的情况下更改用户名.

作为每个用户拥有一个唯一salt并将其与密码哈希一起存储的示例,我将在典型的Linux系统上指出/ etc/shadow.

root@linux:/root# cat /etc/shadow | grep root
root:$1$oL5TTZxL$RhfGUZSbFwQN6jnX5D.Ck/:12139:0:99999:7:::
Run Code Online (Sandbox Code Playgroud)

这里,oL5TTZxL是盐,RhfGUZSbFwQN6jnX5D.Ck/是哈希.在这种情况下,纯文本密码是root,我的系统使用的哈希算法是基于MD5的BSD密码算法.(比我更新的系统有更好的哈希算法)


bri*_*ian 5

您没有输入密码进行比较.您在尝试登录时加密密码,并将存储的值与新加密的值进行比较.

  • @unknown:如果您正在谈论最大登录试验,那么您正在查看错误的威胁模型.正确的威胁模型是一个破解者刚刚下载了你的整个用户/密码表,并希望破解尽可能多的密码. (4认同)
  • @unknown:也许吧.但所有这些关于最大化试验的讨论与使用盐渍密码散列的目的完全正交(这是OP的问题); 前者针对的是在线密码猜测攻击,而后者针对的是离线破解攻击. (2认同)