电子邮件确认码的最佳实践

发布于 2024-08-17 11:03:45 字数 889 浏览 14 评论 0原文

我正在创建一个涉及用户注册的 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 作业,其中 wget 是一个删除内容的脚本,因为这很混乱 =P。

谢谢!
马拉

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

I'm creating a PHP website which involves users signing up, and I'm wondering about best practices for "email confirmation" codes.

New users must confirm their email addresses - I do this by generating a code and sending it to the user in an email, which he can then use to activate his account. Rather than storing this key in a database, I'm using a handy little workaround: the code is the result of:

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

Where $timestamp refers to the user-creation time. On the whole I was quite pleased with this, but then I got to thinking, is this secure enough? And what about the possibility of collisions? And I also need to generate codes for password reset, etc. If I used a similar methodology, a collision could result in one user inadvertently resetting another user's password. And that's no good.

So how do you do these things? My thoughts was a table of the following format:

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

Where 'type' would be 1, 2 or 3 meaning "activation", "email change" or "password reset". Is this a good way of doing it? Do you have a better way?

Using a method similar to the above, could I automatically delete anything over two days old without using cron-jobs? My host (nearlyfreespeech.net) does not support them. If at all possible I'd like to avoid having a cron-job on an external host which wget's a script which deletes things, as that's just messy =P.

Thanks!
Mala

Update:
To clarify: I've realized the only way to securely and safely go about this task is by using a database, which is what the original function was trying to avoid. My question is on how the table (or tables?) should be structured. Somebody suggested I do away with codePK and just make the code a PK. So in short, my question is: is this what you do?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

蓬勃野心 2024-08-24 11:03:45

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

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

当然还有许多其他情况你会考虑使用这样的结构。

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

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

通常,您只需向用户表添加一个附加列即可。不过这样也有一些问题,主要是一旦重置了密码或者激活了用户,就会从数据库中删除这个key,导致大部分行都是空值,进而带来一些其他的麻烦。

这本质上可以归结为:

  • 使用用户唯一的盐(也许是全局的秘密盐)对用户的密码进行哈希处理。
  • 通过散列许多随机或伪随机源(例如时间戳、mt_rand() 甚至 random.org)(如果您确实想要随机的东西)来生成密钥。
  • 切勿使用全局盐或用户独有的盐来散列用户可以访问的任何内容,包括密码重置密钥、激活密钥等。

请注意,我绝不是安全专家,而且我可能有忘记了很多事情,而且我可能提到了一些非常糟糕的做法。只是我的 5 美分;-)

When I need these kinds of tricks it is normally of one of two reasons, both mentioned by you:

  1. As a key used for verification emails sent to the user
  2. As a key used for password-reset links

Of course there would be numerous other occasions where you would consider using such a construction.

First of all, you should always use some kind of salt that is hidden and that only you know. Note that this salt should be different for each user. The salt could for example be calculated as sha256(something random). This salt should then be stored in the database along with the username and password (hashed with the salt).

What I would do when sending the password reset link is to create another salt (don't give the user access to anything hashed with your salt. He knows his password, so using bruteforce he could potentially figure out your salt). The other salt, which essentially is only a hash of a random string (you might wanna go for md5 here, as you mentioned that the length is an issue), should you then save into your database.

Often you can just get away with adding an additional column to your users table. This, however, also has a few problems, mainly that once the password has been reset or the user has been activated, you will remove the key from the database, which results in most rows having null values, which in turn brings some other trouble.

What this essentially boils down to:

  • Hash your users' passwords using a unique-for-the-user salt (and perhaps a global, secret salt).
  • Generate a key by hashing a number of random or pseudorandom sources like timestamps, mt_rand() or even random.org if you really want random stuff.
  • Never use your global salt or the salt that is unique to the user for hashing anything that the user gets access to, including password reset keys, activation keys, etc.

Please not that I am by no means a security expert, and I have probably forgotten a number of things, and I may have mentioned some very bad practice things. Just my 5 cents ;-)

仲春光 2024-08-24 11:03:45

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

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

Why use any of the user's data as the basis for the authorisation key?

I presume you're storing the de-activated data in a database so why not just add an additional record that is simply a random key (perhaps an md5'ed uniqid with some additional manipulation) and then check against that?

行至春深 2024-08-24 11:03:45

在您在互联网上发布您的方法之前,它已经足够安全了!这是因为您依赖于默默无闻的安全性,这不是一个好主意。

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

It was secure enough right up until the point where you published your method on the Internet! This is because you were relying on security by obscurity, which is not a good idea.

You ought to use some sort of keyed hash function or MAC ideally, which incorporates a secret key known only to you.

坏尐絯 2024-08-24 11:03:45

为什么不让code字段有唯一索引呢?那么永远不会发生碰撞吗?

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

Why not make code field unique index? So there will never be collsisions?

Also, if you don't need to create hash from user input and match it to database hash (email confirmation, password reset etc) - you can add random string to the hash body, like
md5('xxx'.$username.'xxx'.time().'xxx'.rand())

无尽的现实 2024-08-24 11:03:45

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

Why not ask the user to enter their username and their code, thereby eliminating any problem with collision? You don't lose anything security-wise as you're still asking for the key which they'd get from the email, but you'd stop them being able to reset other users' passwords.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文