Php Crypt函数不';比较字符串和散列时不起作用

Php Crypt函数不';比较字符串和散列时不起作用,php,crypt,Php,Crypt,我使用了一种非常标准的cookie登录方式——我给用户两个cookie,一个带有他的用户名,另一个带有随机生成的字符串加上用户特定的salt 这是登录时发生的情况: $_SESSION['username']=$row[username]; $_SESSION['user_id']=$row['id']; $loginhash=generateRandomBase64String()."_".$row['salt']; $number_of_days = 14; $date_of_expiry

我使用了一种非常标准的cookie登录方式——我给用户两个cookie,一个带有他的用户名,另一个带有随机生成的字符串加上用户特定的salt

这是登录时发生的情况:

$_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已经在数据库中,因为它是在每个用户注册时为他们生成的

我使用会话变量(
$\u session[username]
)来保持用户登录。然后,当用户访问该站点时,我检查两件事:如果未设置
$\u会话[username]
,但设置了
$\u 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'];
    }
}
$\u COOKIE[userlogin]
是正确的值。当我在数据库中检查用户名/salt组合时,找到了正确的结果(
echo$row[username]
给出了正确的值)。但是,下面的
if
条件从未满足。我认为我的PHP配置有点奇怪,但我使用相同的加密机制来存储密码,在那里它可以正常工作

有人能看到这里出了什么问题吗


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

这行是错误的
$loginhash=mysql\u real\u escape\u string($\u cookie['loginhash'])
您不需要转义它(您没有在数据库中使用它),在将它传递给crypt()时必须原封不动地使用它,因此该行可以写为
$loginhash=$\u COOKIE['loginhash'](至少对于这部分代码)

问题在于:

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

crypt()。这样做的副作用是,如果将返回的salt+哈希作为后续调用的哈希传递,crypt()仍将从中提取正确的salt

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


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

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

下面是您的新函数代码:(未经测试,请评论)

使用
PASSWORD\u DEFAULT
将使舒尔在未来的版本中始终使用最强大的算法


三次检查所有涉及的值,特别是
crypt($loginhash,$cryptedhash)
$cryptedhash
返回的值。您的数据库字段是否太短,无法容纳整个散列?DB字段是varchar(64),因此肯定足够长。如果我手动从cookie中获取字符串并执行
crypt(“stringhere”,$cryptedhash)
,它仍然不起作用
crypt($loginhash,$cryptedhash)
返回格式正确的哈希,在本例中,
$1$8CwqG9Pq$Y.3YmFOhXc78ucBki4GPA。
我可能错了,但如果(crypt($loginhash,$cryptedhash)==$cryptedhash)
正在说,如果(crypt(hash,salt)==salt)。。。盐析散列不会使结果不等于盐分吗?@ContextSwitch php文档说,您可以使用该方法来验证hashesblowfish使这一切变得更容易(仅供参考)。您只需在注册时调用预构建函数salt,然后在登录时检查函数salt已经修复,它存储在数据库中,对每个用户都是唯一的且不可更改。哦,我看到您在我发表评论后更改了答案。但这并不能解决问题-如果我不转义字符串,它将保持不变,如果我不直接在crypt函数中使用字符串而不是
$loginhash
,它将保持不变(手动复制/粘贴)。请显示
print\r($row)
紧接着
$row=mysql\u fetch\u assoc($result)
$_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());
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'];
    }
}