Home » Data Provider

Category Archives: Data Provider

Advertisements

Encrypt Sitecore Experience Forms Data in Powerful Ways

Last week, I was honoured to co-present Sitecore Experience Forms alongside my dear friend — and fellow trollster šŸ˜‰ — Sitecore MVP Kamruz Jaman at SUGCON EU 2018. We had a blast showing the ins and outs of Experience Forms, and of course trolled a bit whilst on the main stage.

During our talk, Kamruz had mentioned the possibility of replacing the “Out of the Box” (OOTB) Sitecore.ExperienceForms.Data.IFormDataProvider — this lives in Sitecore.ExperienceForms.dll — whose class implementations serve as Repository objects for storing or retrieving from some datastore (in Experience Forms this is MS SQL Server OOTB) with another to encrypt/decrypt data when saving to or retrieving from the datastore.

Well, I had done something exactly like this for Web Forms for Marketers (WFFM) about five years ago — be sure to have a read my blog post on this before proceeding as it gives context to the rest of this blog post — so thought it would be appropriate for me to have a swing at doing this for Experience Forms.

I first created an interface for classes that will encrypt/decrypt strings — this is virtually the same interface I had used in my older post on encrypting data in WFFM:

namespace Sandbox.Foundation.Forms.Services.Encryption
{
	public interface IEncryptor
	{
		string Encrypt(string key, string input);

		string Decrypt(string key, string input);
	}
}

I then created a class to encrypt/decrypt strings using the RC2 encryption algorithm — I had also poached this from my older post on encrypting data in WFFM (please note, this encryption algorithm is not the most robust so do not use this in any production environment. Please be sure to use something more robust):

using System.Text;
using System.Security.Cryptography;

namespace Sandbox.Foundation.Forms.Services.Encryption
{
	public class RC2Encryptor : IEncryptor
	{
		public string Encrypt(string key, string input)
		{
			byte[] inputArray = UTF8Encoding.UTF8.GetBytes(input);
			RC2CryptoServiceProvider rc2 = new RC2CryptoServiceProvider();
			rc2.Key = UTF8Encoding.UTF8.GetBytes(key);
			rc2.Mode = CipherMode.ECB;
			rc2.Padding = PaddingMode.PKCS7;
			ICryptoTransform cTransform = rc2.CreateEncryptor();
			byte[] resultArray = cTransform.TransformFinalBlock(inputArray, 0, inputArray.Length);
			rc2.Clear();
			return System.Convert.ToBase64String(resultArray, 0, resultArray.Length);
		}

		public string Decrypt(string key, string input)
		{
			byte[] inputArray = System.Convert.FromBase64String(input);
			RC2CryptoServiceProvider rc2 = new RC2CryptoServiceProvider();
			rc2.Key = UTF8Encoding.UTF8.GetBytes(key);
			rc2.Mode = CipherMode.ECB;
			rc2.Padding = PaddingMode.PKCS7;
			ICryptoTransform cTransform = rc2.CreateDecryptor();
			byte[] resultArray = cTransform.TransformFinalBlock(inputArray, 0, inputArray.Length);
			rc2.Clear();
			return UTF8Encoding.UTF8.GetString(resultArray);
		}
	}
}

Next, I created the following class to store settings I need for encrypting and decrypting data using the RC2 algorithm class above:

namespace Sandbox.Foundation.Forms.Models
{
	public class FormEncryptionSettings
	{
		public string EncryptionKey { get; set; }
	}
}

The encryption key above is needed for the RC2 algorithm to encrypt/decrypt data. I set this key in a config object defined in a Sitecore patch configuration file towards the bottom of this post.

I then created an interface for classes that will encrypt/decrypt FormEntry instances (FormEntry objects contain submitted data from form submissions):

using Sitecore.ExperienceForms.Data.Entities;

namespace Sandbox.Foundation.Forms.Services.Encryption
{
	public interface IFormEntryEncryptor
	{
		void EncryptFormEntry(FormEntry entry);

		void DecryptFormEntry(FormEntry entry);
	}
}

The following class implements the interface above:

using System.Linq;

using Sitecore.ExperienceForms.Data.Entities;

using Sandbox.Foundation.Forms.Models;

namespace Sandbox.Foundation.Forms.Services.Encryption
{
	public class FormEntryEncryptor : IFormEntryEncryptor
	{
		private readonly FormEncryptionSettings _formEncryptionSettings;
		private readonly IEncryptor _encryptor;

		public FormEntryEncryptor(FormEncryptionSettings formEncryptionSettings, IEncryptor encryptor)
		{
			_formEncryptionSettings = formEncryptionSettings;
			_encryptor = encryptor;
		}

		public void EncryptFormEntry(FormEntry entry)
		{
			if (!HasFields(entry))
			{
				return;
			}

			foreach (FieldData field in entry.Fields)
			{
				EncryptField(field);
			}
		}

		protected virtual void EncryptField(FieldData field)
		{
			if(field == null)
			{
				return;
			}

			field.FieldName = Encrypt(field.FieldName);
			field.Value = Encrypt(field.Value);
			field.ValueType = Encrypt(field.ValueType);
		}

		protected virtual string Encrypt(string input)
		{
			return _encryptor.Encrypt(_formEncryptionSettings.EncryptionKey, input);
		}

		public void DecryptFormEntry(FormEntry entry)
		{
			if (!HasFields(entry))
			{
				return;
			}

			foreach (FieldData field in entry.Fields)
			{
				DecryptField(field);
			}
		}

		protected virtual bool HasFields(FormEntry entry)
		{
			return entry != null
					&& entry.Fields != null
					&& entry.Fields.Any();
		}

		protected virtual void DecryptField(FieldData field)
		{
			if(field == null)
			{
				return;
			}

			field.FieldName = Decrypt(field.FieldName);
			field.Value = Decrypt(field.Value);
			field.ValueType = Decrypt(field.ValueType);
		}

		protected virtual string Decrypt(string input)
		{
			return _encryptor.Decrypt(_formEncryptionSettings.EncryptionKey, input);
		}
	}
}

The EncryptFormEntry() method above iterates over all FieldData objects contained on the FormEntry instance, and passes them to the EncryptField() mehod which encrypts the FieldName, Value and ValueType properties on them.

Likewise, the DecryptFormEntry() method iterates over all FieldData objects contained on the FormEntry instance, and passes them to the DecryptField() mehod which decrypts the same properties mentioned above.

I then created an interface for classes that will serve as factories for IFormDataProvider instances:

using Sitecore.ExperienceForms.Data;
using Sitecore.ExperienceForms.Data.SqlServer;

namespace Sandbox.Foundation.Forms.Services.Factories
{
	public interface IFormDataProviderFactory
	{
		IFormDataProvider CreateNewSqlFormDataProvider(ISqlDataApiFactory sqlDataApiFactory);
	}
}

The following class implements the interface above:

using Sitecore.ExperienceForms.Data;
using Sitecore.ExperienceForms.Data.SqlServer;

namespace Sandbox.Foundation.Forms.Services.Factories
{
	public class FormDataProviderFactory : IFormDataProviderFactory
	{
		public IFormDataProvider CreateNewSqlFormDataProvider(ISqlDataApiFactory sqlDataApiFactory)
		{
			return new SqlFormDataProvider(sqlDataApiFactory);
		}
	}
}

The CreateNewSqlFormDataProvider() method above does exactly was the method name says. You’ll see it being used in the following class below.

This next class ultimately becomes the new IFormDataProvider instance but decorates the OOTB one which is created from the factory class above:

using System;
using System.Collections.Generic;
using System.Linq;


