我是否正确使用了PHP的crypt()函数?

17

我一直在使用PHP的crypt()函数将密码存储和验证到我的数据库。我对其他事情使用哈希算法,但对于密码使用crypt()。文档并不是很好,而且似乎存在很多争论。我正在使用Blowfish和两个盐来加密密码并将其存储在数据库中。以前我会存储盐和加密后的密码(就像盐值哈希一样),但意识到这是多余的,因为盐已经成为加密密码字符串的一部分。

我有点困惑关于如何在crypt()上应用彩虹表攻击,总之从安全角度来看是否正确。我使用第二个盐将其附加到密码上,以增加短密码的熵,可能有点过度设计,但为什么不这样呢?

function crypt_password($password) {
if ($password) {
    //find the longest valid salt allowed by server
    $max_salt = CRYPT_SALT_LENGTH;

    //blowfish hashing with a salt as follows: "$2a$", a two digit cost parameter, "$", and 22 base 64
    $blowfish = '$2a$10$';

    //get the longest salt, could set to 22 crypt ignores extra data
    $salt = get_salt ( $max_salt );

    //get a second salt to strengthen password
    $salt2 = get_salt ( 30 ); //set to whatever


    //append salt2 data to the password, and crypt using salt, results in a 60 char output
    $crypt_pass = crypt ( $password . $salt2, $blowfish . $salt );

    //insert crypt pass along with salt2 into database.
    $sql = "insert into database....";

    return true;
    }
}  


function get_salt($length) {
$options = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./';

$salt = '';

for($i = 0; $i <= $length; $i ++) {
    $options = str_shuffle ( $options );
    $salt .= $options [rand ( 0, 63 )];
}
return $salt;
}

function verify_password($input_password)
{
if($input_password)
{
    //get stored crypt pass,and salt2 from the database
    $stored_password = 'somethingfromdatabase';
    $stored_salt2 = 'somethingelsefromdatabase';

    //compare the crypt of input+stored_salt2 to the stored crypt password
    if (crypt($input_password . $stored_salt2, $stored_password) == $stored_password) {
        //authenticated
        return true;
    }
    else return false;
}
else return false;
}

使用 mt_rand 而不是 rand 将会稍微提高你的脚本性能。 - Sliq
5个回答

15

你真的应该看看PHPASS:http://www.openwall.com/phpass/它是一个使用crypt()的密码哈希框架,被用于Wordpress和phpBB等项目。

这个网站上也有一篇关于使用crypt()进行密码哈希、盐值和拉伸的优秀文章:http://www.openwall.com/articles/PHP-Users-Passwords

更新: 目前有一种替代PHPASS库的选择。在PHP的下一个版本中,有专门用于哈希和验证密码的特殊函数(使用bcrypt):http://www.php.net/manual/en/ref.password.php。有一个兼容性库可以为PHP 5.3.7+实现这些功能:https://github.com/ircmaxell/password_compat


1
我能看出那个框架对于开箱即用的方法会有所帮助,但是我并没有看到我的代码和phpass代码之间有太大区别。 - Brian
1
而且:PHPass是一团糟。非常混乱。它如此混乱,我不会依赖它。 - Sliq
@Panique,你能解释一下吗? - Robert Ros

11

你使用crypt()是正确的。 crypt($input, $stored) == $stored 是它设计时的使用方式。

你的get_salt()函数不够好,因为它使用了通常较差的rand()函数。你应该考虑使用更强的随机函数,比如openssl_random_pseudo_bytes()


3
彩虹表的理念是攻击者可以在家中创建一个包含所有可能密码及其哈希值的表格。
例如:
PASSWORD HASH
iloveSO  gjroewjgo
password knbnogjwm
secret   gjroehghe
jbieber  rewgroewj

有了这张表,攻击者可以快速将任何哈希值转换为密码。彩虹表使用一些技巧,使得不必存储所有哈希值,但它仍然会预先计算出所有哈希值。

通过使用盐,即使将其与密码一起存储,也会使这个过程变得更加困难。攻击者现在不再需要对字典中的每个单词进行哈希,而是需要对每个盐和每个单词进行哈希。如果盐足够长,这将给出足够多的组合,使得计算所有这些哈希值变得不可行。

因此,盐并不意味着成为应用程序独有的额外密码,而是意味着改变哈希函数,使其变得非标准化。


他正在使用一种盐,但其长度是未知的。如果盐值非常大,那么彩虹表就会变得不可行。例如256^256,或者一个256字节的基础盐已经如此之大,我们甚至没有一个词来描述破解仅有一个字符的密码所需的字节数。 - rook

2
这是对crypt()函数的误用,因为您正在使用已弃用的原语。 Blowfish非常古老,Twofish是替代品,即使如此,它也很老,因为Threefish几乎定稿了。您应该使用sha2系列中的一个,sha256或sha512都是不错的选择。可以使用crypt()与sha256或sha512一起使用,分别应使用CRYPT_SHA256 CRYPT_SHA512参数。
此外,您的盐具有非常小的熵/大小比率,您仅使用字母数字集合,这是一个笑话,因为字母数字彩虹表是最常见的。您应该使用完整的基于256个字节的base256字节,并且我建议使用长达256个字节的盐。请记住,所有哈希函数在定义上都是二进制安全的,因此您不必担心空字节等问题。

Blowfish 只能处理字母数字字符,所以也许我应该使用 CRYPT_SHA512。 - Brian
@Brian Perin,那么这就是crypt()函数的一些奇怪之处,因为块密码经常用于加密文件和流量。 - rook
2
crypt() 使用的 Blowfish 算法并不完全与旧版 Blowfish 块密码相同 - 它是一种专门用于密码哈希/密钥派生的变体算法。 - caf
@caf 我知道任何块密码都可以通过反馈的方式用作消息摘要。但我仍然认为它可以接受非字母数字字符,如果不能接受这些字符,它就不是很有用了。也许@Brian Perin应该检查一下。 - rook
@Rook:可以,但是crypt()接口使用不同的符号集来产生可以包含在UNIX风格的passwd格式文件中的输出。 - caf
@caf 哇,这个函数太糟糕了。我不会使用那种垃圾。 - rook

0

使用 SHA-512(如果可用)和包含 time() 和 openssl_random_pseudo_bytes() 的盐。Crypt 是一种合并/高效的方法,因为它将插入到散列字符串中的盐一起返回。


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