【问题标题】:Comparing passwords with crypt() in PHP在 PHP 中将密码与 crypt() 进行比较
【发布时间】:2011-03-09 07:26:20
【问题描述】:

我需要了解此功能的基础知识。对于河豚算法,php.net 文档指出:

使用盐的 Blowfish 散列如下:“$2a$”,两位数的成本参数,“$”,以及来自字母表“./0-9A-Za-z”的 22 个基数为 64 位的数字。在盐中使用超出此范围的字符将导致 crypt() 返回零长度字符串

因此,根据定义,这应该不起作用:

echo crypt('rasmuslerdorf', '$2a$07$usesomadasdsadsadsadasdasdasdsadesillystringforsalt$');

然而,它吐了出来:

$2a$07$usesomadasdsadsadsadaeMTUHlZEItvtV00u0.kb7qhDlC0Kou9e

似乎 crypt() 已将盐本身的长度缩短为 22。有人可以解释一下吗?

这个函数的另一个我无法理解的方面是当他们使用 crypt() 来比较密码时。 http://php.net/manual/en/function.crypt.php(参见前文#1)。这是否意味着如果我使用相同的盐来加密所有密码,我必须先加密它?即:

$salt = "usesomadasdsadsadsadae";
$salt_crypt = crypt($salt);

if (crypt($user_input, $salt) == $password) {
   // FAIL WONT WORK
}

if (crypt($user_input, $salt_crypt) == $password) {
   // I HAVE TO DO THIS?
}    

感谢您的宝贵时间

【问题讨论】:

  • 我无耻地撞了这个。我剩下的问题是我对 ZZ Coder 的回复,看这篇文章的底部。

标签: php encryption bcrypt


【解决方案1】:

参考手册

CRYPT_BLOWFISH - 河豚散列 盐如下:“$2a$”,两位数 成本参数、“$”和 22 base 64 字母表中的数字

注意:22 以 64 位为基数

【讨论】:

  • 英语不是我的第一语言,所以也许我完全误解了这一点 :) 在我的示例中我没有使用 22 个字符,但无论如何 PHP 都可以吗?
  • PHP 不介意盐中 >22 个字符;它反对
【解决方案2】:

第一个问题:

因此,根据定义,这应该不起作用:

echo crypt('rasmuslerdorf', '$2a$07$usesomadasdsadsadsadasdasdasdsadesillystringforsalt$');

crypt() 似乎在哪里被删掉了 盐本身的长度为 22。 有人可以解释一下吗?