using Sitecore.ExperienceForms.Data;
using Sitecore.ExperienceForms.Data.Entities;
using Sitecore.ExperienceForms.Data.SqlServer;

using Sandbox.Foundation.Forms.Services.Encryption;
using Sandbox.Foundation.Forms.Services.Factories;

namespace Sandbox.Foundation.Forms.Services.Data
{
	public class FormEncryptionDataProvider : IFormDataProvider
	{
		private readonly IFormDataProvider _innerProvider;
		private readonly IFormEntryEncryptor _formEntryEncryptor;

		public FormEncryptionDataProvider(ISqlDataApiFactory sqlDataApiFactory, IFormDataProviderFactory formDataProviderFactory, IFormEntryEncryptor formEntryEncryptor)
		{
			_innerProvider = CreateInnerProvider(sqlDataApiFactory, formDataProviderFactory);
			_formEntryEncryptor = formEntryEncryptor;
		}

		protected virtual IFormDataProvider CreateInnerProvider(ISqlDataApiFactory sqlDataApiFactory, IFormDataProviderFactory formDataProviderFactory)
		{
			return formDataProviderFactory.CreateNewSqlFormDataProvider(sqlDataApiFactory);
		}

		public void CreateEntry(FormEntry entry)
		{
			EncryptFormEntryField(entry);
			_innerProvider.CreateEntry(entry);
		}

		protected virtual void EncryptFormEntryField(FormEntry entry)
		{
			_formEntryEncryptor.EncryptFormEntry(entry);
		}

		public void DeleteEntries(Guid formId)
		{
			_innerProvider.DeleteEntries(formId);
		}

		public IReadOnlyCollection<FormEntry> GetEntries(Guid formId, DateTime? startDate, DateTime? endDate)
		{
			IReadOnlyCollection<FormEntry>  entries = _innerProvider.GetEntries(formId, startDate, endDate);
			if(entries == null || !entries.Any())
			{
				return entries;
			}

			foreach(FormEntry entry in entries)
			{
				DecryptFormEntryField(entry);
			}

			return entries;
		}

		protected virtual void DecryptFormEntryField(FormEntry entry)
		{
			_formEntryEncryptor.DecryptFormEntry(entry);
		}
	}
}

The class above does delegation to the IFormEntryEncryptor instance to encrypt the FormEntry data and then passes the FormEntry to the inner provider for saving.

For decrypting, it retrieves the data from the inner provider, and then decrypts it via the IFormEntryEncryptor instance before returning to the caller.

Finally, I created an IServicesConfigurator class to wire everything up into the Sitecore container (I hope you are using Sitecore Dependency Injection capabilities as this comes OOTB — there are no excuses for not using this!!!!!!):

using System;

using Microsoft.Extensions.DependencyInjection;

using Sitecore.Abstractions;
using Sitecore.DependencyInjection;
using Sitecore.ExperienceForms.Data;

using Sandbox.Foundation.Forms.Models;
using Sandbox.Foundation.Forms.Services.Encryption;
using Sandbox.Foundation.Forms.Services.Data;
using Sandbox.Foundation.Forms.Services.Factories;

namespace Sandbox.Foundation.Forms
{
	public class FormsServicesConfigurator : IServicesConfigurator
	{
		public void Configure(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton(provider => GetFormEncryptionSettings(provider));
			serviceCollection.AddSingleton<IEncryptor, RC2Encryptor>();
			serviceCollection.AddSingleton<IFormEntryEncryptor, FormEntryEncryptor>();
			serviceCollection.AddSingleton<IFormDataProviderFactory, FormDataProviderFactory>();
			serviceCollection.AddSingleton<IFormDataProvider, FormEncryptionDataProvider>();
		}

		private FormEncryptionSettings GetFormEncryptionSettings(IServiceProvider provider)
		{
			return CreateConfigObject<FormEncryptionSettings>(provider, "moduleSettings/foundation/forms/formEncryptionSettings");
		}

		private TConfigObject CreateConfigObject<TConfigObject>(IServiceProvider provider, string path) where TConfigObject : class
		{
			BaseFactory factory = GetService<BaseFactory>(provider);
			return factory.CreateObject(path, true) as TConfigObject;
		}

		private TService GetService<TService>(IServiceProvider provider)
		{
			return provider.GetService<TService>();
		}
	}
}

Everything above is normal service class registration except for the stuff in the GetFormEncryptionSettings() method. Here, I’m creating an instance of a FormEncryptionSettings class but am instantiating it using the Sitecore Configuration Factory for the configuration object defined in the Sitecore patch configuration file below, and am making that available for being injected into classes that need it (the FormEntryEncryptor above uses it).

I then wired everything together using the following Sitecore patch configuration file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<services>
			<configurator type="Sandbox.Foundation.Forms.FormsServicesConfigurator, Sandbox.Foundation.Forms" />
			<register serviceType="Sitecore.ExperienceForms.IFormDataProvider, Sitecore.ExperienceForms">
				<patch:delete />
			</register>
		</services>
		<moduleSettings>
			<foundation>
				<forms>
					<formEncryptionSettings type="Sandbox.Foundation.Forms.Models.FormEncryptionSettings, Sandbox.Foundation.Forms" singleInstance="true">
						<!-- I stole this from https://sitecorejunkie.com/2013/06/21/encrypt-web-forms-for-marketers-fields-in-sitecore/ -->
						<EncryptionKey>88bca90e90875a</EncryptionKey>
					</formEncryptionSettings>
				</forms>
			</foundation>
		</moduleSettings>
	</sitecore>
</configuration>

I want to call out that I’m deleting the OOTB IFormDataProvider above using a patch:delete. I’m re-adding it via the IServicesConfigurator above using the decorator class previously shown above.

Let’s take this for a spin.

I first created a new form (this is under “Forms” on the Sitecore Launchepad ):

I then put it on a page with an MVC Layout; published everything; navigated to the test page with the form created above; filled out the form; and then clicked the submit button:

Let’s see if the data was encrypted. I opened up SQL Server Management Studio and ran a query on the FormEntry table in my Experience Forms Database:

As you can see the data was encrypted.

Let’s export the data to make sure it gets decrypted. We can do that by exporting the data as a CSV from Forms in the Sitecore Launchpad:

As you can see the data is decrypted in the CSV:

I do want to mention that Sitecore MVP JoĆ£o Neto had provided two other methods for encrypting data in Experience Forms in a post he wrote last January. I recommend having a read of that.

Until next time, see you on the Sitecore Slack šŸ˜‰

Advertisements

Encrypt Web Forms For Marketers Fields in Sitecore

In an earlier post, I walked you through how I experimented with data encryption of field values in Sitecore, and alluded to how I had done a similar thing for the Web Forms For Marketers (WFFM) module on a past project at work.

Months have gone by, and guilt has begun to gnaw away at my entire being — no, not really, I’m exaggerating a bit — but I definitely have been feeling bad for not sharing a solution.

In order to shake feeling bad, I decided to put my nose to the grindstone over the past few days to come up with a different solution than the one I had built at work, and this post shows the fruits of that labor.

I decided to reuse the interface I had created in my older post on data encryption. I am re-posting it here for reference:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Sitecore.Sandbox.Security.Encryption.Base
{
    public interface IEncryptor
    {
        string Encrypt(string input);
 
        string Decrypt(string input);
    }
}

I then asked myself “What encryption algorithm should I use?” I scavenged through the System.Security.Cryptography namespace in mscorlib.dll using .NET Reflector, and discovered some classes, when used together, achieve data encryption using the RC2 algorithm — an algorithm I know nothing about:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

using Sitecore.Diagnostics;

using Sitecore.Sandbox.Security.Encryption.Base;

