电子邮件确认码的最佳做法

Posted

技术标签:

【中文标题】电子邮件确认码的最佳做法【英文标题】:Best practices for email confirmation codes 【发布时间】:2011-01-03 05:58:34 【问题描述】:

我正在创建一个涉及用户注册的 php 网站,我想知道“电子邮件确认”代码的最佳做法。

新用户必须确认他们的电子邮件地址 - 我通过生成代码并将其通过电子邮件发送给用户来完成此操作,然后他可以使用该代码来激活他的帐户。我没有将此密钥存储在数据库中,而是使用了一个方便的小解决方法:代码是以下结果:

md5("xxxxxxxxx".$username."xxxxxxxxxxx".$timestamp."xxxxxxxxx");

其中 $timestamp 指的是用户创建时间。总的来说,我对此感到非常满意,但后来我开始思考,这是否足够安全?那么碰撞的可能性呢?而且我还需要生成密码重置等代码。如果我使用类似的方法,冲突可能会导致一个用户无意中重置另一个用户的密码。这也不好。

那么你是怎么做这些事情的呢?我的想法是以下格式的表格:

codePK (int, a-I), userID (int), type (int), code (varchar 32), date (timestamp)

其中“类型”将是 1、2 或 3,表示“激活”、“电子邮件更改”或“密码重置”。这是一个好方法吗?你有更好的方法吗?

使用与上述类似的方法,我可以在不使用 cron-jobs 的情况下自动删除超过两天的任何内容吗?我的主机(nearlyfreespeech.net)不支持它们。如果可能的话,我想避免在外部主机上执行 cron-job,而 wget 是一个删除内容的脚本,因为这很混乱 =P。

谢谢! 马拉

更新: 澄清一下:我已经意识到安全地完成这项任务的唯一方法是使用数据库,这是原始功能试图避免的。我的问题是应该如何构建表格(或表格?)。有人建议我取消 codePK,只将代码设为 PK。所以简而言之,我的问题是:这是你做的吗?

【问题讨论】:

用户有一个有效的电子邮件地址是很重要的,即使只是为了密码重置。虽然有人可以声称“这是用户的责任”,但这是我们正在处理的真实世界......一旦第一个“永远不会忘记密码”的人忘记他的密码,可能一两周后,我的系统将是“有史以来最愚蠢的”。 【参考方案1】:

当我需要这些技巧时,通常是您提到的两个原因之一:

    作为用于发送给用户的验证电子邮件的密钥 作为用于密码重置链接的密钥

当然,还有很多其他场合你会考虑使用这种结构。

首先,您应该始终使用某种隐藏且只有您自己知道的盐。请注意,每个用户的盐应该不同。例如,盐可以计算为sha256(something random)。然后,该盐应与用户名和密码(与盐散列)一起存储在数据库中。

发送密码重置链接时我会做的是创建另一个盐(不要让用户访问任何用你的盐散列的东西。他知道他的密码,所以使用暴力破解他可能会找出你的盐)。另一种盐,本质上只是一个随机字符串的散列(你可能想在这里使用 md5,正如你提到的长度是一个问题),然后你应该保存到你的数据库中。

通常,您只需在用户表中添加一个额外的列即可。但是,这也有一些问题,主要是一旦重置密码或激活用户,您将从数据库中删除密钥,这导致大多数行具有空值,这反过来又带来了一些其他麻烦.

这基本上归结为:

使用用户唯一的盐(可能是全局的秘密盐)散列用户密码。 通过散列多个随机或伪随机源(如时间戳mt_rand() 或 random.org(如果您真的想要随机内容)来生成密钥。 切勿使用您的全局盐或用户独有的盐来散列用户可以访问的任何内容,包括密码重置密钥、激活密钥等。

请不要说我绝不是安全专家,我可能已经忘记了一些事情,并且我可能提到了一些非常糟糕的做法。只是我的 5 美分 ;-)

【讨论】:

【参考方案2】:

为什么使用任何用户数据作为授权密钥的基础?

我假设您将停用的数据存储在数据库中,所以为什么不添加一个简单的随机键的附加记录(可能是 md5'ed uniqid 并进行一些额外的操作)然后检查它?

【讨论】:

最初我试图避免为此使用数据库 - 因此使用 userdata 说实话,我不禁认为数据库是要走的路,否则你将要散列/操作一个相当有限的静态数据池。【参考方案3】:

直到您在 Internet 上发布您的方法之前,它都是足够安全的!这是因为您在默默无闻地依赖安全性,这不是一个好主意。

理想情况下,您应该使用某种带密钥的散列函数或 MAC,其中包含只有您知道的密钥。

【讨论】:

在这种情况下,您已经创建了一个键控散列函数,您可以开始了。我可能应该已经猜到了... ;) ;) 抱歉,我以为我已经指定了 - 但显然我编辑了我明确说过的那一点......无论如何,我想知道表结构/使用单个表所有代码都合适【参考方案4】:

为什么不让代码字段唯一索引?所以永远不会发生冲突?

此外,如果您不需要从用户输入创建哈希并将其与数据库哈希匹配(电子邮件确认、密码重置等) - 您可以将随机字符串添加到哈希正文,例如 md5('xxx'.$username.'xxx'.time().'xxx'.rand())

【讨论】:

那么如果不使用数据库,我将无法检查它 - 该功能的全部意义在于避免使用它们。如果我无论如何都要使用数据库,那么我可以让整个字符串完全随机【参考方案5】:

为什么不要求用户输入他们的用户名他们的代码,从而消除任何碰撞问题?在安全方面您不会丢失任何东西,因为您仍在询问他们从电子邮件中获得的密钥,但您会阻止他们重置其他用户的密码。

【讨论】:

理想情况下,他们只需点击电子邮件中的链接即可。我不希望这太长了=P

以上是关于电子邮件确认码的最佳做法的主要内容,如果未能解决你的问题,请参考以下文章

视图验证的MVC最佳做法?

Firebase Auth 密码重置:如何正确发送包含确认码的密码重置电子邮件?

创建新密码的最佳方法是啥?

忘记密码:实现忘记密码功能的最佳方法是什么?

从 PHP 脚本发送电子邮件的最佳做法是啥?

忘记密码过程的最佳做法是啥?