Encrypted Hidden Inputs in ASP.NET MVC

by ashic 25. February 2010 02:27

I have written an update to this article that shows a better way of doing what is presented here. The core concepts are pretty much the same and I would request you to read this one before the improved version.

There are many situations where we need to persist some data during the user's session. There are a few options in achieving this:

1. Session: Using the Session object will persist the data in server resources. By default, they are persisted to server RAM. Using Session is very simple as to persist, you just go Session["key"] = "value, and to retrieve, var val = Session["key"] as [type]. There are a couple of problems in this approach, however. Firstly, it consumes server resources. If you anticipate large volumes of traffic, you can't store the data in the server's RAM cause it'll simply be too much of a resource hog. Second, if you're using a web farm setup where multiple physical machines (or virtual machines) are hosting your site, then data stored on one machine's RAM will not be available to the other machine. So, if requests from the same user go to different machines, complications are bound to arise. The workaround to this can be using a state server. That also needs setting it up and maintaining, load balancing etc. Furthermore, using a state server would result in a (however small) time delay where state information is persisted and retrieved during requests.

2. Cookies: Information can easily be persisted by using cookies. You can't rely on them though, as many users have them disabled – and they're very easy to disable.

3. Hidden Inputs: Using this method, you store the data as the value of hidden input controls. These values get posted during form submission and you can retrieve them easily. The data is stored in the html sent to the browser and thus does not consume any server resources. There's a slight amount of work required to store the value and retrieve it, but other than that, it's an ideal way to persist session specific data in a scalable way. We are going to explore this method in this article.

I don't discuss ViewState and ControlState here as the discussion is about ASP.NET MVC and not Webforms.

The Problem

Although hidden inputs sound great, there's one major flaw. The user can just go to the browser's "view source" option and actually see what value is persisted. This may not be a problem if you're simply storing a user's colour choice for example, but for sensitive data (for example, some id) that needs to be hidden from prying eyes (of the user and / or eavesdroppers), it can be a major issue.

The Solution

We try to solve the security issue by creating an Html Helper which will encrypt the data to be stored in the hidden input control. When the data is posted back, we need to ensure that consuming the data in the hidden input control is no more difficult than using a plain hidden input. To achieve this, we create a custom controller factory.

The Project

Let's create a default ASP.NET MVC 2 Web Application in VS 2010. Name it "HiddenEncryptDemo".

I installed MVC 2 RC 2 before installing VS 2010 RC. If you have some other combination, slight changes may be necessary.

The first thing I'm going to do is to create a very simple Computer.cs class in the Model folder:


public class Computer
    {
        public string Setting { get; set; }
    }

The reason I'm adding this class is to show that our approach will work not just on simple action parameters, but also on more complex types without a need for a separate model binder.

Next, add this to the Index.aspx view (in the /Views/Home folder):


<% Html.BeginForm("Something", "Home", FormMethod.Post); %>
        <%= Html.Hidden("computer.Setting", "hello world!") %>
        <input type="submit" value='submit' />
<% Html.EndForm(); %>

After that, add the following method to the Home controller:


        [HttpPost]
        public ActionResult Something(Computer computer)
        {
            ViewData["Message"] = computer.Setting;
            return View("Index");
        }

Nothing fancy, we're just setting the ViewData["Message"] to the computer.Setting value. Notice that in the code we added to Index.aspx, the form submitted to "Something" (the name of our action) and the name of the hidden input was "computer.Setting". The default model binder will find a value for "computer.Something" in the request parameters and upon seeing that it can set the Setting property of the computer parameter, it's going to set computer.Setting to the value it found (in our case, "hello world!"). If you run the project, you should see the index page with a submit button. Clicking the button will result in a post to the server where the Something action will get called. The Something action will then set the ViewData["Message"] and return the Index view. As such, you will see the words "hello world!" displayed on the page:

hello

On the initial Index page (or the page got after clicking the button), if you right click anywhere and select view source, you should see this (among other things):

hidden