namespace Sitecore.Sandbox.Security.Encryption
{
    public class RC2Encryptor : IEncryptor
    {
        public string Key { get; set; }

        private RC2Encryptor(string key)
        {
            SetKey(key);
        }

        private void SetKey(string key)
        {
            Assert.ArgumentNotNullOrEmpty(key, "key");
            Key = key;
        }

        public string Encrypt(string input)
        {
            return Encrypt(input, Key);
        }

        public static string Encrypt(string input, string key)
        {
            byte[] inputArray = UTF8Encoding.UTF8.GetBytes(input);
            RC2CryptoServiceProvider rc2 = new RC2CryptoServiceProvider();
            rc2.Key = UTF8Encoding.UTF8.GetBytes(key);
            rc2.Mode = CipherMode.ECB;
            rc2.Padding = PaddingMode.PKCS7;
            ICryptoTransform cTransform = rc2.CreateEncryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(inputArray, 0, inputArray.Length);
            rc2.Clear();
            return System.Convert.ToBase64String(resultArray, 0, resultArray.Length);
        }

        public string Decrypt(string input)
        {
            return Decrypt(input, Key);
        }

        public static string Decrypt(string input, string key)
        {
            byte[] inputArray = System.Convert.FromBase64String(input);
            RC2CryptoServiceProvider rc2 = new RC2CryptoServiceProvider();
            rc2.Key = UTF8Encoding.UTF8.GetBytes(key);
            rc2.Mode = CipherMode.ECB;
            rc2.Padding = PaddingMode.PKCS7;
            ICryptoTransform cTransform = rc2.CreateDecryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(inputArray, 0, inputArray.Length);
            rc2.Clear();
            return UTF8Encoding.UTF8.GetString(resultArray);
        }

        public static IEncryptor CreateNewRC2Encryptor(string key)
        {
            return new RC2Encryptor(key);
        }
    }
}

As I had mentioned in my previous post on data encryption, I am not a cryptography expert, nor a security expert.

I am not aware of how strong the RC2 encryption algorithm is, or what it would take to crack it. I strongly advise against using this algorithm in any production system without first consulting with a security expert. I am using it in this post only as an example.

If you happen to be a security expert, or are able to compare encryption algorithms defined in the System.Security.Cryptography namespace in mscorlib.dll, please share in a comment.

In a previous post on manipulating field values for WFFM forms, I had to define a new class that implements Sitecore.Forms.Data.IField in Sitecore.Forms.Core.dll in order to change field values — it appears the property mutator for the “out of the box” class is ignored — and decided to reuse it here:

using System;

using Sitecore.Forms.Data;

namespace Sitecore.Sandbox.Utilities.Manipulators.DTO
{
    public class WFFMField : IField
    {
        public string Data { get; set; }

        public Guid FieldId { get; set; }

        public string FieldName { get; set; }

        public IForm Form { get; set; }

        public Guid Id { get; internal set; }

        public string Value { get; set; }
    }
}

Next, I created a WFFM Data Provider that encrypts and decrypts field names and values:

using System;
using System.Collections.Generic;

using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Forms.Data;
using Sitecore.Forms.Data.DataProviders;
using Sitecore.Reflection;

using Sitecore.Sandbox.Security.DTO;
using Sitecore.Sandbox.Security.Encryption.Base;
using Sitecore.Sandbox.Security.Encryption;
using Sitecore.Sandbox.Utilities.Manipulators.DTO;

namespace Sitecore.Sandbox.WFFM.Forms.Data.DataProviders
{
    public class WFFMEncryptionDataProvider : WFMDataProviderBase
    {
        private WFMDataProviderBase InnerProvider { get; set; }
        private IEncryptor Encryptor { get; set; }

        public WFFMEncryptionDataProvider(string innerProvider)
            : this(CreateInnerProvider(innerProvider), CreateDefaultEncryptor())
        {
        }

        public WFFMEncryptionDataProvider(string connectionString, string innerProvider)
            : this(CreateInnerProvider(innerProvider, connectionString), CreateDefaultEncryptor())
        {
        }

        public WFFMEncryptionDataProvider(WFMDataProviderBase innerProvider)
            : this(innerProvider, CreateDefaultEncryptor())
        {
        }

        public WFFMEncryptionDataProvider(WFMDataProviderBase innerProvider, IEncryptor encryptor)
        {
            SetInnerProvider(innerProvider);
            SetEncryptor(encryptor);
        }

        private static WFMDataProviderBase CreateInnerProvider(string innerProvider, string connectionString = null)
        {
            Assert.ArgumentNotNullOrEmpty(innerProvider, "innerProvider");
            if (!string.IsNullOrWhiteSpace(connectionString))
            {
                return ReflectionUtil.CreateObject(innerProvider, new[] { connectionString }) as WFMDataProviderBase;
            }

            return ReflectionUtil.CreateObject(innerProvider, new object[0]) as WFMDataProviderBase;
        }
        
        private void SetInnerProvider(WFMDataProviderBase innerProvider)
        {
            Assert.ArgumentNotNull(innerProvider, "innerProvider");
            InnerProvider = innerProvider;
        }

        private static IEncryptor CreateDefaultEncryptor()
        {
            return DataNullTerminatorEncryptor.CreateNewDataNullTerminatorEncryptor(GetEncryptorSettings());
        }

        private static DataNullTerminatorEncryptorSettings GetEncryptorSettings()
        {
            return new DataNullTerminatorEncryptorSettings
            {
                EncryptionDataNullTerminator = Settings.GetSetting("WFFM.Encryption.DataNullTerminator"),
                InnerEncryptor = RC2Encryptor.CreateNewRC2Encryptor(Settings.GetSetting("WFFM.Encryption.Key"))
            };
        }

        private void SetEncryptor(IEncryptor encryptor)
        {
            Assert.ArgumentNotNull(encryptor, "encryptor");
            Encryptor = encryptor;
        }

        public override void ChangeStorage(Guid formItemId, string newStorage)
        {
            InnerProvider.ChangeStorage(formItemId, newStorage);
        }

        public override void ChangeStorageForForms(IEnumerable<Guid> ids, string storageName)
        {
            InnerProvider.ChangeStorageForForms(ids, storageName);
        }

        public override void DeleteForms(IEnumerable<Guid> formSubmitIds)
        {
            InnerProvider.DeleteForms(formSubmitIds);
        }

        public override void DeleteForms(Guid formItemId, string storageName)
        {
            InnerProvider.DeleteForms(formItemId, storageName);
        }

        public override IEnumerable<IPool> GetAbundantPools(Guid fieldId, int top, out int total)
        {
            return InnerProvider.GetAbundantPools(fieldId, top, out total);
        }

        public override IEnumerable<IForm> GetForms(QueryParams queryParams, out int total)
        {
            IEnumerable<IForm> forms = InnerProvider.GetForms(queryParams, out total);
            DecryptForms(forms);
            return forms;
        }

        public override IEnumerable<IForm> GetFormsByIds(IEnumerable<Guid> ids)
        {
            IEnumerable<IForm> forms = InnerProvider.GetFormsByIds(ids);
            DecryptForms(forms);
            return forms;
        }

        public override int GetFormsCount(Guid formItemId, string storageName, string filter)
        {
            return InnerProvider.GetFormsCount(formItemId, storageName, filter);
        }

        public override IEnumerable<IPool> GetPools(Guid fieldId)
        {
            return InnerProvider.GetPools(fieldId);
        }

        public override void InsertForm(IForm form)
        {
            EncryptForm(form);
            InnerProvider.InsertForm(form);
        }

        public override void ResetPool(Guid fieldId)
        {
            InnerProvider.ResetPool(fieldId);
        }

