Silverlight to WCF Service Object Encryption

Details a relatively easy way to protect your intellectual property / data going over the wire from your WCF service to your Silverlight application.

Recently in discussing a Silverlight project that uses a WCF Service to handle it's data, the issue came up that private data which represents the intellectual property of the application owner/developer would be transmitted over the wire as plaintext, enabling someone to use an HTTP sniffer such as Fiddler to capture and "steal" this information while using the application. Furthermore, it became apparent that users might be able to attempt to access the WCF service independently from outside of the Silverlight application, an action that was not desirable.  We would only want somebody to be able to use the service via the Silverlight application.

You could enable SSL under such a scenario, and provide the requisite elements in your clientaccesspolicy.xml file:

 <allow-from http-request-headers="SOAPAction">
 <domain uri="http://*"/>
 <domain uri="https://*" />
 </allow-from>

However, there is another technique that may be a lot easier, especially if the process of setting up a public certificate and all the requisite SSL settings is not feasible. You can provide an encryption class that both the Silverlight app and the WCF service can use, and simply encrypt the string properties of all DTO's that go over the wire, in either direction.  Silverlight supports AESManaged, which is essentially the Rijndael symmetric algorithm with a fixed block size and iteration count. This class functions the same way as the .NET Framework RijndaelManaged class but limits blocks to 128 bits and does not allow feedback modes.  An "Encryption Helper" class that provides this functionality will work both in Silverlight and on the server in the WCF Service.

In addition, we can create the salt or other part of the Rfc2898DeriveBytes key required for encryption and decryption from the hash codes of properties of the  Silverlight assembly itself - something that would prove that the service is truly being accessed only via our Silverlight app. By storing these values in  appSettings elements of the service's web.config file, all we would need to do is get these salt and password values in the Application_Startup event of our Silverlight app like this:

  Salt = Assembly.GetExecutingAssembly().FullName.GetHashCode().ToString();

  I originally started with the hash code of the Assembly, but an astute reader (see comments below) commented that it was changing.  The FullName  value will be the same in either debug or release builds, and at runtime, making it easy to grab and store in our web.config on the server. You, the developer, can easily set a breakpoint in your Silverlight codebehind just below the above line, get the salt value, and paste it into your WCF service web.config file in the appropriate appSettings "salt" key element. Another property of the assembly that appears not to change at runtime is:

ManifestModule.Name.GetHashCode();

  The final part of the solution involves providing "generic object encryption". Using Reflection, it is possible to create Encrypt and Decrypt methods that will accept any object, change all the string properties to their encrypted values, and return the object back to your code to send over the wire.

  The resultant encrypted  SOAP payload for a "Customer" DTO instance encrypted in this manner looks like this:


<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetCustomerResponse>
<GetCustomerResult xmlns:a="http://schemas.datacontract.org/2004/07/SLObjectEncryption" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Address>5yjSsZRiL2Au18YW/iuUWg==</a:Address>
<a:City>S/DxIstfGrzloOyhzYjHTw==</a:City>
<a:CustomerId>0691d937-8036-411b-9d83-d391df25ab31</a:CustomerId>
<a:Email>a8FK6lLtrXFFcMpFoa1i8mkiNvMB2HXQWi494rb1mto=</a:Email>
<a:FirstName>5r1HSbQEkuA99JT9AEUFmg==</a:FirstName>
<a:LastName>UMD0tfrtWy6fT6F8wdGSBA==</a:LastName>
<a:Phone>uT8bPX+Ap+vKPsk0ZS9gbA==</a:Phone>
<a:State>MPnz12L4A01l93TkqHotEQ==</a:State>
<a:ZipCode>BnmFYko5wpm1PYtZ2yhMtw==</a:ZipCode>
</GetCustomerResult></GetCustomerResponse>
</s:Body>
</s:Envelope>

As can be seen above, the names of the properties are visible, but all the values are encrypted. At the Silverlight side, this object needs only to be passed into the Decrypt method using the same key generated from strings that are created only at runtime, and we've got back our unencrypted class instance. Certainly a lot cheaper than paying $250 a year for a certificate, assuming you even had the infrastructure availability to install and maintain it.

Here is the code for the EncryptionHelper class:

using System;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;

namespace SLObjectEncryption
{
    internal static class EncryptionHelper