Notice how the helper added a hidden input control, set its id to "computer_Setting" and name to "computer.Setting". The name is used as the key of the data in the post variables. Also notice how the value is "hello world!". The value is clearly visible to anyone looking at the source. Our goal is to come up with a way to hide that value from clear sight while at the same time ensuring everything works as smoothly and easily as it just did. A bonus would be if no change was required in the controller's code (i.e. no specific attributes needed on the consuming action or controller, no special model binding needed for the parameter etc.) – it would be unobtrusive.

The Encryption Provider

The first thing we'll need is an encrypting mechanism that's easy to use. Create a new folder in the project called "Helpers". [I know, I know, I should structure it better – but this is a demo :) ] Add a new interface called IEncryptString to the folder. The interface is very simple:


public interface IEncryptString
{
    string Encrypt(string value);
    string Decrypt(string value);
    string Prefix { get; }
}

Add an implementation of that interface called ConfigurationBasedEncryptionProvider:


public class ConfigurationBasedStringEncrypter : IEncryptString
{
    private static readonly ICryptoTransform _encrypter;
    private static readonly ICryptoTransform _decrypter;
    private static string _prefix;
    static ConfigurationBasedStringEncrypter()
    {
        //read settings from configuration
        var key = ConfigurationManager.AppSettings["EncryptionKey"];
        var useHashingString = ConfigurationManager.AppSettings["UseHashingForEncryption"];
        bool useHashing = true;
        if (string.Compare(useHashingString, "false", true) == 0)
        {
            useHashing = false;
        }

        var prefix = ConfigurationManager.AppSettings["EncryptionPrefix"];
        if (string.IsNullOrWhiteSpace(prefix))
        {
            _prefix = "encryptedHidden_";
        }
        byte[] keyArray = null;

        if (useHashing)
        {
            var hashmd5 = new MD5CryptoServiceProvider();
            keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(key));
            hashmd5.Clear();
        }
        else
        {
            keyArray = UTF8Encoding.UTF8.GetBytes(key);
        }

        TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
        tdes.Key = keyArray;
        tdes.Mode = CipherMode.ECB;
        tdes.Padding = PaddingMode.PKCS7;

        _encrypter = tdes.CreateEncryptor();
        _decrypter = tdes.CreateDecryptor();

        tdes.Clear();
    }

    #region IEncryptionSettingsProvider Members

    public string Encrypt(string value)
    {
        var bytes = UTF8Encoding.UTF8.GetBytes(value);
        var encryptedBytes = _encrypter.TransformFinalBlock(bytes, 0, bytes.Length);
        var encrypted = Convert.ToBase64String(encryptedBytes);
        return encrypted;
    }

    public string Decrypt(string value)
    {
        var bytes = Convert.FromBase64String(value);
        var decryptedBytes = _decrypter.TransformFinalBlock(bytes, 0, bytes.Length);
        var decrypted = UTF8Encoding.UTF8.GetString(decryptedBytes);
        return decrypted;
    }

    public string Prefix
    {
        get { return _prefix; }
    }

    #endregion

}

You can see that in the class' static constructor, I'm reading in some configuration settings. While this goes against "convention over configuration", extensibility isn't really needed in terms of encryption keys. I though about using the machine key as the encryption key, but considering sever farm environments and other issues that could arise, I just chose to go with an app setting instead. Anyway, you can see that I'm using the configuration data to set up a couple of ICryptoTransforms that will be used for encrypting and decrypting. The class has two instance methods, Encrypt and Decrypt, which do pretty much what you'd expect. I'll explain the prefix part later.

The Html Helper

With the encryption provider out of the way, let's add our Html Helper. Create a new file called InputExtensions.cs in our Helpers folder and add the following code:


namespace HiddenEncryptDemo.Helpers
{
    using System;
    using System.Web.Mvc;
    using System.Web.Mvc.Html;

    public static class InputExtensions
    {
        public static HeartysoftHtmlHelper Heartysoft(this HtmlHelper helper)
        {
            return new HeartysoftHtmlHelper(helper);
        }
    }

    public partial class HeartysoftHtmlHelper
    {
        private readonly HtmlHelper _helper;
        private static readonly IEncryptString _encrypter = new ConfigurationBasedStringEncrypter();