        public override IForm SelectSingleForm(Guid fieldId, string likeValue)
        {
            IForm form = InnerProvider.SelectSingleForm(fieldId, likeValue);
            DecryptForm(form);
            return form;
        }

        public override bool UpdateForm(IForm form)
        {
            EncryptForm(form);
            return InnerProvider.UpdateForm(form);
        }

        private void EncryptForms(IEnumerable<IForm> forms)
        {
            Assert.ArgumentNotNull(forms, "forms");
            foreach (IForm form in forms)
            {
                EncryptForm(form);
            }
        }

        private void EncryptForm(IForm form)
        {
            Assert.ArgumentNotNull(form, "form");
            Assert.ArgumentNotNull(form.Field, "form.Field");
            form.Field = EncryptFields(form.Field);
        }

        private IEnumerable<IField> EncryptFields(IEnumerable<IField> fields)
        {
            Assert.ArgumentNotNull(fields, "fields");
            IList<IField> encryptedFields = new List<IField>();
            foreach (IField field in fields)
            {
                encryptedFields.Add(EncryptField(field));
            }

            return encryptedFields;
        }

        private IField EncryptField(IField field)
        {
            Assert.ArgumentNotNull(field, "field");
            return CreateNewWFFMField(field, Encrypt(field.FieldName), Encrypt(field.Value));
        }

        private void DecryptForms(IEnumerable<IForm> forms)
        {
            Assert.ArgumentNotNull(forms, "forms");
            foreach (IForm form in forms)
            {
                DecryptForm(form);
            }
        }

        private void DecryptForm(IForm form)
        {
            Assert.ArgumentNotNull(form, "form");
            Assert.ArgumentNotNull(form.Field, "form.Field");
            form.Field = DecryptFields(form.Field);
        }

        private IEnumerable<IField> DecryptFields(IEnumerable<IField> fields)
        {
            Assert.ArgumentNotNull(fields, "fields");
            IList<IField> decryptedFields = new List<IField>();
            foreach (IField field in fields)
            {
                decryptedFields.Add(DecryptField(field));
            }

            return decryptedFields;
        }

        private IField DecryptField(IField field)
        {
            Assert.ArgumentNotNull(field, "field");
            return CreateNewWFFMField(field, Decrypt(field.FieldName), Decrypt(field.Value));
        }

        private string Encrypt(string input)
        {
            return Encryptor.Encrypt(input);
        }

        private string Decrypt(string input)
        {
            return Encryptor.Decrypt(input);
        }

        private static IField CreateNewWFFMField(IField field, string fieldName, string value)
        {
            if (field != null)
            {
                return new WFFMField
                {
                    Data = field.Data,
                    FieldId = field.FieldId,
                    FieldName = fieldName,
                    Form = field.Form,
                    Id = field.Id,
                    Value = value
                };
            }

            return null;
        }
    }
}

The above class employs the decorator pattern. An inner WFFM Data Provider — which is supplied via a parameter configuration node in \App_Config\Include\forms.config, and is created via magic within the Sitecore.Reflection.ReflectionUtil class — is wrapped.

Methods that save and retrieve form data in the above Data Provider decorate the same methods defined on the inner WFFM Data Provider.

Methods that save form data pass form(s) — and eventually their fields — through a chain of Encrypt methods. The Encrypt method that takes in an IField instance as an argument encrypts the instance’s field name and value, and returns a new instance of the WFFMField class using the encrypted data and the other properties on the IField instance untouched.

Similarly, a chain of Decrypt methods are called for form(s) being retrieved from the inner Data Provider — field names and values are decrypted and saved into a new instance of the WFFMField class, and the manipulated form(s) are returned.

I want to point out that the IEncryptor instance is actually an instance of DataNullTerminatorEncryptor — see my earlier post on data encryption to see how this is implemented — which decorates our RC2Encryptor. This decorating encryptor stamps encrypted strings with a special string so we don’t accidentally encrypt a value twice, and it also won’t try to decrypt a string value that isn’t encrypted.

I added a new include configuration file to hold encryption related settings — the IEncryptor’s key, and the string that will be put at the end of all encrypted data via the DataNullTerminatorEncryptor instance:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <!-- TODO: change the terminator so it does not scream "PLEASE TRY TO CRACK ME!" -->
      <setting name="WFFM.Encryption.DataNullTerminator" value="#I_AM_ENCRYPTED#" />

      <!-- I found this key somewhere on the internet, so it must be secure -->
      <setting name="WFFM.Encryption.Key" value="88bca90e90875a" />
    </settings>
  </sitecore>
</configuration>

I then hooked in the encryption WFFM Data Provider in \App_Config\Include\forms.config, and set the type for the inner provider:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
  <sitecore>
  
	<!-- There is stuff up here -->
  
	<!-- MS SQL -->
	<formsDataProvider type="Sitecore.Sandbox.WFFM.Forms.Data.DataProviders.WFFMEncryptionDataProvider, Sitecore.Sandbox">
		<!-- No, this is not my real connection string -->
		<param desc="connection string">user id=(user);password=(password);Data Source=(database)</param>
		<param desc="inner provider">Sitecore.Forms.Data.DataProviders.WFMDataProvider, Sitecore.Forms.Core</param>
	</formsDataProvider>

	<!-- There is stuff down here -->
	
	</sitecore>
</configuration>

Let’s see this in action.

I created a new WFFM form with some fields for testing:

the-form-in-sitecore

I then mapped the above form to a new page in Sitecore, and published both the form and page.

I navigated to the form page, and filled it out:

the-form-page

After clicking submit, I was given a ‘thank you’ page’:

the-form-page-confirmation

Let’s see what our field data looks like in the WFFM database:

the-form-submission-encrypted-db

As you can see, the data is encrypted.

Now, let’s see if the data is also encrypted in the Forms Report for our test form:

forms-report-encryption

As you can see, the end-user would be unaware that any data manipulation is happening under the hood.

If you have any thoughts on this, please leave a comment.

Manipulate Field Values in a Custom Sitecore Web Forms for Marketers DataProvider

In my Experiments with Field Data Encryption in Sitecore article, I briefly mentioned designing a custom Web Forms for Marketers (WFFM) DataProvider that uses the decorator pattern for encrypting and decrypting field values before saving and retrieving field values from the WFFM database.

From the time I penned that article up until now, I have been feeling a bit guilty that I may have left you hanging by not going into the mechanics around how I did that — I built that DataProvider for my company to be used in one of our healthcare specific content management modules.

To make up for not showing you this, I decided to build another custom WFFM DataProvider — one that will replace periods with smiley faces and the word “Sitecore” with “SitecoreĀ®”, case insensitively.

First, I defined an interface for utility classes that will manipulate objects for us. All manipulators will consume an object of a specified type, manipulate that object in some way, and then return the maniputed object to the manipulator object’s client:

namespace Sitecore.Sandbox.Utilities.Manipulators.Base
{
    public interface IManipulator<T>
    {
        T Manipulate(T source);
    }
}

The first manipulator I built is a string manipulator. Basically, this object will take in a string and replace a specified substring with another:

namespace Sitecore.Sandbox.Utilities.Manipulators.Base
{
    public interface IStringReplacementManipulator : IManipulator<string>
    {
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

using Sitecore.Diagnostics;

using Sitecore.Sandbox.Utilities.Manipulators.Base;

namespace Sitecore.Sandbox.Utilities.Manipulators
{
    public class StringReplacementManipulator : IStringReplacementManipulator
    {
        private string PatternToReplace { get; set; }
        private string ReplacementString { get; set; }
        private bool IgnoreCase { get; set; }

        private StringReplacementManipulator(string patternToReplace, string replacementString)
            : this(patternToReplace, replacementString, false)
        {
        }

