PHP 忘记密码功能

36

我有一个小型社区网站,需要实现一些类似于忘记密码的功能。目前我将密码存储在数据库中,使用 MD5 进行加密。

是否有可能对其进行“解密”并通过电子邮件发送给用户,或者我需要设置一个密码重置页面?


7
将密码进行MD5加密存储的整个概念在于您无法解码它们。即使有人闯入您的系统并转储数据库,他们也无法获取密码(当然,如果密码太弱,那是另一回事...)。 - Karoly Horvath
12个回答

136
一个经过MD5哈希的密码是不可逆的。(MD5是哈希,而不是真正的加密,所以有微妙的区别)。而且,你肯定会想要提供一个密码“重置”流程(而不仅仅是通过电子邮件发送密码)。
为了给您提供安全密码重置的高级工作流程:
1.当用户请求重置密码时,让他们输入他们的电子邮件地址
2.不要指示该电子邮件地址是否有效(只告诉他们已发送电子邮件)。这是有争议的,因为它降低了可用性(即我不知道我注册时使用了哪个电子邮件地址),但它为试图收集实际上在您网站上注册的电子邮件信息的人提供了更少的信息。
3.生成一个令牌(可能是哈希时间戳和盐),并将其存储到用户记录中的数据库中。
4.向用户发送一封电子邮件,并附上链接到您的https重置页面的链接(在url中包含令牌和电子邮件地址)。
5.使用令牌和电子邮件地址验证用户。
6.让他们选择一个新密码,替换旧密码。
7.此外,最好在一定时间范围内(通常为24小时)使这些令牌失效。
8.可选地,记录已发生多少“忘记”尝试,并在人们请求大量电子邮件时实施更复杂的功能。
9.可选地,在单独的表中记录请求重置的个体的IP地址。从该IP递增计数。如果它超过了10个……忽略他们未来的请求。
为了让您对哈希有更详细的了解...
当您使用PHP中的md5()函数对值(如密码)进行哈希时,无论在哪个服务器上运行,最终值都将相同。因此,我们可以立即看到哈希和加密之间的一个区别...没有涉及私钥/公钥。
这就是人们提到彩虹表漏洞的地方。彩虹表的基本解释是...将一堆字典单词(弱密码)进行md5()哈希以获得它们的md5()哈希值。将其放入数据库表(彩虹表)中。
现在,如果您破坏了网站的数据库,您可以将用户的哈希密码与彩虹表匹配,以实现“反向”哈希回密码。(您并没有真正地“反向”哈希...但您会明白这个想法)。
这就是“加盐”密码最佳实践的原因。这意味着(再次,非常基本的想法)在对用户的密码进行哈希之前,将随机值附加到其末尾。现在,当彩虹表与您的数据库匹配时,由于“password”的md5()哈希值与“password384746”的不同,因此不容易“反向”。
以下是一个有用的SO Q/A:PHP密码的安全哈希和加盐

1
@Jared Cobb:你具体指的是数字8是什么意思? - testing
2
@testing 好问题... 这与第9点非常相似,只是在这种情况下,攻击者使用机器人网络、代理或其他轮询方法从不同的IP地址攻击您。例如,如果您的系统检测到“忘记请求”的数量在几分钟或几小时内急剧增加,您可能会考虑暂时减缓或禁用该功能。(当然,您的阈值将由您自己对用户基数大小和什么被认为是“正常”体积的主观判断来确定)。 - Jared Cobb
@JaredCobb #7 我通常在重置密码后立即生成新的令牌,这样下次他必须使用新的令牌来重置密码。 - Ing. Michal Hudak
我能否使用先前用户的密码(已经哈希并存储在数据库中)作为随机令牌(用于第三步)?实际上,我需要将其作为参数传递给重置URL。我可以这样做吗? - stack
1
@Thoaren 在我看来,强制要求他们提交用户名是可选的。我认为这并没有提供任何额外的安全性。如果有人(欺诈地)从用户的电子邮件账户中获取了链接,那么他们很可能已经在_您_的网站上破坏了该账户。如果用户的电子邮件地址就是他们的用户名,那么这又是一个无意义的点。 - Jared Cobb
显示剩余4条评论

