Practical .NET

An Encryption Strategy

Encrypting data that you want stored in your View and returned to you when the user clicks the Submit button has its own special problems. Here’s a complete solution and, as a bonus, an Encryption object you can use anywhere.

Sometimes, in my Views, I have data enclosed in hidden elements that I don’t want my users to access (as I’ll discuss in an upcoming column, this is a way to deal with the over post/mass migration hack). To hide data from my users, I encrypt the data as part of putting it in the hidden element and decrypt it when it’s posted back to the server. This is more of an interesting problem than you might think because ASP.NET has two unique problems when it comes to encrypting and decrypting data that you intend to roundtrip to the browser.

If, for example, you want to use the Advanced Encryption Standard (AES) as your encryption model -- and you do -- then you need a matched set of an encryptor and a decryptor, built with identical keys. This leads to the first problem: Where do you put those two objects while the page is down in the browser, while minimizing the space taken on the server? And then there’s the second problem: If you’ve looked at any encryption code, you might have noticed that it usually involves working with byte arrays. If I’m going to stash the data in an HTML page, I have to store it as text.

Creating Encryptors and Decryptors
The strategy I’ve settled on is to create a unique AES encryptor/decryptor pair each time a user starts a new session with my Web site and store them in the Session object. To implement that, I add the Session_Start method shown in the following code to my Global.Asax file:

Protected Sub Session_Start()
  Dim aes As Aes
  aes = Aes.Create()

  Session("encryptor") = aes.CreateEncryptor(aes.Key, aes.IV)
  Session("decryptor") = aes.CreateDecryptor(aes.Key, aes.IV)
End Sub

This code creates an Aes object with Key and IV properties, each of which contain a random collection of bytes, new every time the Aes object is created. I use those keys to create a matched set of AES encryptor and decryptor objects, stashed in my Web site’s Session object.

You have options here: You could store the aes object or the two keys in the Session object instead of storing the encryptor/decryptor pair. That would transfer the responsibility of creating the encryptor/decryptor pair to your controllers (and require you to create the pair every time you want to use them). Alternatively, you could create the pair once at startup by putting this code in the Application_Start method and putting the pair in the Application object. I prefer changing the encryption method more frequently and, by creating the pair in the Session_Start method, if I ever want to change the encryption strategy for my Web site, I just change the code in this method.

Encrypting and Decrypting Data
To hold the code that uses the encryptor/decryptor pair, I created a separate module (in C#, I’d create a class marked as static). I use this module in non-ASP.NET environments also, but it has some special features to support ASP.NET (while I’m using ASP.NET MVC in this code, this strategy works equally well in ASP.NET WebForms).

I declare the class as a module or a static class so that I can call the methods on the class without having to instantiate the class (or, in Visual Basic, even mentioning the module name). Here’s the module’s declaration in Visual Basic:

Public Module Encryption

My module/static class contains an EncryptText method and a DecryptText method. Both methods accept a string and return a string. Here’s the signature for my EncryptText method:

Public Function EncryptText(
  text As String, Optional encryptor As ICryptoTransform = Nothing) As String

As you can see, I provide a second, optional parameter that accepts an encryptor. Inside the method, the first thing I do is check if that second parameter has anything in it and, if it doesn’t, I grab the encryptor out of the Session object. I do this so I can override the encryptor from the Session object when creating unit tests (and when using this object in environments that don’t have an equivalent to the Session object):

If encryptor Is Nothing Then
  encryptor = HttpContext.Current.Session("encryptor")
End If

Now I have to encrypt the text passed in as the first parameter to the method. I’ve seen this done using a combination of a StreamWriter, a MemoryStream and a CryptoStream ... but I’ve never successfully used that technique to encrypt the data into text and decrypt it back out (I’m not saying that there aren’t people doing that every day -- I’m just saying that I’m not one of those people). Besides, this code is shorter than the stream-based version:

Dim textBytes() As Byte
Dim encryptedBytes() As Byte
textBytes = Encoding.Unicode.GetBytes(text)
encryptedBytes = encryptor.TransformFinalBlock(textBytes, 0, textBytes.Length)

The output of the TransformFinalBlock is a byte array, so I use the Convert object’s ToBase64String method to change that into a text string before returning it:

  Return Convert.ToBase64String(encryptedBytes)
End Function

Here’s the matching text-to-text decrypt method:

Public Function DecryptText(encrypted As String, 
  Optional decryptor As ICryptoTransform = Nothing) As String
  If decryptor Is Nothing Then
    decryptor = HttpContext.Current.Session("decryptor")
  End If

  Dim encryptedBytes() As Byte
  Dim textBytes() As Byte
  encryptedBytes = Convert.FromBase64String(encrypted)
  textBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length)

  Return Encoding.Unicode.GetString(textBytes)
End Function

Using the Methods
A typical scenario for using these methods is to encrypt a Customer object’s Id property value in my Controller’s Action method before sending it to the View to be wrapped in a hidden element:

Public Sub UpdateCustomer(custId as String) As ActionResult
  Dim cust As Customer
  cust = CustomerRepositor.GetCustomerById(custId)
  cust.Id = Encryption.DecryptText(cust.Id)
  Return View("UpdateCustomer", cust)
End Sub

Of course, this assumes that the property you’re encoding is a string value. However, the properties whose values I typically want to encrypt are, as in this example, the Id properties of the class. These properties are often declared as integers to support auto-generated values back in the database. The typical solution to this problem is to define a separate Model class to pass to your Views and, in the Model class, declare the Id property as String.

With that strategy, more typical code in the Action method would look like this:

Dim cust As Customer
Dim custDTO As New CustomerDTO
cust = CustomerRepositor.GetCustomerById(custId)
custDTO.Id = Encryption.DecryptText(cust.Id)
'... copy other properties to custDTO
Return View("UpdateCustomer", custDTO)

In the View, I wrap the encrypted Id property in a Hidden element using the HiddenFor helper, like this:

@Html.HiddenFor(Function(m) m.Id)

To decrypt the Id when the data is returned to the server, I use code like this:

<HttpPost>
Function UpdateCustomer(cust As CustomerDTO) As ActionResult
  Dim cust As New Customer
  cust.Id = Integer.Parse(Encryption.DecryptText(cust.Id))

I need similar code in any method called using AJAX where the Id property stored in the page to the method is sent to the server.

As for why you’d want to use these routines? Tune in later when I discuss the over post hack and make fun of people much smarter than me.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

  • TypeScript Tops New JetBrains 'Language Promise Index'

    In its latest annual developer ecosystem report, JetBrains introduced a new "Language Promise Index" topped by Microsoft's TypeScript programming language.

Subscribe on YouTube