        private StringReplacementManipulator(string patternToReplace, string replacementString, bool ignoreCase)
        {
            SetPatternToReplace(patternToReplace);
            SetReplacementString(replacementString);
            SetIgnoreCase(ignoreCase);
        }

        private void SetPatternToReplace(string patternToReplace)
        {
            Assert.ArgumentNotNullOrEmpty(patternToReplace, "patternToReplace");
            PatternToReplace = patternToReplace;
        }

        private void SetReplacementString(string replacementString)
        {
            ReplacementString = replacementString;
        }

        private void SetIgnoreCase(bool ignoreCase)
        {
            IgnoreCase = ignoreCase;
        }

        public string Manipulate(string source)
        {
            Assert.ArgumentNotNullOrEmpty(source, "source");
            RegexOptions regexOptions = RegexOptions.None;

            if (IgnoreCase)
            {
                regexOptions = RegexOptions.IgnoreCase;
            }

            return Regex.Replace(source, PatternToReplace, ReplacementString, regexOptions);
        }

        public static IStringReplacementManipulator CreateNewStringReplacementManipulator(string patternToReplace, string replacementString)
        {
            return new StringReplacementManipulator(patternToReplace, replacementString);
        }

        public static IStringReplacementManipulator CreateNewStringReplacementManipulator(string patternToReplace, string replacementString, bool ignoreCase)
        {
            return new StringReplacementManipulator(patternToReplace, replacementString, ignoreCase);
        }
    }
}

Clients of this class can choose to have substrings replaced in a case sensitive or insensitive manner.

By experimenting in a custom WFFM DataProvider and investigating code via .NET reflector in the WFFM assemblies, I discovered I had to create a custom object that implements Sitecore.Forms.Data.IField.

Out of the box, WFFM uses Sitecore.Forms.Data.DefiniteField — a class that implements Sitecore.Forms.Data.IField, albeit this class is declared internal and cannot be reused outside of the Sitecore.Forms.Core.dll assembly.

When I attempted to change the Value property of this object in a custom WFFM DataProvider, changes did not stick for some reason — a reason that I have not definitely ascertained.

However, to get around these lost Value property changes, I created a custom object that implements Sitecore.Forms.Data.IField:

using System;

using Sitecore.Forms.Data;

namespace Sitecore.Sandbox.Utilities.Manipulators.DTO
{
    public class WFFMField : IField
    {
        public string Data { get; set; }

        public Guid FieldId { get; set; }

        public string FieldName { get; set; }

        public IForm Form { get; set; }

        public Guid Id { get; internal set; }

        public string Value { get; set; }
    }
}

Next, I built a WFFM Field collection manipulator — an IEnumerable of Sitecore.Forms.Data.IField defined in Sitecore.Forms.Core.dll — using the DTO defined above:

using System.Collections.Generic;

using Sitecore.Forms.Data;

namespace Sitecore.Sandbox.Utilities.Manipulators.Base
{
    public interface IWFFMFieldsManipulator : IManipulator<IEnumerable<IField>>
    {
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Diagnostics;
using Sitecore.Forms.Data;

using Sitecore.Sandbox.Utilities.Manipulators.Base;
using Sitecore.Sandbox.Utilities.Manipulators.DTO;

namespace Sitecore.Sandbox.Utilities.Manipulators
{
    public class WFFMFieldsManipulator : IWFFMFieldsManipulator
    {
        private IEnumerable<IStringReplacementManipulator> FieldValueManipulators { get; set; }

        private WFFMFieldsManipulator(params IStringReplacementManipulator[] fieldValueManipulator)
            : this(fieldValueManipulator.AsEnumerable())
        {
        }

        private WFFMFieldsManipulator(IEnumerable<IStringReplacementManipulator> fieldValueManipulators)
        {
            SetFieldValueManipulator(fieldValueManipulators);
        }

        private void SetFieldValueManipulator(IEnumerable<IStringReplacementManipulator> fieldValueManipulators)
        {
            Assert.ArgumentNotNull(fieldValueManipulators, "fieldValueManipulators");
            foreach (IStringReplacementManipulator fieldValueManipulator in fieldValueManipulators)
            {
                Assert.ArgumentNotNull(fieldValueManipulator, "fieldValueManipulator");
            }

            FieldValueManipulators = fieldValueManipulators;
        }

        public IEnumerable<IField> Manipulate(IEnumerable<IField> fields)
        {
            IList<IField> maniuplatdFields = new List<IField>();

            foreach (IField field in fields)
            {
                maniuplatdFields.Add(MainpulateFieldValue(field));
            }

            return maniuplatdFields;
        }

        private IField MainpulateFieldValue(IField field)
        {
            IField maniuplatedField = CreateNewWFFMField(field);

            if (maniuplatedField != null)
            {
                maniuplatedField.Value = ManipulateString(maniuplatedField.Value);
            }

            return maniuplatedField;
        }

        private static IField CreateNewWFFMField(IField field)
        {
            if(field != null)
            {
                return new WFFMField
                {
                    Data = field.Data,
                    FieldId = field.FieldId,
                    FieldName = field.FieldName,
                    Form = field.Form,
                    Id = field.Id,
                    Value = field.Value
                };
            }

            return null;
        }

        private string ManipulateString(string stringToManipulate)
        {
            if (string.IsNullOrEmpty(stringToManipulate))
            {
                return string.Empty;
            }

            string manipulatedString = stringToManipulate;

            foreach(IStringReplacementManipulator fieldValueManipulator in FieldValueManipulators)
            {
                manipulatedString = fieldValueManipulator.Manipulate(manipulatedString);
            }

            return manipulatedString;
        }

        public static IWFFMFieldsManipulator CreateNewWFFMFieldsManipulator(params IStringReplacementManipulator[] fieldValueManipulators)
        {
            return new WFFMFieldsManipulator(fieldValueManipulators);
        }

        public static IWFFMFieldsManipulator CreateNewWFFMFieldsManipulator(IEnumerable<IStringReplacementManipulator> fieldValueManipulators)
        {
            return new WFFMFieldsManipulator(fieldValueManipulators);
        }
    }
}

This manipulator consumes a collection of string manipulators and delegates to these for making changes to WFFM field values.

Now, it’s time to create a custom WFFM DataProvider that uses our manipulators defined above.

In my local sandbox Sitecore instance, I’m using SQLite for my WFFM module, and must decorate an instance of Sitecore.Forms.Data.DataProviders.SQLite.SQLiteWFMDataProvider — although this approach would be the same using MS SQL or Oracle since all WFFM DataProviders should inherit from the abstract class Sitecore.Forms.Data.DataProviders.WFMDataProviderBase:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Diagnostics;
using Sitecore.Forms.Data;
using Sitecore.Forms.Data.DataProviders;
using Sitecore.Forms.Data.DataProviders.SQLite;

using Sitecore.Sandbox.Translation.Base;
using Sitecore.Sandbox.Utilities.Manipulators.Base;
using Sitecore.Sandbox.Utilities.Manipulators;

namespace Sitecore.Sandbox.WFFM.Data.DataProviders
{
    public class FieldValueManipulationSQLiteWFMDataProvider : WFMDataProviderBase
    {
        private static readonly IStringReplacementManipulator RegisteredTrademarkManipulator = StringReplacementManipulator.CreateNewStringReplacementManipulator("sitecore", " SitecoreĀ®", true);
        private static readonly IStringReplacementManipulator PeriodsToSmiliesManipulator = StringReplacementManipulator.CreateNewStringReplacementManipulator("\\.", " :)");
        private static readonly IWFFMFieldsManipulator FieldsManipulator = WFFMFieldsManipulator.CreateNewWFFMFieldsManipulator(RegisteredTrademarkManipulator, PeriodsToSmiliesManipulator);

