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.
- Is a GUID sufficiently long enough such that values cannot be easily guessed (or brute-forced over time)?
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"?
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?
Will using a URL rewriting hide the true source of the data?
Should I consider using TSQL's SELECT NEWID() as the GUID generator over the .NET implementation?
Am I completely wrong with my approach to this problem?
Many thanks,
Carl
- No, GUIDs are not fully random, and most of the bits are either static or easily guessable.
- 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.
- It's not, see 1 and 2.
- you can, but dont need to... see my solution at the end.
- No, see 1 and 2
- 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.
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.
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.
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.
create a table in the database with a linkID and a Datesent column, on generation of the link send insert DateTime.Now into the table and return linkId, set the linkID as a querystring parameter on the activationLink. On load of the activation page retrive the linkId and use it to evoke a stored procedure that will return the date when passed the correspondin linkId as a parameter, when you get the date back you can add how long you want the link to stay active by using the .AddDays()/.AddMonths this are C# methods for datetime. then compare the date you got back with today's date. if it has passed its length of days or months give an error message or else continue and display the page content. I sugest you keep the contents of the page in a panel and and set it vissible = "false" then only make the panel visible="true" if the date is still within range.