我正在使用一种非常标准的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]
是正确的值。当我在数据库中检查用户名/ salt组合时,找到了正确的结果(echo $row[username]
给出了正确的值)。但是,下面的if
条件永远不会满足。我认为我的PHP配置有些奇怪,但我使用相同的加密机制来存储密码,并且它可以正常工作。
所以有人能看到这里出了什么问题吗?
PS我不打算在这里开始讨论cookie安全性或各种可用的散列函数。
答案 0 :(得分:3)
问题在于:
在第一次调用crypt()时,不指定salt。 在你第二次调用crypt()时,你将$ cryptedhash作为salt传递。
如果您没有提供随机盐,则会记录 crypt()
生成随机盐,然后将该盐添加到返回的散列中。这有副作用,如果你传递一个返回的salt + hash作为后续调用的哈希值,crypt()仍会从中提取正确的salt。
不幸的是,使用的算法和salt + hash的长度/格式是基于操作系统,PHP版本以及是否指定salt参数的组合。当你以前使用过你的代码时,你很高兴在两次调用crypt()时都选择了DES。现在,您的环境正在对crypt()的2次调用使用不同的算法,因为您只在其中一个中提供了哈希。
解决方案是将一致的salt传递给对crypt()的两次调用。你可以停止将salt附加到你想要哈希的字符串上,并实际将你的用户盐作为salt参数传递,一切都会好的。
答案 1 :(得分:0)
这一行是错误的$loginhash=mysql_real_escape_string($_COOKIE['loginhash']);
你不需要转义它(你没有将它与数据库一起使用),你必须在将它传递给crypt()时不加改变地使用它,所以可以写入该行作为$loginhash=$_COOKIE['loginhash'];
(至少对于这部分代码而言)
答案 2 :(得分: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'];
}
}