This is the first in a series of articles focused on sharing “real-world” technical information from the Windows Azure community.  This article was written by Walter Myers III, Principal Consultant, Microsoft Consulting Services.

Problem

I have seen various Windows Azure-related posts where developers have chosen to use symmetric key schemes for the encryption and decryption of data. An important scenario is when a developer needs to store encrypted data in SQL Azure, which will then be decrypted in a Windows Azure application for presenting to the user.  Another is a data synchronization scenario where on-premises data must be kept synchronized with data in SQL Azure, with the data encrypted while off-premises in Windows Azure.

A developer might store the encryption key in Windows Azure storage as a blob, which will be secure as long as the storage key that references the Windows Azure storage is safe; but this is not a best practice, as the developer must have access to the symmetric key and may unknowingly compromise the symmetric key on premises. Additionally, if the Windows Azure application is compromised, then it is possible the key will become compromised as well. This article provides a model and code for certificate-based encryption/decryption of data for Windows Azure applications.

Solution

First, let me provide some background. With a certificate-based (asymmetric key) approach, a best practice is to follow a “separation of concerns” protocol in order to protect the private key. Thus, IT would be responsible for any certificates with private keys that are uploaded to the Windows Azure Management Portal as service certificates for use by Windows Azure applications (service certificates available to Windows Azure applications must be uploaded to the corresponding hosted service). Developers will be provided with the public key only for use on their development machines at the time of application deployment. When testing in the development fabric, the developer must use a certificate that they have created through self-certification using IIS7. When deploying, they would simply replace the thumbprint in their encrypt/decrypt code with that of the service certificate uploaded to Windows Azure and also deploy the public key of the service certificate with their application.

The developer must deploy the public key with their application so that, when Windows Azure spins up role instances, it will match up the thumbprint in the service definition with the uploaded service certificate and deploy the private key to the role instance. The private key is intentionally non-exportable to the .pfx format, so you won’t be able to grab the private key through an RDC connection into a role instance.

Implementation of Solution

Now that we have tackled a little theory, let’s walk through this to see the concepts demonstrated concretely. Note that this solution uses the Visual Studio-provided functionality for certificate management.

If you haven’t already, go ahead and install the public key certificate into your personal certificate store. Use Local Computer instead of the Current User store, so your code will be consistent with where Windows Azure will deploy your certificate. Note that in order to see the certificate, you can’t just launch certmgr.msc, because it will take you to the Current User store. You will have to launch mmc.exe and select the File | Add/Remove Snap-In… menu item, add the Certificates snap-in, and select Computer Account in order to see the Local Computer Certificates, as seen in the snapshot below.

 

So your certificate console should now look something like below:

 

Now let’s take a look at how things will look in Visual Studio 2010 before you deploy your application, and then we will look at how the certificate console looks in an Windows Azure role instance. Below is a screen shot where I selected the Properties page for my web role, and selected the Certificates tab. I added the certificate highlighted in the above screenshot and renamed it EncryptDecrypt. Note that the store location is LocalMachine, and the Store Name is My, which is what we want.

Once you have added your certificate here, you can now go to the ServiceDefinition.csdef file and yours should look similar to below. You will also find an entry along with the thumbprint in the ServiceConfiguration.cscfg file.

After you have deployed your application, you can then establish a Remote Desktop Connection (RDC) to any instance (presuming you have configured RDC when you published your application).  In the same manner as above, launch mmc.exe and add the Certificates snap-ins for both Local Computer and Current User.  Your RDC window should look similar to below.

Notice that certificates are installed into the Local Computer personal certificates store, but none have been installed in the Current User personal store. It was the combination of uploading the service certificate to your hosted service and configuring the certificate for your role that caused Windows Azure to install the certificate in your certificate store. Now if you right-click on the certificate and attempt to export, as discussed above, you will see that the private key is not exportable, which is what we would expect, as seen below.

So now we know how the certificate we will use for encrypt/decrypt of our data is handled. Let’s next take a look at the encrypt/decrypt routine that will do the work for us.

public static class X509CertificateHelper

{

public static X509Certificate2 LoadCertificate(StoreName storeName, StoreLocation

storeLocation, string thumbprint)