        public HeartysoftHtmlHelper(HtmlHelper helper)
        {
            _helper = helper;
        }
        public MvcHtmlString EncryptedHidden(string name, object value)
        {
            if (value == null)
            {
                value = string.Empty;
            }

            string strValue = value.ToString();
            var encryptedValue = _encrypter.Encrypt(strValue);
            var encodedValue = _helper.Encode(encryptedValue);
            var newName = string.Concat(_encrypter.Prefix, name);

            return _helper.Hidden(newName, encodedValue);
        }
    }
}

Here, I'm firstly creating a "Heartysoft" helper which in turn has an EncryptedHidden method. This approach ensures that my helpers don't conflict with other helpers and in the view, I can do this: Html.Heartysoft().HiddenEncrypt(…). The EncryptedHidden helper is pretty simple. It takes the name and value to be used and encrypts the value. It also adds the prefix to the name. Remember our EncryptionProvider had a Prefix property? Here's where it comes in. So, if you pass in "computer.Name", the hidden input would have a name of "encryptedHidden_computer.Name" and id of "encryptedHidden_computer_Name". Of course, if you specify a different prefix in configuration, that would have been used instead. The prefix is used to determine which inputs need decrypted down the line. At the end of the EncryptedHidden method, we're using the default Html helper's Hidden method to generate the <input> tag.

Configuration

Go to web.config and ensure that under <configuration>, the following entries are present:


<appSettings>
        <add key="EncryptionKey" value="asdjahsdkhaksj dkashdkhak sdhkahsdkha kjsdhkasd"/>
</appSettings>

The View

At this point, go to Index.aspx and change the html of the form to:


<% Html.BeginForm("Something", "Home", FormMethod.Post); %>
       <%= Html.Heartysoft().EncryptedHidden("computer.Setting", "hello world!") %>
       <%--<%= Html.Hidden("computer.Setting", "hello world!") %>--%>
       <input type="submit" value='submit' />
   <% Html.EndForm(); %>

I've just commented out the old helper and used our new helper. If you run the page and click the button, you should see this:

encrypted-res

Notice that the message has disappeared. Viewing the html source, you should see that the html tag now looks like this:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="TA9p71fDeNMfjlCPGOtK9Q==" />

Notice that although the "hello world!" value has been encrypted, the name has changed to "encryptedHidden_computer.Setting". When the form is posted back after the button click, the input parameter is thus "encryptedHidden_computer.Setting". Since there's no longer a "computer.Setting" input parameter, the model binder doesn't assign the computer parameter's Setting property with any value. The ViewData["Message"] thus stays empty and we se a blank on the result page.

So, our final challenge is to ensure that the model binder finds the original value. This will be done through a custom controller factory.

The Controller Factory

Our controller factory will look for any encrypted input parameters, decrypt them and add them to RouteData using their original names so that they're available to the model binder. Add a class called DecryptingControllerFactory:


public class DecryptingControllerFactory : DefaultControllerFactory
{
    private static IEncryptString _decrypter = new ConfigurationBasedStringEncrypter();

    public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        var parameters = requestContext.HttpContext.Request.Params;
        var encryptedParamKeys = parameters.AllKeys.Where(x => x.StartsWith(_decrypter.Prefix)).ToList();
        foreach (var key in encryptedParamKeys)
        {
            var oldKey = key.Replace(_decrypter.Prefix, string.Empty);
            var oldValue = _decrypter.Decrypt(parameters[key]);
            requestContext.RouteData.Values[oldKey] =  oldValue;
        }

        return base.CreateController(requestContext, controllerName);
    }
}

As you can see, we're identifying encrypted input parameters by checking which keys start with our encryption prefix. For each match, we get the value by decrypting and the original name by removing the prefix. We're not really changing the generated controller in any way – the controller factory just acts as a point in the request-response cycle where we can add parameters that are to be consumed by the model binder.

The last change you need to do is open up the global.asax.cs file and ensure the Application_Start looks like this:


protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    ControllerBuilder.Current.SetControllerFactory(typeof(DecryptingControllerFactory));
}

