TK1*_*123 40 php security authentication encryption login
我是PHP新手,这也是我的第一个登录系统,所以如果你们可以查看我的代码,看看你是否能发现任何安全漏洞,那将是很棒的:
注意:虽然此处未显示,但我正在清理所有用户输入.
第1步:我获取用户选择的密码并通过此功能运行它:
encrypt($user_chosen_password, $salt);
function encrypt($plain_text, $salt) {
if(!$salt) {
$salt = uniqid(rand(0, 1000000));
}
return array(
'hash' => $salt.hash('sha512', $salt.$plain_text),
'salt' => $salt
);
}
Run Code Online (Sandbox Code Playgroud)
第2步:然后我将哈希和salt($password['hash']和$password['salt'])存储在数据库的users表中:
id | username | password | salt | unrelated info...
-----------------------------------------------------------
1 | bobby | 809a28377 | 809a28377f | ...
fd131e5934
180dc24e15
bbe5f8be77
371623ce36
4d5b851e46
Run Code Online (Sandbox Code Playgroud)
步骤1:我获取用户输入的用户名并查看数据库以查看是否返回了任何行.在我的网站上,没有2个用户可以共享相同的用户名,因此用户名字段始终具有唯一值.如果我返回1行,我会抓住该用户的盐.
步骤2:然后我通过加密功能运行用户输入的密码(如上所述),但这次我还提供从数据库检索到的盐:
encrypt($user_entered_password, $salt);
Run Code Online (Sandbox Code Playgroud)
第3步:我现在有一个正确的密码来匹配这个变量:$password['hash'].所以我在数据库上进行第二次查找,看看输入的用户名和散列密码是否一起返回一行.如果是,那么用户的凭证是正确的.
步骤4:为了在用户凭证通过后记录用户,我生成一个随机唯一字符串并对其进行哈希处理:
$random_string = uniqid(rand(0, 1000000));
$session_key = hash('sha512', $random_string);
Run Code Online (Sandbox Code Playgroud)
然后我插入数据库中$session_key的active_sessions表:
user_id | key
------------------------------------------------------------
1 | 431b5f80879068b304db1880d8b1fa7805c63dde5d3dd05a5b
Run Code Online (Sandbox Code Playgroud)
第5步:
我接受在最后一步($random_string)中生成的未加密的唯一字符串,并将其设置为我调用的cookie的值active_session:
setcookie('active_session', $random_string, time()+3600*48, '/');
Run Code Online (Sandbox Code Playgroud)
第6步:
在我的header.php包括顶部有这个检查:
if(isset($_COOKIE['active_session']) && !isset($_SESSION['userinfo'])) {
get_userinfo();
}
Run Code Online (Sandbox Code Playgroud)
该get_userinfo()函数users对数据库中的表执行查找,并返回一个关联数组,该数组存储在名为的会话中userinfo:
//首先此函数获取active_session cookie的值并对其进行哈希以获取session_key:
hash('sha512', $random_string);
Run Code Online (Sandbox Code Playgroud)
//那么它的查找active_sessions,看看表,如果该记录key存在,如果是的话它会抢了user_id与该记录相关联,并使用该做的第二个查找users表来获得userinfo:
$_SESSION['userinfo'] = array(
'user_id' => $row->user_id,
'username' => $row->username,
'dob' => $row->dob,
'country' => $row->country,
'city' => $row->city,
'zip' => $row->zip,
'email' => $row->email,
'avatar' => $row->avatar,
'account_status' => $row->account_status,
'timestamp' => $row->timestamp,
);
Run Code Online (Sandbox Code Playgroud)
如果userinfo会话存在,我知道用户已通过身份验证.如果它不存在但active_sessioncookie存在,那么header.php文件顶部的检查将创建该会话.
我之所以使用cookie而不是单独的会话,是为了坚持登录.因此,如果用户关闭浏览器,会话可能会消失,但cookie仍然存在.并且由于在顶部有检查header.php,因此会重新创建会话,并且用户可以像平常一样充当登录用户.
第1步:无论是userinfo会议和active_session饼干都没有设置.
步骤2:active_sessions删除数据库中表的关联记录.
注意:我可以看到的唯一问题(也许还有许多其他问题)是,如果用户active_session通过在浏览器中自行创建cookie来伪造该cookie.当然,他们必须将cookie的值设置为一个字符串,在加密后,该字符串必须与active_sessions表中的记录匹配,我将从中检索user_id创建该会话的记录.我不确定这是多么现实的可能性,对于一个用户(可能使用自动程序)正确猜测一个他们不知道的字符串然后将被sha512加密并与active_sessions数据库中的表中的字符串匹配到获取用户ID以构建该会话.
对于这篇大文章感到抱歉,但由于这是我网站的一个关键部分,由于我的经验不足,我只想让更有经验的开发人员运行它,以确保它真的安全.
所以你看到这条路线上有任何安全漏洞,如何改进?
set*_*rgo 29
您应该包括某种超时或故障转移,以防止暴力攻击.有很多方法可以做到这一点,包括基于IP的阻止,增量超时等.这些方法都不会阻止黑客,但它们可能会让它变得更加困难.
另一点(你没有提到,所以我不知道你的计划)是失败的消息.使失败消息尽可能模糊.提供一个错误信息,如"该用户名存在,但密码不匹配"可能有助于最终用户,但它杀死登录功能.你刚刚转换了一个需要花费O(n^2)时间O(n)+ 的蛮力攻击O(n).黑客只需先尝试用户名(使用设置密码)的所有值,直到失败消息发生变化,而不是需要尝试彩虹表中的每个排列(例如).然后,它知道一个有效的用户,只需强制密码.
除此之外,您还应确保在用户名存在且不存在时经过相同的时间.当用户名实际存在时,您正在运行其他进程.因此,当用户名存在时,响应时间会更长,而不存在.一个非常熟练的黑客可以定时页面请求以找到有效的用户名.
同样,除了过期的cookie之外,还应该确保会使会话表到期.
最后,在get_user_info()调用中,如果存在多个并发的活动登录,则应终止所有打开的会话.确保在一定量的不活动(例如30分钟)后超时会话.
按照@Greg Hewgill提到的内容,您没有包含以下任何内容:
您的服务器是安全的,但如果有人可以读取已交换的数据(MITM),那么算法的安全性并不重要.您应该确保只通过加密协议进行通信.
...通过加密功能运行用户输入的密码...
那么密码如何从浏览器传到服务器?你没有提到防范中间人攻击.
此代码...
function encrypt($plain_text, $salt) {
if(!$salt) {
$salt = uniqid(rand(0, 1000000));
}
return array(
'hash' => $salt.hash('sha512', $salt.$plain_text),
'salt' => $salt
);
}
Run Code Online (Sandbox Code Playgroud)
...不好。使用新的密码API并完成操作。除非您是专家,否则您不应尝试设计自己的身份验证系统。他们很难做到正确。
为了在通过凭据后登录用户,我生成了一个随机的唯一字符串并将其哈希:
只要让PHP处理会话管理即可。rand()并且mt_rand()都是非常不安全的随机数生成器。