         {

                  // The following code gets the cert from the keystore

                  X509Store store = new X509Store(storeName, storeLocation);

                           store.Open(OpenFlags.ReadOnly);

                  X509Certificate2Collection certCollection =

                           store.Certificates.Find(X509FindType.FindByThumbprint,

                           thumbprint, false);

                  X509Certificate2Enumerator enumerator = certCollection.GetEnumerator();

                  X509Certificate2 cert = null;

                  while (enumerator.MoveNext())

                  {

                           cert = enumerator.Current;

                  }

                  return cert;

         }

         public static byte[] Encrypt(byte[] plainData, bool fOAEP,

                  X509Certificate2 certificate)

         {

                  if (plainData == null)

                  {

                           throw new ArgumentNullException("plainData");

                  }

                  if (certificate == null)

                  {

                           throw new ArgumentNullException("certificate");

                  }

                  using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider())

                  {

                           provider.FromXmlString(GetPublicKey(certificate));

// We use the public key to encrypt.

                           return provider.Encrypt(plainData, fOAEP);

                  }

          }

         public static byte[] Decrypt(byte[] encryptedData, bool fOAEP,

                  X509Certificate2 certificate)

         {

                  if (encryptedData == null)

                  {

                           throw new ArgumentNullException("encryptedData");

                  }

 

                  if (certificate == null)

                  {

                           throw new ArgumentNullException("certificate");

                  }

                  using (RSACryptoServiceProvider provider = (RSACryptoServiceProvider)

certificate.PrivateKey)

                  {

                           // We use the private key to decrypt.

                           return provider.Decrypt(encryptedData, fOAEP);

                  }

         }

         public static string GetPublicKey(X509Certificate2 certificate)

         {

                  if (certificate == null)

                  {

                           throw new ArgumentNullException("certificate");

                  }

                  return certificate.PublicKey.Key.ToXmlString(false);

         }

         public static string GetXmlKeyPair(X509Certificate2 certificate)

         {

                  if (certificate == null)

                  {

                           throw new ArgumentNullException("certificate");

                  }

                  if (!certificate.HasPrivateKey)

                  {

                           throw new ArgumentException("certificate does not have a PK");

                  }

                  else

                  {

                           return certificate.PrivateKey.ToXmlString(true);

                  }

         }

}

Note that in the Encrypt and Decrypt routines above, we have to get the public key for the encryption but must get the private key for the decryption. This makes sense, because Public Key Infrastructure (PKI) allows us to perform encryption by anyone who has the public key, but only the person with the private key has the privilege of decrypting the encrypted string. A notable difference is when we get the key, we can export the public key to XML as seen in the Encrypt routine, but we can’t export the private key to XML in the Decrypt routine, since certificates are deployed with the private key set as non-exportable on Windows Azure, which we learned earlier.

Let’s now take a look at some code I wrote to simply encrypt a string and decrypt a string using the X509 encrypt/decrypt helper class from above:

string myText = "Encrypt me.";

X509Certificate2 certificate = X509CertificateHelper.LoadCertificate(

         StoreName.My,

         StoreLocation.LocalMachine,

        "D3E6F7F969546ED620A255794CAB31D8C07E9F31");

if (certificate == null)

{

         Response.Write("Certificate is null.");

         return;

}

byte[] encoded = System.Text.UTF8Encoding.UTF8.GetBytes(myText)

byte[] encrypted;

byte[] decrypted;

try

{

         encrypted = X509CertificateHelper.Encrypt(encoded, true, certificate);

}

catch (Exception ee)

{

         Response.Write("Encrypt failed with error: " + ee.Message + "<br>");

         return;

}

try

{

         decrypted = X509CertificateHelper.Decrypt(encrypted, true, certificate);

}

catch (Exception ed)

{

         Response.Write("Decrypt failed with error: " + ed.Message + "<br>");

         return;

}

So in the code above I loaded my certificate, using the personal store on the local machine. The last parameter in the LoadCertificate method of my X509 encrypt/decrypt class holds the thumbprint that I grabbed from the Certificates tab in the property page for the role. As an exercise, you can write some code to retrieve this string from the ServiceConfiguration.cscfg file.

References:  http://www.josefcobonnin.com/post/2007/02/20/Encrypting-with-Certificates.aspx