We're just registering our controller factory on that last line.

Running the Page

We'll need to stop the dev server (if you're using IIS, simply restart it). We need to do this because we made changes to the global.asax.cs page. Right click the dev server's icon in your traybar and click stop:

stop-server

You can now simply hit ctrl + F5 in VS to run the page. Click the button and you should see this:

result

Finally, view the html source in the browser, and you should see that the hidden input's value is encrypted:


<input id="encryptedHidden_computer_Setting" name="encryptedHidden_computer.Setting" type="hidden" value="TA9p71fDeNMfjlCPGOtK9Q==" />

That means that our helper has encrypted the value for us and on postback, the controller factory is decrypting the value and adding it to the input parameters so that the model binder can correctly do its thing.

Possible Questions

1. Phil Haack already has a blog post stating how to add action method parameters here:
http://haacked.com/archive/2010/02/21/manipulating-action-method-parameters.aspx
Why didn't you use that method to add the input parameter?
The method described in that post uses an action filter. There are two downsides to that. First, you'd need to go about and decorating your controllers and actions with some [Decrypt] attribute. That's obtrusive. Second-and-more-important, action filters run after model binding. That would work for simple parameters for action methods, but for complex parameters (i.e. viewmodels) or parameters that need complex model binding, that simply won't work. My approach here adds the input parameter before the model binding stage and thus, the model binders can access the correct values without changing anything.

2. Why didn't you use a ModelBinder attribute for decryption?
That could have worked, but would have required you to use the model binder whenever you intended to use the Html.Heartysoft().EncryptedHidden() helper. That's obtrusive and forgetting to set an attribute could have caused nightmares. Furthermore, your other model binders would not have recognised the values. The approach outlined here will work with any and all existing model binders.

3. Is the data really secure?
The value sent to the browser is encrypted using a key. The key is never sent to the browser. As long as the key is secure, the data is as secure as the encryption is.

4. Your code structure is crappy.
It's a demo…deal with it ;) The code is pretty straight forward and you can use your our design patterns and abstractions to make it more your-framework-friendly.

5. I changed the key but the change isn't "happening". What gives?
The encryption key and whether to use hashing or not settings are read during the static constructor of our ConfigurationBasedEncryptionProvider. If you need to change the values, restart the server. I figured you didn't need to change these at runtime and thus put them in the static constructor so that they are read in only once.

6. Why a ControllerFactory? Why not in Begin_Request in global.asax.cs?
It seems that adding the parameters in Begin_Request doesn't seem to persist further down the line. Meaning, I tried that and the new parameters added seem to disappear. Hence the controller factory. Adding the parameters at that stage seems to allow the model binder access to the parameters without any problems. Perhaps a custom ActionInvoker would also have worked, but would have needed setting on each controller – or in a custom controller factory. If the latter, then why bother with the ActionInvoker when the factory can add the parameters itself?

7. Why do you add the parameters to RouteData and not Request[…] or Request.Params[…] or Request.Form[…] etc.?
Coz those are readonly.

Source Code

Download

kick it on DotNetKicks.com  Shout it

---------------------------------------------------------

Questions  and comments relating to this article are welcome. Comments completely unrelated to the article and posted with the sole intention of putting your link here are not.

If you spam, your comment will not be approved, will be deleted and your IP blocked. I maintain my site almost daily and such comments – even if they pass the spam filter – will get removed as soon as possible. If this gets too tedious, I may disable comments entirely. Please don't ruin it for everybody else.

---------------------------------------------------------

Share or Bookmark this post…
  • Facebook
  • DotNetKicks
  • Digg
  • LinkedIn
  • Technorati
  • del.icio.us
  • Google
  • Live
  • Tumblr
  • msdn Social
  • Ping.fm
  • Reddit
  • Slashdot
  • StumbleUpon
  • TwitThis

Comments

1/11/2010 5:45:18 PM #

trackback

Encrypted Hidden Inputs in ASP.NET MVC

In this article, Ashic Mahtab shows an elegant, reusable and unobtrusive way in which to persist sensitive

Community Blogs