    {
         public static Object EncryptObject(object obj)
        {

             byte[] key = EncryptionHelper.GetHashKey(App.Password, App.Salt);
            Type t = obj.GetType();
          
            PropertyInfo[] fi = t.GetProperties();

             object o = null;
            foreach (PropertyInfo f in fi)
            {

                 if (f.PropertyType == typeof(string))
                {
                    PropertyInfo pi = t.GetProperty(f.Name, BindingFlags.Public | BindingFlags.Instance);

                     try
                    {
                        o = pi.GetValue(obj, null);
                     }
                      catch (Exception ex)
                     {
                          System.Diagnostics.Debug.WriteLine(ex.ToString());
                     }

                      string s = (string)o;
                    f.SetValue(obj, EncryptionHelper.Encrypt(key, s), null);
                 }

             }
             return obj;
        }

public static Object DecryptObject(object obj)
{
     byte[] key = EncryptionHelper.GetHashKey(App.Pasword, App.Salt);
     Type t = obj.GetType();
     PropertyInfo[] fi = t.GetProperties();
     object o = null;
     foreach (PropertyInfo f in fi)
     {
         if (f.PropertyType == typeof(string))
         {
             PropertyInfo pi = t.GetProperty(f.Name, BindingFlags.Public | BindingFlags.Instance);

             try
             {
                 o = pi.GetValue(obj, null);
             }
             catch (Exception ex)
             {
                 System.Diagnostics.Debug.WriteLine(ex.ToString());
             }

             string s = (string)o;
             f.SetValue(obj, EncryptionHelper.Decrypt(key, s), null);
         }
     }
     return obj;
}
        
         internal static byte[] GetHashKey(string hashKey, string salt)
        {
            UTF8Encoding encoder = new UTF8Encoding();
             byte[] saltBytes = encoder.GetBytes(salt);
          
            Rfc2898DeriveBytes rfc = new Rfc2898DeriveBytes(hashKey, saltBytes);

             // Return the key
            return rfc.GetBytes(16);
        }

        internal static string Encrypt(byte[] key, string dataToEncrypt)
        {
            AesManaged encryptor = new AesManaged();

             // Set key and IV
            encryptor.Key = key;
            encryptor.IV = key;

             // create memory stream
            using (MemoryStream encryptionStream = new MemoryStream())
             {
                 // Create crypto stream
                 using (CryptoStream encrypt = new CryptoStream(encryptionStream, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
                 {
                     // Encrypt
                     byte[] data = UTF8Encoding.UTF8.GetBytes(dataToEncrypt);
                    encrypt.Write(data, 0, data.Length);
                     encrypt.FlushFinalBlock();
                     encrypt.Close();

                      // Return encrypted data as base64 string
                     return Convert.ToBase64String(encryptionStream.ToArray());
                 }
            }
        }

         internal static string Decrypt(byte[] key, string encryptedString)
        {
            AesManaged decryptor = new AesManaged();

             // convert base64 string to byte array
            byte[] encryptedData = Convert.FromBase64String(encryptedString);

             // Set key and IV
            decryptor.Key = key;
            decryptor.IV = key;

             // create  memory stream
            using (MemoryStream decryptionStream = new MemoryStream())
             {
                 // Create crypto stream
                 using (CryptoStream decrypt = new CryptoStream(decryptionStream, decryptor.CreateDecryptor(), CryptoStreamMode.Write))
                 {
                     // Encrypt
                    decrypt.Write(encryptedData, 0, encryptedData.Length);
                     decrypt.Flush();
                     decrypt.Close();

                      // Return unencrypted data
                     byte[] decryptedData = decryptionStream.ToArray();
                      return UTF8Encoding.UTF8.GetString(decryptedData, 0, decryptedData.Length);
                  }
             }
        }
     }
}

And here is what the Silverlight UI looks like, after it's been "exercised":



In closing, let me just address a couple of concerns: First, nowhere here do I say that this is "better" than SSL. I provide it only as an alternative.  Second, the fact that the password or other component of the generated cryptographic key may be shown in plaintext in the code is not useful to a hacker, as both the password and the hash (both of which can be generated only at runtime) are required for a valid key. The download reflects the change to using the FullName property of the assembly to generate the salt value, and the ManifestModule.Name.GetHashCode() for the password, as discussed earlier.

You can download the full Visual Studio 2008 Solution here.

By Peter Bromberg   Popularity  (5564 Views)