Implementing secure, unique "single-use" activation URLs in ASP.NET (C#)

11,800

Solution 1

  1. Yes, 2128 is long enough.
  2. No, GUID implementations are designed to generate unique GUIDs rather than random ones. You should use a cryptographically secure random number generator (e.g. RNGCryptoServiceProvider) to generate 16 random bytes and initialize a Guid structure with that.
  3. Yes, it's an acceptable approach overall. Both will work.
  4. Yes, if you don't give out any other clues
  5. No, goto 2
  6. No, it's pretty OK. You just need to use a cryptographically secure random number generator to generate the GUID.

Solution 2

  1. No, GUIDs are not fully random, and most of the bits are either static or easily guessable.
  2. No, they're not random, see 1. There is actually a very small number of bits that are actually random, and not cryptographically strong random at that.
  3. It's not, see 1 and 2.
  4. you can, but dont need to... see my solution at the end.
  5. No, see 1 and 2
  6. Yes.

What you should be using instead of a GUID, is a cryptographically strong random number generator - use System.Security.Cryptography.RNGCryptoServiceProvider, to generate long (say, 32 bytes) string of data, then base64 encode that.
Also, assuming this is some kind of registration with sensitive data, you'd want to time limit the validity of the link, say 60 minutes, or 24 hours - depends on your site.
You'll need to keep a mapping of these values to the specific users. Then you can automatically present him with the proper form as needed. Dont need to do url rewriting, just use that as the user's identifier (on this page).
Of course, dont forget this URL should be HTTPS...

Btw, just a note - its good practice to put some form of text in the email, explaining that users shouldnt click on links in anonymous emails, and typically your site wont send, and they should never enter their password after clicking blablabla....

Oh, almost forgot - another issue you should consider is what happens if the user wants several emails sent to him, e.g. hits register several times. Can he do this over and over again, and get many valid URLs? Is only the last one valid? Or maybe the same value gets resent over and over again? Of course, if an anonymous user can put in a request for this email, then DoS may become an issue... not to mention that if he puts in his own email, he can put in any random address too, flooding some poor shmuck's inbox and possibly causing your mail server to get blacklisted...
No one right answer, but needs to be considered in context of your application.

Solution 3

I would recommend just using a plain random number, e.g. bytes from RNGCryptoServiceProvider.GetBytes . GUIDs are not meant to be completely random. They have fixed bits, and some versions use your MAC address. GetBytes also gives you the option to use more than 128 bits (though that should be plenty).

I think it is better not to put the user's data in a url. Though HTTPS can protect it in transit, it may still be in the user's browser history or such. It is better to use POST and associate the random number with a row of your database.

Solution 4

It might be overkill, but you could hash their email address with SHA1 using your guid (NewGuid is fine) as a hash salt and place that in the URL. Then, when they arrive at your page, you could ask them their email address, retrieve the guid and recompute the hash to validate. Even if somebody were to know what email addresses to try, they would never be able to generate a hash collision without knowing the guid you salted with (or it would take them a hell of a long time :). Of course, you would have to save their email and the hash salt guid in the database.

Solution 5

First, if possible you should limit brute force, by limiting the throughput of the query (e.g. limited tries per IP per second, and limited total tries per second).

Depending on the GUID generation algorithm, this might not be a good idea. While recent implementations should be "good enough usually", there can be a lot of predictability to the values. Recent implementations as the one used in .NET apply a hash, otherwise you have clearly identifiable fields for MAC address, and time since boot. However, the possible values are limited, so you don't have true 128 random bits.

If security is the top priority, I'd pick a cryptographically strong random number generator, and build a string of random characters. This also makes oyu independent of an easily guessable algorithm (as a guid.ToString() is easily identified as such) for which an attack might be discovered in the near future.

If these criteria are met, I see no problem in having the key in the query string.

Share:
11,800

Related videos on Youtube

Tyst
Author by

Tyst

Updated on April 17, 2022

Comments

  • Tyst
    Tyst about 2 years

    I have a scenario inwhich users of a site I am building need the ability to enter some basic information into a webform without having to logon. The site is being developed with ASP.NET/C# and is using MSSQL 2005 for its relational data.

    The users will be sent an email from the site, providing them a unique link to enter the specific information they are required. The email will be very similar to the style of email we all get when registering for sites such as forums, containing a randomly generated, unique URL paramter specifically pertaining to a single purpose (such as verifying an email address for a forum).

    My queries are regarding the secure implementation of this problem. I was considering using a GUID as the unique identifier, but am unsure of its implications in the security world.

    1. Is a GUID sufficiently long enough such that values cannot be easily guessed (or brute-forced over time)?
    2. Is .NET's GUID implmentation sufficiently random in the sense that there is an equal chance of generation of all possible values in the "key space"?

    3. If using a GUID is an acceptable approach, should the site then redirect to the information via URL rewriting or by associating the information in a datatable with the GUID as a reference?

    4. Will using a URL rewriting hide the true source of the data?

    5. Should I consider using TSQL's SELECT NEWID() as the GUID generator over the .NET implementation?

    6. Am I completely wrong with my approach to this problem?

    Many thanks,

    Carl

  • AviD
    AviD about 15 years
    Sorry, but this is really wrong, and misleading - GUIDs are not good for this sort of thing, see my answer below.
  • mmx
    mmx about 15 years
    GUID, in sense of unique identifier is not really suited (as I mentioned in #2). But there's nothing to prevent you from using them as a 128 bit number.
  • AviD
    AviD about 15 years
    But the thing is they're not really 128 bit numbers (even though it does take 128 bits to store them). Again, too much of it is either static, or easily guessable. GUIDs have no use in secure identifiers. What the OP needs is something secure, and long enough in the sense of "128 bits of randomness", which GUIDs are not, not even close.
  • mmx
    mmx about 15 years
    The thing is, you don't use Guid.NewGuid() and NEWID() as I said explicitly. You use a CSRNG to generate 16 random bytes and get a GUID representation from it.
  • JP Alioto
    JP Alioto about 15 years
    Glad to help. One note about your solution, you open yourself to a new use case since you cannot recreate the email. That is, the user requests and email, does nothing with it, requests another one and then clicks the link in the first email (as I'm sure you know, users do stuff like that :) So, you will have to expire each ServerHash by keeping the date you sent the email out in the DB and giving the user a reasonable time to respond.
  • Tyst
    Tyst about 15 years
    Yup, thanks. I realise this. The emails and URLs generated are very case-specific and generated to allow the users to generally save themselves some money, so they should probably use them, but I'll implement some handling also.
  • AviD
    AviD about 15 years
    Ah, see - CRNG's do not generate GUIDs. A GUID has a very specific structure, which is not random, only a small part of it is.
  • AviD
    AviD about 15 years
    Oh, and as long as we're mentioning it - 16 bytes of random data will NOT translate to 16 characters - since there will be a lot bytes that would be represented by non-printable characters, you need to base64-encode it. Thus you'll get a longer output string, more like 24 characters... but again, thats not 24 bytes worth of randomness, only 16.
  • mmx
    mmx almost 15 years
    GUID is not 16 ASCII chars. It's 32 hex digits = 16 bytes which you fill with 16 bytes of random data. I don't see a relation here.
  • AviD
    AviD about 14 years
    @Mehrdad, GUID is not 16 bytes of random data, it has a very specific structure, out of which the first few (most) bytes are NOT random. The random element is only in the last few bytes.
  • mmx
    mmx about 14 years
    @AviD: I never said you'd generate a GUID by calling Guid.NewGuid. All I care about is the GUID format. You generate 16 arbitrary bytes with a random number generator and pack it in a GUID format. Yes, it may not serve as a Globally Unique Identifier but that doesn't matter. GUID format is just a convenient format.
  • Kieveli
    Kieveli over 13 years
    Most salting suggestions I've read recommend adding the salt to the beginning as opposed to the end.