	    private WFMDataProviderBase InnerProvider { get; set; }

        public FieldValueManipulationSQLiteWFMDataProvider() 
		    : this(CreateNewSQLiteWFMDataProvider())
        {
        }

        public FieldValueManipulationSQLiteWFMDataProvider(string connectionString)
		    : this(CreateNewSQLiteWFMDataProvider(connectionString))
        {
        }

        public FieldValueManipulationSQLiteWFMDataProvider(WFMDataProviderBase innerProvider)
        {
		    SetInnerProvider(innerProvider);
	    }
	
	    private void SetInnerProvider(WFMDataProviderBase innerProvider)
	    {
		    Assert.ArgumentNotNull(innerProvider, "innerProvider");
		    InnerProvider = innerProvider;
	    }

        private static WFMDataProviderBase CreateNewSQLiteWFMDataProvider()
	    {
            return new SQLiteWFMDataProvider();
	    }

        private static WFMDataProviderBase CreateNewSQLiteWFMDataProvider(string connectionString)
	    {
		    Assert.ArgumentNotNullOrEmpty(connectionString, "connectionString");
            return new SQLiteWFMDataProvider(connectionString);
	    }

        public override void ChangeStorage(Guid formItemId, string newStorage)
        {
            InnerProvider.ChangeStorage(formItemId, newStorage);
        }

        public override void ChangeStorageForForms(IEnumerable<Guid> ids, string storageName)
        {
            InnerProvider.ChangeStorageForForms(ids, storageName);
        }

        public override void DeleteForms(IEnumerable<Guid> formSubmitIds)
        {
            InnerProvider.DeleteForms(formSubmitIds);
        }

        public override void DeleteForms(Guid formItemId, string storageName)
        {
            InnerProvider.DeleteForms(formItemId, storageName);
        }

        public override IEnumerable<IPool> GetAbundantPools(Guid fieldId, int top, out int total)
        {
            return InnerProvider.GetAbundantPools(fieldId, top, out total);
        }

        public override IEnumerable<IForm> GetForms(QueryParams queryParams, out int total)
        {
            return InnerProvider.GetForms(queryParams, out total);
        }

        public override IEnumerable<IForm> GetFormsByIds(IEnumerable<Guid> ids)
        {
            return InnerProvider.GetFormsByIds(ids);
        }

        public override int GetFormsCount(Guid formItemId, string storageName, string filter)
        {
            return InnerProvider.GetFormsCount(formItemId, storageName, filter);
        }

        public override IEnumerable<IPool> GetPools(Guid fieldId)
        {
            return InnerProvider.GetPools(fieldId);
        }

        public override void InsertForm(IForm form)
        {
            ManipulateFields(form);
            InnerProvider.InsertForm(form);
        }

        public override void ResetPool(Guid fieldId)
        {
            InnerProvider.ResetPool(fieldId);
        }

        public override IForm SelectSingleForm(Guid fieldId, string likeValue)
        {
            return InnerProvider.SelectSingleForm(fieldId, likeValue);
        }

        public override bool UpdateForm(IForm form)
        {
            ManipulateFields(form);
            return InnerProvider.UpdateForm(form);
        }

        private static void ManipulateFields(IForm form)
        {
            Assert.ArgumentNotNull(form, "form");
            Assert.ArgumentNotNull(form.Field, "form.Field");
            form.Field = FieldsManipulator.Manipulate(form.Field);
        }
    }
}

This custom DataProvider creates an instance of Sitecore.Forms.Data.DataProviders.SQLite.SQLiteWFMDataProvider and delegates method calls to it.

However, before delegating to insert and update method calls, forms fields are manipulated via our manipulators objects — our manipulators will replace periods with smiley faces, and the word “Sitecore” with “SitecoreĀ®”.

I then had to configure WFFM to use my custom DataProvider above in /App_Config/Include/forms.config:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
	<sitecore>
	
		<!-- A bunch of stuff here -->
		
		<!-- SQLite -->
		<formsDataProvider type="Sitecore.Sandbox.WFFM.Data.DataProviders.FieldValueManipulationSQLiteWFMDataProvider,Sitecore.Sandbox">
			<param desc="connection string">Data Source=/data/sitecore_webforms.db;version=3;BinaryGUID=true</param>
		</formsDataProvider>

		<!-- A bunch of stuff here -->
	
	</sitecore>
</configuration>

For testing, I build a random WFFM form containing three fields, and created a page item to hold this form.

I then navigated to my form page and filled it in:

random-form-before-sumbit

I then clicked the Submit button:

random-form-after-submit

I then opened up the Form Reports for my form:

random-form-reports

As you can see, it all gelled together nicely. šŸ™‚

Experiments with Field Data Encryption in Sitecore

Last week, I came up with a design employing the decorator pattern for encrypting and decrypting Web Forms for Marketers form data. My design encapsulates and decorates an instance of Sitecore.Forms.Data.DataProviders.WFMDataProvider — the main data provider that lives in the Sitecore.Forms.Core assembly and is used by the module for saving/retrieving form data from its database — by extending and implementing its base class and interface, respectively, and delegates requests to its public methods using the same method signatures. The “decoration” part occurs before before form data is inserted and updated in the database, and decrypted after being retrieved.

Once I completed this design, I began to ponder whether it would be possible to take this one step further and encrypt/decrypt all field data in Sitecore as a whole.

I knew I would eventually have to do some research to see if this were possible. However, I decided to begin my encryption ventures by building classes that encrypt/decrypt data.

I envisioned stockpiling an arsenal of encryption algorithms. I imagined all encryption algorithms having the same public methods to encrypt and decrypt strings of data, so I created the following interface:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Sitecore.Sandbox.Security.Encryption.Base
{
    public interface IEncryptor
    {
        string Encrypt(string input);

        string Decrypt(string input);
    }
}

Since I am lightyears away from being a cryptography expert — or even considering myself a novice — I had to go fishing on the internet to find an encryption utility class in .NET. I found the following algorithm at http://www.deltasblog.co.uk/code-snippets/basic-encryptiondecryption-c/, and put it within a class that adheres to my IEncryptor interface above:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

using Sitecore.Diagnostics;

using Sitecore.Sandbox.Security.Encryption.Base;

namespace Sitecore.Sandbox.Security.Encryption
{
    // retrieved from http://www.deltasblog.co.uk/code-snippets/basic-encryptiondecryption-c/

    public class TripleDESEncryptor : IEncryptor
    {
        private string Key { get; set; }

        private TripleDESEncryptor(string key)
        {
            SetKey(key);
        }

        private void SetKey(string key)
        {
            Assert.ArgumentNotNullOrEmpty(key, "key");
            Key = key;
        }

        public string Encrypt(string input)
        {
            return Encrypt(input, Key);
        }

        public static string Encrypt(string input, string key)
        {
            byte[] inputArray = UTF8Encoding.UTF8.GetBytes(input);
            TripleDESCryptoServiceProvider tripleDES = new TripleDESCryptoServiceProvider();
            tripleDES.Key = UTF8Encoding.UTF8.GetBytes(key);
            tripleDES.Mode = CipherMode.ECB;
            tripleDES.Padding = PaddingMode.PKCS7;
            ICryptoTransform cTransform = tripleDES.CreateEncryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(inputArray, 0, inputArray.Length);
            tripleDES.Clear();
            return System.Convert.ToBase64String(resultArray, 0, resultArray.Length);
        }

        public string Decrypt(string input)
        {
            return Decrypt(input, Key);
        }

