Whenever you make use of ViewState, Session, Forms authentication, or other encrypted and/or secured values, ASP.NET uses a set of keys to do the encryption and decryption. Normally, these keys are hidden and automatically generated by ASP.NET every time your application recycles, but there are times when you want to specify a particular, persistent key. This article will explain why hard-coded machineKeys are good, and how to generate random ones for ASP.NET 1.1 or 2.0.
There are two keys that ASP.NET uses to encrypt, decrypt, and validate data in ViewState, Forms Authetication tickets, and out-of-process Session data. The decryptionKey
is used for encryption and decryption of authentication tickets and encrypted ViewState information. The validationKey
is used to validate ViewState and ensure it hasn't been tampered with, and generate unique application-specific session IDs for out-of-process session storage.
You can run into problems if the key changes between postbacks, e.g., if the keys used to generate the ViewState information are different from one page to the next. If that happens, the ViewState validation will fail (because the validationKey
after postback will be different from what was used pre-postback) and the user will get an Invalid_ViewState error. Different keys across postback can also mean that your Forms Authentication tickets will fail, requiring users to log back into your application; out-of-process session information will be lost (since the session IDs will change); and encrypted ViewState information will be unable to be decrypted and read.
Lastly, if you want to use encrypted passwords with ASP.NET 2.0's Membership provider, you have to provide a static key, or else you'll get a You must specify a non-autogenerated machine key to store passwords in the encrypted format error.
Keys can change across postbacks more often than you'd expect. One way is if you're running a web farm. By default, the validationKey
used to create the MAC is randomly generated by ASP.NET when the application pool starts up. This ensures that the validation key is unique and changes periodically. However, since the key is different from server to server, if you're viewing a page on Server A and post it to Server B, when Server B generates a MAC based on the viewstate data, that value won't match the MAC value when the page was initially served by Server A. Thus, you'll get an Invalid_Viewstate error.
Another reason the validationKey and decryptionKey
(and thus the MAC) will be different is if you cross application pools. If you view a page running in pool A, and post to another page on pool B (e.g., through Server.Transfer
), the keys will be different & you'll get a mismatch.
Lastly, the validationKey
and decryptionKey
can change mid-session for users if the application pool restarts. Assume some user is viewing a page on your site and filling out a form. While they're doing that, some sysadmin restarts the pool, thus generating a new key. When the user posts the page, they'll get an Invalid_Viewstate error. The application can also restart if it is set to shut down while idle (the default is to shut down pools that have been idle for 20 minutes). Imagine a user who views a page on your site, goes away for 10 minutes, then maybe spends 20 minutes filling in the form on the page. Meanwhile, no one else is on your site, so the application pool times out & shuts down. When the user finally posts the form (30 minutes after viewing it), the application pool will start back up, create a new validation key, generate a new MAC, notice that the MAC values don't match, and reward your user's diligence with an Invalid_Viewstate error.
So if you want to create a static set of keys, you'll need to put it in the machineKey
block in either the machine.config or web.config. You can read more about the machineKey element on MSDN.
The validationKey
s are a maximum of 64 bytes long. In ASP.NET 1.1, the encryption algorithm was 3DES along with a maximum 24 byte decryptionKey
. ASP.NET 2.0 provided an attribute called decryption
allowing the user to specify the hashing algorithm used for decrypting data. The AES algorithm is the best choice, and accommodates a 32 byte decryptionKey
.
Please note that the below examples contain [...] to indicate that some characters were removed for readability. The actual values of the keys are long, unbroken hex-encoded strings. Don't copy & paste the below examples verbatim into your web.config -- instead, download and run the sample project, or generate random keys via the online demo.
ASP.NET 1.1 version:
<machineKey
validationKey="C3BB96E9C96[...]3F7ACCB7E7DEA"
decryptionKey="E5E046B77ED2C[...]C13205DBA8E3ECEC4BDE346"
validation="SHA1"
/>
ASP.NET 2.0 version:
<machineKey
validationKey="3DC93913A3E7998AE43[...]C519574359262"
decryptionKey="9470AD8F914387CBE0[...]ABA8A0DB762"
validation="SHA1" decryption="AES"
/>
Now that we've discussed the keys in the machineKey
section, let's take a quick peek at some code to randomly generate keys. It's pretty simple.
The below function accepts a number of bytes, uses the .NET Crypto library to generate a byte array of random numbers, and StringBuilder
to build and return a hex-encoded string. Since the random numbers are hex-encoded, a 24 byte random key will produce a 48 character hex-encoded string.
public string getRandomKey(int bytelength)
{
byte[] buff = new byte
;
RNGCryptoServiceProvider rng =
new RNGCryptoServiceProvider();
rng.GetBytes(buff);
StringBuilder sb =
new StringBuilder(bytelength *
2);
for (
int i =
0; i < buff.Length; i++)
sb.Append(
string.Format(
"
{0:X2}", buff[i]));
return sb.ToString();
}
Now we have two simple functions that use the getRandomKey
function to build and return a complete machineKey
section that can be pasted into a web.config.
public string getASPNET20machinekey()
{
StringBuilder aspnet20machinekey = new StringBuilder();
string key64byte = getRandomKey(64);
string key32byte = getRandomKey(32);
aspnet20machinekey.Append("<machineKey \n");
aspnet20machinekey.Append("validationKey=\"" + key64byte + "\"\n");
aspnet20machinekey.Append("decryptionKey=\"" + key32byte + "\"\n");
aspnet20machinekey.Append("validation=\"SHA1\" decryption=\"AES\"\n");
aspnet20machinekey.Append("/>\n");
return aspnet20machinekey.ToString();
}
public string getASPNET11machinekey()
{
StringBuilder aspnet11machinekey = new StringBuilder();
string key64byte = getRandomKey(64);
string key24byte = getRandomKey(24);
aspnet11machinekey.Append("<machineKey ");
aspnet11machinekey.Append("validationKey=\"" + key64byte + "\"\n");
aspnet11machinekey.Append("decryptionKey=\"" + key24byte + "\"\n");
aspnet11machinekey.Append("validation=\"SHA1\"\n");
aspnet11machinekey.Append("/>\n");
return aspnet11machinekey.ToString();
}
Now all we have to do is call the getASPNET11machinekey
or getASPNET20machinekey
functions to get back a random machineKey
section for the environment of our choice.
The sample application is an ASP.NET 1.1 Web Project that contains a machineKey.aspx file demonstrating the above functionality. You can also see a live demo here if you want to generate keys yourself or just see how it works.
We've discussed the decryptionKey
and validationKey
, reviewed how they can change and what problems that can cause, and discussed how to generate and deploy static keys to avoid problems. Hopefully that will help you next time you run into ViewState or other related problems.
You can read more about machineKey
and its impact at:
machineKey
's relation to Invalid_ViewState errors and the ASP.NET Membership password format.