1/11/2010 6:17:25 PM #

trackback

Encrypted Hidden Inputs in ASP.NET MVC

You've been kicked (a good thing) - Trackback from DotNetKicks.com

DotNetKicks.com

1/12/2010 2:53:33 AM #

trackback

Heartysoft.com | Encrypted Hidden Inputs in ASP.NET MVC

Thank you for submitting this cool story - Trackback from DotNetShoutout

DotNetShoutout

1/12/2010 3:12:40 AM #

Ufuk Kayserilioglu

While the technique documented here is sound and the demonstration is clear, I can see one problem with it. The code does not do any salting and thus is prone to differential cryptoanalysis techniques and might leak some info about the key that was used if the attacker can successfully load many different web pages with differing encrypted hidden values.

The lack of salting also leads to attacks where the attacker uses the encrypted hidden value contained one web page as the encrypted hidden value in another. Thus, the attacker can impersonate another user or submit an unexpected value (or whatever the encrypted value enables him/her to do).

Probably, the best way to salt is using an extra hidden field in the output document that holds part of the salt and/or use the users User-Agent string or IP address (which might change between requests) as another part of the salt.

Ufuk Kayserilioglu Turkey

1/12/2010 3:24:54 AM #

Ori Peleg

Cool! Consider using AES-256 (Rijndael) instead of 3DES, though, and SHA-256 instead of MD5.

Ori Peleg United States

1/12/2010 4:13:28 AM #

ashic

Ufuk, good catch. Although, if the attacker only has many different encrypted values without knowledge of what any one value actually looks like (is it an int? is it a guid?) or what one's original value is, it would be extremely difficult to get the key. The request forgery is a genuine concern. You should ALWAYS consider preventing CSRF attacks by putting a Html.AntiForgeryToken() in the form and a [ValidateAntiForgeryToken] attribute on any action that changes anything in the database or takes any important action. Doing that would mean that although the attacker can submit another user's encrypted value, the [ValidateAntiForgeryToken] attribute would ensure that CSRFS don't hang ten (pardon the pun). This would ensure the site is at least as secure as ASP.NET MVC's [ValidateAntiForgeryToken] verification.

Ori, definitely...Rijndael and SHA would give even better results. It would be very simple to change the encryption method in the static constructor of the ConfigurationBasedEncryptionProvider.

ashic Bangladesh

1/13/2010 1:51:03 AM #

Sohan

Ashic:
Sounds interesting. However, can you please share an example data that you would put in such a hidden field and encrypt? I am sure you have an example that led you to implement this solution.
Having any data in session is not a good thing for sure. But, of course the security is often more important than memory issues. And you can use database to store your session data in case you are running short of memory.
I am sure you have a killer example, can't wait to get that!

Sohan Canada

1/13/2010 2:27:12 AM #

ashic

Actually, there were a lot of questions over at the ASP.NET forums regarding session, asp.net mvc, pros, cons and what not. Hidden inputs are the scalable way of persisting "session" info without needing to think about state servers, their performance hit, load-balancing yet another component etc. There were scenarios were people suggested storing data in Session (ASP.NET Session) because of security concerns and that using encryption would be a good idea but cumbersome (and by people, that includes me!). Since ASP.NET MVC is all about code reuse and DRY, my approach seems to fit all the bills - no database, security, unobtrusiveness, performance etc.

The data you'd persist using this technique would largely depend on your domain. Perhaps you could store user ids without exposing the true id. Perhaps some transaction identifier. Perhaps other things...The point is that the true nature of that data will not be exposed to consumers of the html. If you're using Session for security because handling encryption / decryption is a monotonous and painful task, then this approach will work nicely.

ashic Bangladesh

1/13/2010 2:56:36 AM #

Sohan

Ashic:
Sure I see your point. I was just thinking of a concrete example from any domain. My concern is, if you need security, then some sort of salt and hashing is required. Otherwise, a good hacker can easily break into the ecrypted stuffs. On the other hand, if you don't keed anything secured, then you don't need the encryption. About going to db and stuffs, I think most apps do that anyway. And at some points, its simple to manage keeping secure information at the server. I know its debatable... but in general I am a bit skeptic about pushing secured information to the client unless I am sure I can really secure it!

