Encrypting was as simple as
string encryptedString = MachineKey.Encode(Encoding.UTF8.GetBytes(mySecretString), MachineKeyProtection.All);
and decrypting was as simple as
byte decryptedData = MachineKey.Decode(encryptedString, MachineKeyProtection.All);
This could be easily fitted into custom methods to provide numerous helpful tasks, such as generating salted password encryptions to be stored in databases, encrypting serialized objects to be stored in cookies (such as custom forms authentication-type implementations or sessions), protecting shopping cart data. The list goes on.
The MachineKeyProtection enumeration parameter allows you to specify exactly what level of protection you wish to implement, which can be helpful for specific needs. Say you need to ensure something is heavily protected and would like to include an HMAC for validation. The "All" option does this for you. Maybe you have a piece of data that you would like to protect, but it's security isn't mission critical and performance speed is more the priority (as in, say, a large collection of small objects). The "Encrypt" parameter would most likely be the enumeration of choice. Point is, it allows for tailoring to specific scenarios.
Enter the .NET 4.5 revamp!
At first glance, I was a little unsure. While the revamped 4.5 version of the MachineKey class contains the now depreciated Encode and Decode methods, it now has two new methods:
MachineKey.Protect(byte data, params string purposes)
MachineKey.Unprotect(byte protectedData, params string purposes)
My first thought was "ughh... what did you do to my MachineKey?!?!". After checking up on documentation that "ughh" feeling turned into a "WOW!".
The idea behind the "purposes" parameter(s) is it provides a way to supply, as the parameter name suggests, purposes for the data being protected. Think of it as being able to supply contextual boundaries for the use of the data. You'll notice that this parameter is also decorated with the params modifier, allowing for a variable amount of string parameters to be supplied, such as:
byte myUnprotectedBytes = Encoding.UTF8.GetBytes("Hello World!"); byte myProtectedBytes = MachineKey.Protect(myUnprotectedBytes, "some special pupose");
Let's imagine that the purpose "some special purpose" really is some special reason that the string "Hello World!" needs to be protected from prying eyes.
Here's the cool part: Since I supplied "some special purpose" as a purpose parameter during the Protect() call, for the data to be unprotected again and restored to it's previous state, the exact same case-sensitive purpose(s) MUST be supplied during the Unprotect() call:
byte myUnprotectedBytes = MachineKey.Unprotect(myProtectedBytes, "some special pupose"); string originalString = Encoding.UTF8.GetString(myUnprotectedBytes);
Like I said earlier, the params string purposes parameters provide a sort of contextual boundary for the data. If I had instead supplied a different string such as "GIVE ME THE SECRET!" as a purpose during the Unprotect() call, I would have received a rather abstract exception thrown stating "a cryptographic error occurred", which is also nice, as it doesn't give too much details about what exactly went wrong, but we know what happened and that's a nice feature in my opinion as well. This would have also occurred if I had provided the proper purpose and included additional string parameters that were not used during the Protect() call.
Now, I don't know for sure, so don't quote me, but I think the methods may be internally using the purposes params as a form of "salting" the encryption as its being processed. Again, this is only speculation so don't quote me. It just seems this way to me.
You may have noticed that with the new 4.5 methods, we no longer are required to pass a MachineKeyProtection enumeration as a parameter. This is because the new approach is to now apply all protection by default, which should be viewed as a big plus. I will say however, that although the old methods are now depreciated, there may still be occasions where you may choose to use the old methods for the sake of specifying to only "Encrypt" without an HMAC and without validation of purposes for the data in less security-sensitive, more performance-sensitive scenarios.
You may have also noticed that the new methods do not directly take strings as input data or return strings as return values. They deal entirely with bytes. Don't view this as having more work to do. View this as the framework relinquishing FULL control of our data to us, the programmers! Essentially it modularized the specific processes of protecting/unprotecting data, and allows us to decide how we want to handle our data before and after the calls to the API.
If the full weight of this incredible revamp to include purposes is not yet apparent to you, fret not for I will shed some light and get those creative wheels turning for you!
Obviously, purposes provided an enormous security boost. It's like requiring 1, or even many passwords to access the data. But there's much more to this than meets the eye...
These purpose parameters can have as little, or as much meaning as you choose. They can be dynamic, user-specific values, application-specific values, session-specific values. You name it. A programmer I respect very much, Brock Allen covered a great scenario used in a cookie-based temp data API using a very clever implementation of dynamically using the Identity property of the current thread principal as a purpose when protecting and unprotecting, ensuring that only the intended user may access the data. You can view his post here.
You could even take this a step further and create a custom class that is designed to act as a "Purpose Factory" to dynamically handle purposes for specific scenarios or you could even create an entire hierarchy of classes or enums designed to provided more specific purposes for different scenarios.
Anyways, simply put, the new MachineKey of .NET 4.5 is a game changer and I suspect we'll be seeing some pretty clever ideas coming out of the woodworks with it.