4

我正在使用一种非常标准的 cookie 登录方式 - 我给用户两个 cookie,一个带有他的用户名,另一个带有随机生成的字符串和用户特定的盐。

这是登录时发生的情况:

$_SESSION['username']=$row[username];
$_SESSION['user_id']=$row['id'];
$loginhash=generateRandomBase64String()."_".$row['salt'];
$number_of_days = 14;
$date_of_expiry = time() + 60 * 60 * 24 * $number_of_days ;
setcookie( "userlogin", $row['username'], $date_of_expiry, "/" ) ;
setcookie( "loginhash", $loginhash, $date_of_expiry, "/" ) ;
$cryptedhash=crypt($loginhash);

$today=date("Y-m-d");

mysql_query("update members set last_login='$today',loginhash='$cryptedhash' where id='$row[id]' ") or die(mysql_error());

所以这个$loginhash值类似于Pe0vFou8qe++CqhcJgFtRmoAldpuIs+d_g5oijF76它的加密版本存储在数据库中。salt 已经在数据库中,因为它是在每个用户注册时为他们生成的。

$_SESSION[username]使用会话变量(问题是,哈希永远不正确。$_SESSION[username]$_COOKIE[userlogin]

if($_COOKIE['userlogin'] && !isset($_SESSION[user_id])){
    $username=mysql_real_escape_string($_COOKIE['userlogin']);
    $loginhash=mysql_real_escape_string($_COOKIE['loginhash']);
    $salt=substr($loginhash,-8);
    $result=mysql_query("select * from members where (username='$username' || email='$username') && salt='$salt' limit 1 ") or die (mysql_error());
    $row=mysql_fetch_assoc($result);
    $cryptedhash=$row['loginhash'];
    if (crypt($loginhash, $cryptedhash) == $cryptedhash){
        $_SESSION['username']=$row[username];
        $_SESSION['user_id']=$row['id'];
    }
}

$_COOKIE[userlogin]是正确的值。当我检查数据库中的用户名/盐组合时,找到了正确的结果(echo $row[username]给出正确的值)。但是,if从未满足以下条件。我认为我的 PHP 配置有些奇怪,但我使用相同的加密机制来存储密码并且它可以正常工作。

那么任何人都可以看到这里出了什么问题吗?

PS 我不想在这里开始讨论 cookie 安全或各种可用的哈希函数。

4

3 回答 3

3

这是问题所在:

在您第一次调用 crypt() 时,您没有指定盐。在您对 crypt() 的第二次调用中,您将 $cryptedhash 作为盐传递。

crypt()如果您不提供随机盐,则记录为生成随机盐,然后将该盐添加到返回的哈希中。这样做的副作用是,如果您将返回的 salt+hash 作为后续调用的 hash 传递,crypt() 仍会从中提取正确的 salt。

不幸的是,所使用的算法和 salt+hash 的长度/格式是基于您的操作系统、PHP 版本以及您是否指定 salt 参数的组合。当您之前使用您的代码时,您有幸在两次调用 crypt() 时都选择了 DES。现在,您的环境对 crypt() 的 2 次调用使用了不同的算法,因为您只在其中一个中提供了哈希。

解决方案是将一致的盐传递给对 crypt() 的两个调用。您可以停止将盐附加到要散列的字符串,并实际将您的用户盐作为盐参数传递,一切都会好起来的。

于 2013-08-27T15:20:47.367 回答
0

这一行是错误的$loginhash=mysql_real_escape_string($_COOKIE['loginhash']);你不需要转义它(你没有将它与数据库一起使用),你必须在将它传递给 crypt() 时原封不动地使用它,所以该行可以写成$loginhash=$_COOKIE['loginhash'];(至少对于这部分代码)

于 2013-08-27T13:37:23.203 回答
0

如果您使用的是 PHP 5.5.0 或更高版本,您应该查看PHPDoc - Password Hashing
我知道你说过你不想讨论不同的散列函数,但我认为这个对你来说更容易,因为它应该可以解决你的问题并且(在我看来)更容易使用!

这是您的新功能代码:(未经测试,如果损坏请评论)

$_SESSION['username']=$row[username];
$_SESSION['user_id']=$row['id'];
$loginhash=generateRandomBase64String()."_".$row['salt'];
$number_of_days = 14;
$date_of_expiry = time() + 60 * 60 * 24 * $number_of_days ;
setcookie( "userlogin", $row['username'], $date_of_expiry, "/" ) ;
setcookie( "loginhash", $loginhash, $date_of_expiry, "/" ) ;
$cryptedhash=password_hash($loginhash, PASSWORD_DEFAULT, array("cost" => 10));
// a cost of 10 is standard, you may want to adjust it according to your hardware (lower/higher cost means faster/slower)

$today=date("Y-m-d");

mysql_query("update members set last_login='$today',loginhash='$cryptedhash' where id='$row[id]' ") or die(mysql_error());

使用PASSWORD_DEFAULT那里将使舒尔即使在未来的版本中也将始终使用最强的算法。

if($_COOKIE['userlogin'] && !isset($_SESSION[user_id])){
    $username=mysql_real_escape_string($_COOKIE['userlogin']);
    $loginhash=$_COOKIE['loginhash']; // i guess you should not use mysql_real_escape_string 
    $salt=mysql_real_escape_string(substr($loginhash,-8)); // here would be the place to use it
    $result=mysql_query("select * from members where (username='$username' || email='$username') && salt='$salt' limit 1 ") or die (mysql_error());
    $row=mysql_fetch_assoc($result);
    $cryptedhash=$row['loginhash'];
    if (password_verify($loginhash, $cryptedhash)){
        $_SESSION['username']=$row[username];
        $_SESSION['user_id']=$row['id'];
    }
}
于 2013-09-02T13:43:03.033 回答