        public static string Decrypt(string input, string key)
        {
            byte[] inputArray = System.Convert.FromBase64String(input);
            TripleDESCryptoServiceProvider tripleDES = new TripleDESCryptoServiceProvider();
            tripleDES.Key = UTF8Encoding.UTF8.GetBytes(key);
            tripleDES.Mode = CipherMode.ECB;
            tripleDES.Padding = PaddingMode.PKCS7;
            ICryptoTransform cTransform = tripleDES.CreateDecryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(inputArray, 0, inputArray.Length);
            tripleDES.Clear();
            return UTF8Encoding.UTF8.GetString(resultArray);
        }

        public static IEncryptor CreateNewTripleDESEncryptor(string key)
        {
            return new TripleDESEncryptor(key);
        }
    }
}

I then started wondering how I could ascertain whether data needed to be encrypted or decrypted. In other words, how could I prevent encrypting data that is already encrypted and, conversely, not decrypting data that isn’t encrypted?

I came up with building a decorator class that looks for a string terminator — a substring at the end of a string — and only encrypts the passed string when this terminating substring is not present, or decrypts only when this substring is found at the end of the string.

I decided to use a data transfer object (DTO) for my encryptor decorator, just in case I ever decide to use this same decorator with any encryption class that implements my encryptor interface. I also send in the encryption key and string terminator via this DTO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Sandbox.Security.Encryption.Base;

namespace Sitecore.Sandbox.Security.DTO
{
    public class DataNullTerminatorEncryptorSettings
    {
        public string EncryptionDataNullTerminator { get; set; }
        public string EncryptionKey { get; set; }
        public IEncryptor Encryptor { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Diagnostics;

using Sitecore.Sandbox.Security.DTO;
using Sitecore.Sandbox.Security.Encryption.Base;

namespace Sitecore.Sandbox.Security.Encryption
{
    public class DataNullTerminatorEncryptor : IEncryptor
    {
        private DataNullTerminatorEncryptorSettings DataNullTerminatorEncryptorSettings { get; set; }

        private DataNullTerminatorEncryptor(DataNullTerminatorEncryptorSettings dataNullTerminatorEncryptorSettings)
        {
            SetDataNullTerminatorEncryptorSettings(dataNullTerminatorEncryptorSettings);
        }

        private void SetDataNullTerminatorEncryptorSettings(DataNullTerminatorEncryptorSettings dataNullTerminatorEncryptorSettings)
        {
            Assert.ArgumentNotNull(dataNullTerminatorEncryptorSettings, "dataNullTerminatorEncryptorSettings");
            Assert.ArgumentNotNullOrEmpty(dataNullTerminatorEncryptorSettings.EncryptionDataNullTerminator, "dataNullTerminatorEncryptorSettings.EncryptionDataNullTerminator");
            Assert.ArgumentNotNullOrEmpty(dataNullTerminatorEncryptorSettings.EncryptionKey, "dataNullTerminatorEncryptorSettings.EncryptionKey");
            Assert.ArgumentNotNull(dataNullTerminatorEncryptorSettings.Encryptor, "dataNullTerminatorEncryptorSettings.Encryptor");
            DataNullTerminatorEncryptorSettings = dataNullTerminatorEncryptorSettings;
        }

        public string Encrypt(string input)
        {
            if (!IsEncrypted(input))
            {
                string encryptedInput = DataNullTerminatorEncryptorSettings.Encryptor.Encrypt(input);
                return string.Concat(encryptedInput, DataNullTerminatorEncryptorSettings.EncryptionDataNullTerminator);
            }

            return input;
        }

        public string Decrypt(string input)
        {
            if (IsEncrypted(input))
            {
                input = input.Replace(DataNullTerminatorEncryptorSettings.EncryptionDataNullTerminator, string.Empty);
                return DataNullTerminatorEncryptorSettings.Encryptor.Decrypt(input);
            }

            return input;
        }

        private bool IsEncrypted(string input)
        {
            if (!string.IsNullOrEmpty(input))
                return input.EndsWith(DataNullTerminatorEncryptorSettings.EncryptionDataNullTerminator);

            return false;
        }

        public static IEncryptor CreateNewDataNullTerminatorEncryptor(DataNullTerminatorEncryptorSettings dataNullTerminatorEncryptorSettings)
        {
            return new DataNullTerminatorEncryptor(dataNullTerminatorEncryptorSettings);
        }
    }
}

Now that I have two encryptors — the TripleDESEncryptor and DataNullTerminatorEncryptor classes — I thought it would be prudent to create a factory class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Sandbox.Security.DTO;

namespace Sitecore.Sandbox.Security.Encryption.Base
{
    public interface IEncryptorFactory
    {
        IEncryptor CreateNewTripleDESEncryptor(string encryptionKey);

        IEncryptor CreateNewDataNullTerminatorEncryptor();

        IEncryptor CreateNewDataNullTerminatorEncryptor(string encryptionDataNullTerminator, string encryptionKey);

        IEncryptor CreateNewDataNullTerminatorEncryptor(DataNullTerminatorEncryptorSettings dataNullTerminatorEncryptorSettings);
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Configuration;

using Sitecore.Sandbox.Security.DTO;
using Sitecore.Sandbox.Security.Encryption.Base;

namespace Sitecore.Sandbox.Security.Encryption
{
    public class EncryptorFactory : IEncryptorFactory
    {
        private static volatile IEncryptorFactory _Current;
        private static object lockObject = new Object();

        public static IEncryptorFactory Current
        {
            get 
            {
                if (_Current == null) 
                {
                    lock (lockObject) 
                    {
                        if (_Current == null)
                        {
                            _Current = new EncryptorFactory();
                        }
                    }
                }

                return _Current;
            }
        }

        private EncryptorFactory()
        {
        }

        public IEncryptor CreateNewTripleDESEncryptor(string encryptionKey)
        {
            return TripleDESEncryptor.CreateNewTripleDESEncryptor(encryptionKey);
        }

        public IEncryptor CreateNewDataNullTerminatorEncryptor()
        {
            string encryptionDataNullTerminator = Settings.GetSetting("Encryption.DataNullTerminator");
            string encryptionKey = Settings.GetSetting("Encryption.Key");
            return CreateNewDataNullTerminatorEncryptor(encryptionDataNullTerminator, encryptionKey);
        }

        public IEncryptor CreateNewDataNullTerminatorEncryptor(string encryptionDataNullTerminator, string encryptionKey)
        {
            DataNullTerminatorEncryptorSettings dataNullTerminatorEncryptorSettings = new DataNullTerminatorEncryptorSettings 
            { 
                EncryptionDataNullTerminator = encryptionDataNullTerminator,
                EncryptionKey = encryptionDataNullTerminator,
                // we're going to use the TripleDESEncryptor by default
                Encryptor = CreateNewTripleDESEncryptor(encryptionKey)  
            };

            return CreateNewDataNullTerminatorEncryptor(dataNullTerminatorEncryptorSettings);
        }

        public IEncryptor CreateNewDataNullTerminatorEncryptor(DataNullTerminatorEncryptorSettings dataNullTerminatorEncryptorSettings)
        {
            return DataNullTerminatorEncryptor.CreateNewDataNullTerminatorEncryptor(dataNullTerminatorEncryptorSettings);
        }
    }
}

I decided to make my factory class be a singleton. I saw no need of having multiple instances of this factory object floating around in memory, and envisioned using this same factory object in a tool I wrote to encrypt all field data in all Sitecore databases. I will discuss this tool at the end of this article.

It’s now time to do some research to see whether I have the ability to hijack the main mechanism for saving/retrieving data from Sitecore.

While snooping around in my Web.config, I saw that the core, master and web databases use a data provider defined at configuration/sitecore/dataProviders/main:

<configuration>
	<sitecore database="SqlServer">

		<!-- More stuff here -->

		<dataProviders>
			<main type="Sitecore.Data.$(database).$(database)DataProvider, Sitecore.Kernel">
				<param connectionStringName="$(1)"/>
				<Name>$(1)</Name>
			</main>

			<!-- More stuff here -->

		<dataProviders>

			<!-- More stuff here -->

	</sitecore>
</configuration>

In my local instance, this points to the class Sitecore.Data.SqlServer.SqlServerDataProvider in Sitecore.Kernel.dll. I knew I had open up .NET Reflector and start investigating.

I discovered that there wasn’t much happening in this class at the field level. I also noticed this class used Sitecore.Data.DataProviders.Sql.SqlDataProvider as its base class, so I continued my research there.

I struck gold in the Sitecore.Data.DataProviders.Sql.SqlDataProvider class. Therein, I found methods that deal with saving/retrieving field data from an instance of the database utility class Sitecore.Data.DataProviders.Sql.SqlDataApi.

I knew this was the object I had to either decorate or override. Since most of the methods I needed to decorate with my encryption functionality were signed as protected, I had to subclass this class in order to get access to them. I then wrapped these methods with calls to my encryptor’s Encrypt(string input) and Decrypt(string input) methods:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

using Sitecore.Collections;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.DataProviders;
using Sitecore.Data.DataProviders.Sql;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;

using Sitecore.Sandbox.Security.Encryption;
using Sitecore.Sandbox.Security.Encryption.Base;

namespace Sitecore.Sandbox.Data.DataProviders.Sql
{
    public class EncryptionSqlDataProvider : SqlDataProvider
    {
        private static readonly IEncryptorFactory EncryptorBegetter = EncryptorFactory.Current;
        private static readonly IEncryptor Encryptor = EncryptorBegetter.CreateNewDataNullTerminatorEncryptor();

        public EncryptionSqlDataProvider(SqlDataApi sqlDataApi) 
            : base(sqlDataApi)
        {
        }

        public override FieldList GetItemFields(ItemDefinition itemDefinition, VersionUri versionUri, CallContext context)
        {
            FieldList fieldList = base.GetItemFields(itemDefinition, versionUri, context);

            if (fieldList == null)
                return null;

            FieldList fieldListDecrypted = new FieldList(); 
            foreach (KeyValuePair<ID, string> field in fieldList)
            {
                string decryptedValue = Decrypt(field.Value);
                fieldListDecrypted.Add(field.Key, decryptedValue);
            }

            return fieldListDecrypted;
        }

        protected override string GetFieldValue(string tableName, Guid entryId)
        {
            string value = base.GetFieldValue(tableName, entryId);
            return Decrypt(value);
        }

        protected override void SetFieldValue(string tableName, Guid entryId, string value)
        {
            string encryptedValue = Encrypt(value);
            base.SetFieldValue(tableName, entryId, encryptedValue);
        }

        protected override void WriteSharedField(ID itemId, FieldChange change, DateTime now, bool fieldsAreEmpty)
        {
            FieldChange unencryptedFieldChange = GetEncryptedFieldChange(itemId, change);
            base.WriteSharedField(itemId, unencryptedFieldChange, now, fieldsAreEmpty);
        }

        protected override void WriteUnversionedField(ID itemId, FieldChange change, DateTime now, bool fieldsAreEmpty)
        {
            FieldChange unencryptedFieldChange = GetEncryptedFieldChange(itemId, change);
            base.WriteUnversionedField(itemId, unencryptedFieldChange, now, fieldsAreEmpty);
        }

        protected override void WriteVersionedField(ID itemId, FieldChange change, DateTime now, bool fieldsAreEmpty)
        {
            FieldChange unencryptedFieldChange = GetEncryptedFieldChange(itemId, change);
            base.WriteVersionedField(itemId, unencryptedFieldChange, now, fieldsAreEmpty);
        }   

        private FieldChange GetEncryptedFieldChange(ID itemId, FieldChange unencryptedFieldChange)
        {
            if (!string.IsNullOrEmpty(unencryptedFieldChange.Value))
            {
                Field field = GetField(itemId, unencryptedFieldChange.FieldID);
                string encryptedValue = Encrypt(unencryptedFieldChange.Value);
                return new FieldChange(field, encryptedValue);
            }

            return unencryptedFieldChange;
        }

        private Field GetField(ID itemId, ID fieldID)
        {
            Item item = GetItem(itemId);
            return GetField(item, fieldID);
        }

        private Item GetItem(ID itemId)
        {
            return base.Database.Items[itemId];
        }

        private static Field GetField(Item owner, ID fieldID)
        {
            return new Field(fieldID, owner);
        }

        private static string Encrypt(string value)
        {
            return Encryptor.Encrypt(value);
        }

        private static string Decrypt(string value)
        {
            return Encryptor.Decrypt(value);
        }
    }
}

After creating my new EncryptionSqlDataProvider above, I had to define my own SqlServerDataProvider that inherits from it:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Data.SqlServer;

using Sitecore.Sandbox.Data.DataProviders.Sql;

namespace Sitecore.Sandbox.Data.SqlServer
{
    public class EncryptionSqlServerDataProvider : EncryptionSqlDataProvider
    {
        public EncryptionSqlServerDataProvider(string connectionString)
             : base(new SqlServerDataApi(connectionString))
        {
        }
    }
}

I put in a patch reference to my new EncryptionSqlServerDataProvider in a new config file (/App_Config/Include/EncryptionDatabaseProvider.config), and defined the settings used by my encryptors:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<dataProviders>
			<main type="Sitecore.Data.$(database).$(database)DataProvider, Sitecore.Kernel">
				<patch:attribute name="type">Sitecore.Sandbox.Data.$(database).Encryption$(database)DataProvider, Sitecore.Sandbox</patch:attribute>
		       </main>
		</dataProviders>
		<settings>
			<!-- must be 24 characters long -->
			<setting name="Encryption.Key" value="4rgjvqm234gswdfd045r3f4q" />
			
			<!-- this is added to the end of encrypted data in the database -->
			<setting name="Encryption.DataNullTerminator" value="%IS_ENCRYPTED%" />
		</settings>
	</sitecore>
</configuration>

Will the above work, or will I be retreating back to the whiteboard scratching my head while being in a state of bewilderment?. Let’s try out the above to see where my fate lies.

I inserted a new Sample Item under my Home node, entered some content into four fields, and then clicked the Save button:

encryption test item

I opened up my Sitecore master database in Microsoft SQL Server Management Studio — I don’t recommend this being a standard Sitecore development practice, although I am doing this here to see if my encryption scheme worked — to see if my field data was encrypted. It was encrypted successfully:

sql

I then published my item to the web database, and pulled up my new Sample Item page in a browser to make sure it works despite being encrypted in the database:

presentation

Hooray! It works!

You might be asking yourself: how would we go about encrypting “legacy” field data in all Sitecore databases? Well, I wrote tool — the reason for creating my EncryptorFactory above — yesterday to do this. It encrypted all field data in the core, master and web databases.

However, when I opened up the desktop view of the Sitecore client, many of my tray buttons disappeared. Seeing this has lead me to conclude it’s not a good idea to encrypt data in the core database.

Plus, after having rolled back my core database to its previous state — a state of being unencrypted — I tried to open up the Content Editor in the master database. I then received an exception stemming from the rules engine — apparently not all fields in both the master and web databases should be encrypted. There must be code outside of the main data provider accessing data in all three databases.

Future research is needed to discover which fields are good candidates for having their data encrypted, and which fields should be ignored.