views:

1064

answers:

5

Hi,

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

+3  A: 
  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.
Mehrdad Afshari
Sorry, but this is really wrong, and misleading - GUIDs are not good for this sort of thing, see my answer below.
AviD
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.
Mehrdad Afshari
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.
AviD
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.
Mehrdad Afshari
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
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.
AviD
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.
Mehrdad Afshari
@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.
AviD
@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.
Mehrdad Afshari
+2  A: 

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.

Matthew Flaschen
+1  A: 

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.

peterchen
+2  A: 

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.

JP Alioto
Although the above technically answered my questions directly, I love your implementation idea. After some thought I have expaneded on it some:URL = SHA1(email + randomPass + salt)serverHash = SHA1(email + randomPass)Table Columns:serverHash (primary key)saltThe randompass would also be sent out with the url in the email and used in the original URL calculation then discarded.I believe in this implementation no sensitive information would be stored in the database. Meaning even if the the raw datatable were exposed you could not generate the URL to brute-force the email address
Carl
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.
JP Alioto
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.
Carl
+2  A: 
  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 (I hope I spelled that right), 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.

AviD