如何在PHP中使用Blowfish创建并存储密码哈希

27

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) 如何验证登录尝试?


8
  1. 阅读手册。2和3是独立的问题。
- Linus Kleen
另外,还可以查看Openwall的PHP密码哈希框架(PHPass)。它是可移植的,并且针对用户密码的许多常见攻击进行了加固。编写该框架的人(SolarDesigner)也是编写John The Ripper的人。因此,他对密码攻击有一定的了解。 - jww
2个回答

13

应该存储crypt的整个输出,因为没有分割它的太多意义,因为无论如何,你需要为要哈希的每个密码生成一个新的salt。像Matt提到的固定隐藏盐是错误的——盐应该对于每个哈希都不同。

更多信息请参见http://www.openwall.com/articles/PHP-Users-Passwords - 我建议使用phpass库,因为它可以为您处理生成随机盐的过程,而不像crypt()。


2
http://chargen.matasano.com/chargen/2007/9/7/enough-with-the-rainbow-tables-what-you-need-to-know-about-s.html 是另一个极好的资源。 - TML
与其使用PHPass,我更倾向于使用PHP提供的内置函数。根据我的阅读理解,当使用password_hash()函数时,PHP会自动生成一个salt。如果省略,则每个加密的密码都会由password_hash()生成一个随机salt。这是期望的操作模式。http://uk1.php.net/password_hash - Luke
是的,自从这篇文章写出来以后,PHP已经添加了password_hash - 现在它比phpass更好。 - xyphoid

0

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

1b) 参见1a

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

2a) 严格来说,除了哈希(在数据库被攻击的情况下)。还要将盐存储在文件中,该文件不可在Web服务器文档根目录下访问,并包含它。使用最严格的权限设置它;理想情况下,只读取Web主机服务(例如Apache),没有写或执行权限。如果您希望对抗黑客,则可以放松一些限制。不存储盐只会使生活更加困难;他们仍然必须正确获取输入到算法的数据 - 但为什么要让它变得更容易呢?

2b) 如果不存储哈希,则VARCHAR(32)应该对blowfish没问题。

3) 假设您已经运行了适当的注入预防代码等等... 所以请不要盲目复制以下内容(最好使用PDO而不是mysql扩展)。以下内容特定于blowfish、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
}

3
注意,上述内容不应用于保护敏感数据的登录。至少,为每个用户使用不同的盐,并将盐值存储在一个单独的数据库中(仅可由不同的MySQL用户访问),并在一个映射用户名和盐的表中进行存储。 - Matt
1
我的 CRYPT_SALT_LENGTH 是 123,所以你的 substr() 行返回了一个空字符串。这是怎么回事? - user479911
10
上面的回答完全错误。在 crypt() 函数中,你需要存储整个函数返回的结果,并在后面将其输入到函数中进行验证... - TML
3
@TML说得对-这个答案给出了错误的建议。盐应该为每个哈希值唯一,并由crypt()自动地作为哈希值的后缀储存。 - Xeoncross
2
拥有一个“固定”的盐是个坏主意。盐的整个目的就是为了在一个可猜测(例如12345678)的密码中添加“随机”位。使用固定的盐存储密码,攻击成功的几率更高。 - rjha94
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接