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.