Sohan Canada

1/13/2010 8:29:56 AM #

ashic

I understand what you're saying. Pushing and encrypted value to the client should be safe if you're not exposing the value by any other means. If you need even more security, you could extend the technique by doing a few things:
1. You could add another hidden input with an encrypted value which would serve as the key of the parameter. On postback, you would then decrypt the key, then decrypt the entry for the key. If your hidden input read computer.Id, then it's easy to assume what it's for (even though getting the value would be much harder). If you have two hiddens like <input value='adasdasda' ... /> and <input value='dfgsawsf' ... />, then it becomes that much harder to know what the values are actually for. This is assuming your encryption key is secure.

2. If you do decide to use a salt, you most likely will need to revert to the Session Id or something that identifies the client request. For this, you'll need to enable Sessions, although you won't be actually persisting data into Session. I'll take a look at what the MVC team is doing in the AntiForgeryToken and see what their using and if I find something useful, I'll update. Ufuk's suggestions for a salt aren't really acceptable in all situations - a user's IP can change, many users can pass in the same user agent string. I guess one solution would be to use the parameter key as the salt, or even multi level encryption.

CSRFs should not be a problem. As I mentioned, ASP.NET MVC's AntiForgeryToken should take care of that. So, even if the same encrypted values are submitted by an attacker, the request will get rejected.

ashic Bangladesh

1/13/2010 9:02:21 AM #

pingback

Pingback from blog.cwa.me.uk

The Morning Brew - Chris Alcock  » The Morning Brew #548

blog.cwa.me.uk

1/13/2010 6:53:44 PM #

trackback

Social comments and analytics for this post

This post was mentioned on Twitter by elijahmanor: "Encrypted Hidden Inputs in ASP.NET MVC" by @ashic #tech #aspnetmvc http://j.mp/cLYhav

uberVU - social comments

1/17/2010 3:52:32 AM #

trackback

Daily tech links for .net and related technologies - Mar 01-03, 2010

Daily tech links for .net and related technologies - Mar 01-03, 2010 Web Development ASP.NET MVC TempData

Sanjeev Agarwal

1/17/2010 2:55:26 PM #

Sanjeev Agarwal

You forget to secure your web.config, appsettings section Smile

Sanjeev Agarwal India

1/17/2010 9:06:36 PM #

ashic

Not really necessary unless your web.config isn't secure. Web.config settings aren't readable outside your server unless someone hands the attacker the physical file. ASP.NET requests for web.config will not return any contents. If you don't trust your hosting environment, then you could easily encrypt sections in the web.config file or the complete file itself.

Btw...I've found a very good salted process to ensure the encryption is even more secure. I'll post an update very soon. Haven't done so yet as I'm swamped with work.

ashic Bangladesh

2/1/2010 2:37:25 PM #

trackback

Encrypted Hidden Redux : Let's Get Salty

Encrypted Hidden Redux : Let's Get Salty

Heartysoft.com

5/13/2010 10:20:17 PM #

pingback

Pingback from 155.renters.ws

318is Headlight 1997 Bmw 323i, 323 Code El Sereno

155.renters.ws

6/8/2010 4:30:21 AM #

pingback

Pingback from alvinashcraft.com

Dew Drop – June 8, 2010 | Alvin Ashcraft's Morning Dew

alvinashcraft.com

8/19/2010 1:13:00 PM #

pingback

Pingback from firstcomputer.interactiveinfonet.info

First computer - Computer electronic - First computer made

firstcomputer.interactiveinfonet.info

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading



Powered by BlogEngine.NET 1.6.1.0
Theme by Ashic Mahtab

Need an expert?

Ashic Mahtab
ashic@live.com
(+44) 07879927393

Stats

Featured Ads

 

Donations

I maintain this site and create all content entirely in my own time just to help you guys out. If you find the stuff helpful, or cool or just like what you see, I'd appreciate you chipping in to help out with the hosting costs. It's easy to do so - just click the button below - no amount is too low :)