字符过多没有问题...短语在盐中使用超出此范围的字符将导致crypt()返回零长度字符串引用外部base 64 的范围不是 22 个字符的范围。尝试在 salt 字符串中放入非法字符,您应该会发现输出为空(或者如果放入

第二个问题:

您将加密的存储密码作为 salt 传递,因为 salt 字符串总是(按设计)出现在加密字符串中,这样您就可以确保对存储的密码和用户输入的密码进行加密时使用相同的 salt。

【讨论】:

  • 所以我将加密的密码存储在数据库中,例如“MTUHlZEItvtV00u0.kb7qhDlC0Kou9e”,然后将其用作盐?但是我使用了另一种盐来创建加密,不是吗?
  • 对,您使用了另一种盐(或默认生成的盐)来创建第一个加密。事实证明,由于加密算法的某些(我想是精心设计的)特性,您可以使用该加密字符串作为另一种加密的盐,并获得使用相同盐的效果。
【解决方案3】:

以下代码示例可能会回答您的问题。

要使用 Blowfish 生成散列密码,首先需要生成一个 salt,它以 $2a$ 开头,后跟迭代计数和 22 个字符的 Base64 字符串。

$salt = '$2a$07$usesomadasdsadsadsadasdasdasdsadesillystringfors';
$digest = crypt('rasmuslerdorf', $salt);

将整个 $digest 存储在数据库中,它同时具有盐和摘要。

比较密码时,就这样做,

  if (crypt($user_input, $digest) == $digest)

您正在重用摘要作为盐。 crypt 知道算法标识符中的盐有多长。

【讨论】:

  • 是的,应该这样做。而那个$salt应该是每次设置密码时随机生成的。
  • 啊,我明白了,我以为我应该只将哈希存储在数据库中。但是将盐与哈希一起存储在 db 中会消除盐的用途,不是吗?
  • @soren.qvist:不完全,不。即使攻击者知道盐,他仍然必须重新计算每个可能附加该盐的密码的哈希值;只计算一次然后在结果中查找哈希是不够的。如果盐是未知的,那就更好了,因为他必须为每个可能的盐尝试所有密码,但是即使每个用户的盐与每个密码一起存储,它仍然需要重新计算该用户的盐。另请参阅en.wikipedia.org/wiki/Salt_%28cryptography%29
  • @soren.qvist:您绝对应该为每条记录使用不同的盐。不管它是随机的还是不随机的——我不确定有多大区别。即使盐是可预测的,您仍然需要为每个盐运行哈希。更改密码时创建新盐也是如此;您仍然需要计算该盐的所有内容。它通过预先计算哈希然后重新使用它来防止某人针对该特定用户,但仅此而已。然而,话虽如此,在每次更改时随机加盐肯定不会伤害 - 所以你不妨这样做。
  • 从 Wordpress 中查看 gensalt_blowfish():cvsweb.openwall.com/cgi/cvsweb.cgi/projects/phpass/…
【解决方案4】:

这个问题与我对 ZZ Coder 的回答有关。基本上我的问题是关于将 crypt() 结果存储在数据库中。我是否应该将整个输出存储在数据库中,以便我的数据库如下所示:

--------------------------------------------------------------------------------
| ID | Username |                          Password                            |
--------------------------------------------------------------------------------
| 32 | testuser | $2a$07$usesomadasdsadsadsadaeMTUHlZEItvtV00u0.kb7qhDlC0Kou9e |
--------------------------------------------------------------------------------

如果是,那么这不是违背了使用盐的初衷吗?如果有人获得了数据库的访问权限,他们可以清楚地看到用于加密的盐吗?

额外问题:为每个密码使用相同的盐是否安全?

【讨论】:

  • 首先 - 您正在散列,而不是加密。第二 - 通过为每个用户使用不同的盐,您可以保护自己免受彩虹表的伤害。盐存储在数据库中这一事实对攻击者没有显着帮助 - 您无法从盐中猜出密码(对于良好的散列算法)。
  • 所以,是的,我应该以这种方式存储密码吗?我认为哈希是加密的结果,但也许我的术语都错了
【解决方案5】:

BCrypt 使用 128 位作为盐,因此 22 字节 Base64,仅使用最后一个字节的两位。

哈希是使用盐和密码计算的。当您传递加密密码时,算法会读取强度、盐(忽略它之外的所有内容)和您提供的密码,并计算散列并附加它。如果你手边有 PostgreSQL 和 pg_crypto, SELECT gen_salt('bf');将显示正在读取的 $salt 的内容。

这里是盐生成的代码示例,来自我的.NET implementation's test-vector-gen.php,或者:

$salt = sprintf('$2a$%02d$%s', [strength goes here],
    strtr(str_replace(
    '=', '', base64_encode(openssl_random_pseudo_bytes(16))
    ),
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
    './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'));

没有理由对所有密码使用相同的盐。无论如何,盐是输出的一部分,所以你没有任何便利......虽然我承认 PHP 应该有一个内置的 gen_salt 函数。

【讨论】:

    【解决方案6】:

    每个密码的新盐

    $password = 'p@ssw0rd';
    
    $salt = uniqid('', true);
    $algo = '6'; // CRYPT_SHA512
    $rounds = '5042';
    $cryptSalt = '$'.$algo.'$rounds='.$rounds.'$'.$salt;
    
    $hashedPassword = crypt($password, $cryptSalt);
    // Store complete $hashedPassword in DB
    
    echo "<hr>$password<hr>$algo<hr>$rounds<hr>$cryptSalt<hr>$hashedPassword";
    

    身份验证

    if (crypt($passwordFromPost, $hashedPasswordInDb) == $hashedPasswordInDb) {
        // Authenticated
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-17
      • 1970-01-01
      • 2015-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-27
      • 1970-01-01
      相关资源
      最近更新 更多