4

Currently I have the following set-up for handling users that forgot their password.

  1. A user types in his/her e-mail address and presses the forgot password button.
  2. I look up the user belonging to that e-mail address and I store a new 'forgot password request' in the database. The forgot password request item looks like this:

    Guid ForgotPasswordRequestId;
    Guid UserId;
    DateTime ValidUntil;
    
  3. I generate a token to identify the forgot password request by converting the ForgotPasswordRequest Guid to a Base64 string. The Guid is a version 4 Guid (random, not time or MAC address based). Check this MSDN link for notes on the implementation.

  4. I create a link like www.example.com/account/ForgotPassword?token=TOKEN which is sent to the user in an e-mail.

  5. Whenever someone visits that link within 24 hours he/she can set a new password for that account.

I think the chances of someone guessing the token is small as a Guid is a 122 bit number (128 bits, minus 6 bits for version information). So a hacker would need to try, on average, (2^122 / 2) tokens within 24 hours before succeeding. If my math is correct that is 3 * 10^31 tokens per second, which I'm sure my server can't handle ;). Also the token they need to guess is in no way related to the user they wish to reset the password for.

However, is it correct to generate the token directly from a generated Guid? Is there anyway that using a Guid and not a secure random number generator makes an attack feasible? Is there any other problem in this scheme that makes it unsecure?

I've seen this question, although its related it doesn't explain best practices for generating the token.

Roy T.
  • 189
  • 1
  • 5

3 Answers3

5

You should create a cryptographically random token for this. Yours is definitely not random enough, and could be guessed much quicker than the (2^122 / 2) you mention. The importance of cryptographic randomness has been discussed many times already on this forum, for example here.

As you mention that you are using .net, have a look at ASP.Identity which can generate tokens for you, using the DataProtectorTokenProvider. If you are not using ASP.Identity (or Owin for that matter), you can also look into using RNGCryptoServiceProvider to generate secure random tokens. I'm not saying you could not do it yourself, but it is hard.

Edit: If you are talking about a GUID (as you reference to it), you can read this answer:

To identify a user of an online banking application on the other hand, (or really probably even a password reset function of a site where identity is valuable) GUIDs are definitely inadequate.

Michael
  • 5,393
  • 2
  • 32
  • 57
  • Unfortunately I'm not using Owin or ASP.Identity. The answer you linked mentions about Guids: `No. They're generated using a non-random algorithm, so they are not in any way cryptographically random or secure`. Which is only true for certain types of Guids (which were based on MAC+Time). A v4 Guid is truly random. However, its generated using the standard random number generator. Not using the RNGCryptoServiceProvider. Would my solution be valid if I construct a Guid from 122 bytes provided by the RNGCryptoServiceProvider? Or do you see other problems? – Roy T. Sep 10 '15 at 13:29
  • 1
    A v4 GUID may be truly random, but it is certainly not cryptographically random, as pointed out by Xander in the linked post. That said, you can indeed use RNGCryptoServiceProvider to generate a 122 byte value. The longer the better (I think asp.identity's reset tokens have a length of around 500 bytes, would need to check). If you are not constrained in length: make it longer. Otherwise, 122 bytes will suffice. As you generate the tokens manually, you have to make sure to let them expire after 24 hours. – Michael Sep 10 '15 at 14:00
  • I store how long a token is valid in the database, just to make sure the outside doesn't do something weird with them. I think I'll shorten the time a bit to be extra safe. – Roy T. Sep 10 '15 at 14:34
  • You could optionally hash the tokens before you store them in db, but if they are only valid for 24 hours, it doesn't add much to the security. – Michael Sep 10 '15 at 14:36
2

You could include not only code, but also user identifier in the password reset link. For example:

www.example.com/account/forgot-password?user-id=a45sdf48kf345&token=TOKEN

Whenever someone visited the Forgot Password link, you would validate not only token and its expiration date but also user id before resetting password.

In this scheme attacker would have to not only guess the token but also the user for which it has been generated.

user44
  • 121
  • 2
0

To improve your approach you can

  • Use strong pseudo-random noise instead of GUID
  • Limit the number of times someone attempts to access a reset link for particular user, e.g. to 5. You'd need to add userId to the request that you send and a count column in the database
  • Enforce delay before the next attempt, e.g. 1 sec, 5 sec, 60 sec
  • Block IP address for some small time if the number of attempts reached some max count. This may help with small scale DOS attack.
  • use SSL for your requests, so that users don't send new passwords in the clear
oleksii
  • 1,046
  • 1
  • 9
  • 19
  • I don't understand your second point. Why would I add a userId to the request? The I can already infer for which user the token is so I don't see what this is for exactly :). – Roy T. Sep 10 '15 at 14:35
  • The second point is about counting how many attempts were made for each user. If you don't store it in a db how would you know? – oleksii Sep 10 '15 at 15:50
  • Ah I understand now! – Roy T. Sep 11 '15 at 07:32