【问题标题】:How to create and store password hashes with Blowfish in PHP如何在 PHP 中使用 Blowfish 创建和存储密码哈希
【发布时间】:2011-02-14 05:47:18
【问题描述】:

1) 如何使用 crypt() 创建安全的 Blowfish 密码哈希?

$hash = crypt('somePassword', '$2a$07$nGYCCmhrzjrgdcxjH$');

1a) “$2a”的意义是什么?它只是表明应该使用 Blowfish 算法吗?
1b) “$07”的意义是什么?较高的值是否意味着更安全的哈希?
1c) “$nGYCCmhrzjrgdcxjH$”的意义是什么?这是要使用的盐吗?这应该是随机生成的吗?硬编码?

2) 如何存储 Blowfish 哈希?

echo $hash;
//Output: $2a$07$nGYCCmhrzjrgdcxjH$$$$.xLJMTJxaRa12DnhpAJmKQw.NXXZHgyq

2a) 其中哪一部分应该存储在数据库中?
2b) 列(MySQL)应该使用什么数据类型?

3) 应该如何验证登录尝试?

【问题讨论】:

  • 1.) Read the manual 2. & 3. 是自己的问题。
  • 另见 Openwall 的 PHP password hashing framework (PHPass)。它的便携性和强化了针对用户密码的一些常见攻击。编写框架 (SolarDesigner) 的人与编写 John The Ripper 的人是同一个人。所以他对密码攻击略知一二。

标签: php security passwords blowfish crypt


【解决方案1】:

您应该存储 crypt 的整个输出,将其拆分并没有多大意义,因为无论如何您都需要为每个要散列的密码生成一个新的盐。使用 Matt 提到的固定隐藏盐是错误的 - 每个哈希的盐应该不同。

有关更多信息,请参阅http://www.openwall.com/articles/PHP-Users-Passwords - 我建议使用 phpass 库,因为它会为您生成随机盐,这与 crypt() 不同。

【讨论】:

  • chargen.matasano.com/chargen/2007/9/7/… 是另一个很好的资源。
  • 比起 PHPass,我更倾向于使用 PHP 提供的内置函数。我相信根据我的阅读,PHP 在使用 password_hash() 函数 If omitted, a random salt will be generated by password_hash() for each password hashed. This is the intended mode of operation. uk1.php.net/password_hash 时会自动生成盐
  • 是的,既然写了这篇文章,PHP 就添加了 password_hash - 现在这是比 phpass 更好的选择。
【解决方案2】:

1a) 加密强度 - 要求在 4..31 范围内。见http://php.net/manual/en/function.crypt.php

1b) 见 1a

1c) 见 1a。 'salt' 不应该是随机的,否则您将无法为给定的输入重新生成相同的哈希 - 请参阅 3。

2a) 严格来说,除了哈希之外的所有内容(以防数据库被破坏)。此外,将您的 salt 存储在 Web 服务器文档根目录下无法访问的文件中并将其包含在内。尽可能设置最严格的权限;理想情况下只对 Web 主机服务(例如 apache)只读,没有写入或执行权限。不太严格地说,取决于您希望对黑客的防御程度。不储存盐只会让生活更加困难;他们仍然必须正确地将数据输入到算法中——但为什么要让它更容易呢?

2b) VARCHAR(32) 应该适用于河豚,如果不存储散列

3) 假设您已经运行了正确的防注入代码等。所以请不要盲目复制以下内容(最好使用 PDO 而不是 mysql 扩展)。下面是特定于河豚、SHA-256 和 SHA-512 的,它们都返回散列中的盐。其他算法需要修改...

//store this in another file outside web directory and include it
$salt = '$2a$07$somevalidbutrandomchars$'

...

//combine username + password to give algorithm more chars to work with
$password_hash = crypt($valid_username . $valid_password, $salt)

//Anything less than 13 chars is a failure (see manual)
if (strlen($password_hash) < 13 || $password_hash == $salt)
then die('Invalid blowfish result');

//Drop the salt from beginning of the hash result. 
//Note, irrespective of number of chars provided, algorithm will always 
//use the number defined in constant CRYPT_SALT_LENGTH
$trimmed_password_hash = substring($password_hash, CRYPT_SALT_LENGTH);
mysql_query("INSERT INTO `users` (username,p assword_hash) VALUES '$valid_username', '$trimmed_password_hash'");

...

$dbRes = mysql_query("SELECT password_hash FROM `users` WHERE username = '$user_input_username' LIMIT 1");
//re-apply salt to output of database and re-run algorithm testing for match
if (substring($salt, CRYPT_SALT_LENGTH) . mysql_result($dbRes, 0, 'password_hash') ) ===
        crypt($user_input_username . $user_input_password, $salt) ) {
    //... do stuff for validated user
}

【讨论】:

  • 注意,以上内容不应用于登录以保护敏感数据。为此,至少为每个用户使用不同的 salt,并将 salt 值存储在单独的数据库中(只能由不同的 MySQL 用户访问),并使用一个将用户名映射到 salt 的表。
  • 我的 CRYPT_SALT_LENGTH 是 123,所以你的 substr() 行返回一个空字符串。那是怎么回事?
  • 上面的答案完全是错误的。在 crypt() 中,您存储 crypt() 的整个结果,并在稍后将其反馈给自身进行验证...
  • @TML,是对的 - 这个答案是个坏建议。每个散列的盐值应该是唯一的,并由crypt() 自动存储为散列的后缀。
  • “固定”盐是个坏主意。 salt 的全部目的是将“随机”位添加到其他可猜测的(例如 12345678)密码中。使用固定 salt 成功攻击存储密码的可能性更高。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-07
  • 1970-01-01
  • 2020-11-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多