首页 文章

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

提问于
浏览
54

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

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

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

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

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


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

8 回答

  • 18

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

    5ebe2294ecd0e0f08eab7690d2a6ee69
    

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

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

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

    结果哈希现在是 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);
    }
    

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

    Note: md5()上还有其他类型的攻击,这就是为什么你使用更安全的哈希算法,例如sha1() . 或者,更好的是,使用Portable PHP password hashing framework,它在设计时考虑了安全性,并且向后兼容几乎任何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';
    }
    
  • 14

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

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

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

    Edit: 我的具体建议:

    • 生成很长时间每个用户的伪随机盐,并存储在DB中

    • 使用基于bcrypt的哈希

    • 理想情况下,不要自己实现它,而是使用existing library

  • 31

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

    有关解释的详细信息,请参阅:http://codahale.com/how-to-safely-store-a-password/

  • 2

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

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

    作为每个用户拥有一个独特的盐并将其与密码哈希一起存储的示例,我将在典型的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密码算法 . (比我更新的系统有更好的哈希算法)

  • 1

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

  • 7

    正如您所提到的,散列算法只能单向工作(或者只有在足够强大时才能工作:-D)

    对于关于盐析的问题,我建议使用 static salt string 和一些 dynamic data from database 哈希密码,一旦创建后应该 not change

    这是一种非常安全的密码存储方式,因为即使数据库遭到破坏,黑客/破解者仍然需要获取静态字符串哈希,并且需要猜测你是如何应用所有salting的 .

    例如,假设您有一个包含以下列的 users 表:

    id
    username
    password
    created_at
    

    idcreated_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);
    ?>
    

    我希望这个帮助:)欢呼

  • 5

    散列密码旨在使这些密码对您自己的管理员保密 .

    1)在数据库中保存纯文本密码是正常的,除非管理员可以使用您的密码来访问其他系统 .

    2)您可以使用单个全局盐,它与密码组合(通过预先或异或),然后散列存储在数据库中 . 但这很容易受到恶意管理员和为这一个盐设计的彩虹表的影响 .

    3)您可以为每个用户分别使用salt:数据库将用于存储salt,以及从密码/ salt组合派生的哈希 . 这可以防止彩虹攻击,但仍然可以进行暴力攻击 .

    4)最后,通过使用速度受限的硬件散列解决方案,您可以保持散列函数的秘密 .

    那就是你能做的一样好 . 由于人性,密码具有有限的域并且易受暴力攻击 . 我们试图阻止管理员获取用户密码,然后在他们无法访问的其他系统上使用它们 .

    其他一些说明:

    a)您可以在密码/ salt组合上使用bcrypt来减缓攻击者的暴力攻击 . 但既然我们正在接受管理员,他们就会耐心等待 .

    b)保持盐与密码哈希分开不是一种有效的防御,我们毕竟是假设管理员 .

    c)使用现有数据作为盐更好一点,但我怀疑现有数据具有随机盐的熵 .

  • 2

    对用户密码进行salting可能比仅对密码进行哈希更安全,因为它可以防止预计算攻击 .

    例如,如果黑客可以访问您的数据库,并且密码没有被腌制,那么他可以在他的哈希数据库中查找哈希值(请参阅http://en.wikipedia.org/wiki/Rainbow_table)以获取原始密码 .

相关问题