9
根据这篇文章《基于表单的网站认证权威指南》,在第3步和第4步中,我不确定您是否应该发送与您存储的相同令牌。
我猜想您必须发送令牌,然后对其进行哈希处理并将哈希令牌存储在数据库中。否则,如果您的数据库被攻击,他人可以访问重置密码页面。
总结一下:
$token = md5(microtime (TRUE)*100000);
$tokenToSendInMail = $token;
$tokenToStoreInDB = hash($token);

其中 hash 是一种哈希算法。


1
这是一个非常好的观点,将哈希令牌存储在数据库中。+1 - Avinor
1
使用可预测的令牌并不是一个好主意...它不应该基于任何可预测的东西,比如时间。(这将允许攻击者在生成令牌的时间周围迭代值) 在PHP7中,应该使用random_bytes。在PHP5中,应该使用兼容实现或openssl_random_pseudo_byte - Gert van den Berg
1
(另一种选择是使用秘密随机字符串和用户详细信息以及一些不错的随机数据的哈希值。由于它被存储在数据库中,因此没有理由让令牌可预测)(使用此方法,我将使用password_hash在长字符串上生成令牌,并在链接中发送哈希值的base64编码版本)。 (在存储在数据库之前再次进行哈希可能也是一个选项,但在4-24小时后过期重置链接可能是更好的防御措施)(使用适当的随机数据,只需发送经过base64编码的数据即可)。 - Gert van den Berg

7
不,MD5是不可逆的。哈希密码的目的是使得攻击者即使获得了你的数据库访问权限也无法获取所有人的密码。
话虽如此,使用未加盐的MD5通常可以通过彩虹表进行攻击。为了安全起见,最好使用bcrypt

3

你不能解密密码,而且你甚至不应该考虑以明文形式向用户发送密码。(这是让我再也不会使用某个网站的最大安全漏洞)提供一个密码重置页面,该页面由包含与时间相关的密钥的链接触发,该链接将发送到用户的密码恢复电子邮件中;这是密码恢复领域中最先进的技术。


3

对于你来说最好的做法是在注册时要求用户提交他们的电子邮件地址。如果他们忘记了密码,可以提供一个“忘记密码”的链接,该链接会将其密码重置为随机值并通过电子邮件发送给他们,以便他们可以获得访问权限,然后将密码更改为更易记的内容。这样,您就不需要妥协安全性。 您可以提供一个只需提交用户名的链接,但出于更好的安全性考虑,您应该提供一个问题和答案或易记单词。


3

正如Marcus Reed所述,如果您的PHP版本>=5.5,不要使用MD5,可以使用password_hash()和password_verify()为密码提供简单安全的哈希,并且能够提供成本并自动添加盐到哈希中。

由于我目前没有投票或评论的权限,因此我提供了一个明确的声明以避免混淆。


2

编写一个页面,接受md5和电子邮件地址作为get参数,并在数据库中查找电子邮件和md5加密的密码。按照Jared Cobb的笔记,这应该可以让您走上正确的道路。我也添加了一些示例。

例如,发送url:http://yourdomain.com/resetpassword.php?code=md5codesentviaemail

 $code = isset($_GET['code']) ? $_GET['code'] : '';
    $email = isset($_GET['email']) ? $_GET['email'] : '';
$checkPw = '';

    if(empty($code) || empty($email))
    {
     die();
    }
    $sqlQuery = 'SELECT * FROM users WHERE email = "'.$email.'";
//remember to check for sql injections
    //then get the results as an array, i use a database class eg $user

    if(!empty($user['password']))
    {
     $checkPw = md5($user['password']);
    }else
    {
     die();
    }

    if($checkPw !== $code)
    {
     die();
    }else
    {
    //display form for user to change password
    }

这足以表明用户是有效用户并更改其密码。


2
使用php内置的password_verify和password_hash函数进行密码验证和加密。

2

MD5是一种单向哈希算法。您需要重置他们的密码。


1

不,你不能解密它。这就是整个想法。

你需要发送给他们一个临时密码并让他们重置它。


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