November Happy Hour will be moved to Thursday December 5th.

Encrypting EPiServer Forms Submission Data

Vote:
 

Hi All

I've created an "EncryptedTextElementBlock" that inherits from the TextElementBlock. I've got a delegate registered for the FormsSubmitting event that checks for these fields and encrypts the data as it is saved.

My issue is I can't find a way to hook into the bit where EPiServer reads the data back, so the administrators will see garbled text in the form submissions view/export view. Does anybody know of a way of rendering the decrypted data in these views?

Thanks

#150965
Jul 05, 2016 16:42
Vote:
 

version please?
/K

#150968
Jul 05, 2016 16:51
Vote:
 

EPiServer v9.10.2

EPiServer Forms v2.1.0.2

#150969
Jul 05, 2016 16:52
Vote:
 

Although I see v3 has literally just been released, so may upgrade to that

#150971
Jul 05, 2016 17:00
Vote:
 

In Settings Tab, You will see a tab with name "WebHook URL", that can be used.

Regards

/K

#150972
Jul 05, 2016 17:05
Vote:
 

Thanks for the reply but that's not really what I'm after. I don't need to post JSON anywhere, I need to override the rendering of the form submissions in episerver itself.

#150974
Jul 05, 2016 17:09
Vote:
 

Only supported events are 
FormsSubmitting //Before each step, at least once
FormsStepSubmitted //After each step, at least once
FormsSubmissionFinalized //When final step is posted
FormsStructureChange //When a Form Container Block is published

Although Customozied Actor can also be added but they will also not fill your requirement as you want to decrypt whenever reading taht data back. I am not expecting any new event in new release. Will be interested to know what solution you will get from EPiserver team.

Regards
/K

#150976
Jul 05, 2016 17:40
Vote:
 

I'll check it out tomorrow Tim :) Fun problem!

#150981
Jul 05, 2016 20:42
Vote:
 

Hey Tim!

I implemented it in a slightly different way. You can hopefully make it work for you. 

I created a new implementation of the IPermanentStorage that supports encrypting elements. Then just add your implementation to the container to let Episerver use it instead. An element can be marked for encryption by using an attribute. Threw in a simple encryption class as well. 

public class EncryptedFormsDataStorage : DdsPermanentStorage
    {
        private readonly ILogger _log = LogManager.GetLogger(typeof(EncryptedFormsDataStorage));
        private Injected<EPiServer.Forms.Core.Data.Internal.DdsStructureStorage> _formStructureStorage;
        private Injected<IContentLoader> _contentLoader;
        public override Guid SaveToStorage(FormIdentity formIden, Submission submission)
        {
            var cryptographyService = new CryptographyService();
            var form = formIden.GetFormBlock();
            var elements = form.ElementsArea.FilteredItems;
            foreach (var contentAreaItem in elements)
            {
                var elementblock = _contentLoader.Service.Get<ElementBlockBase>(contentAreaItem.ContentLink);
                if (ShouldEncryptElement(elementblock))
                {
                    var oldText = submission.Data[elementblock.FormElement.ElementName];
                    if (oldText != null)
                    {
                        submission.Data.Remove(elementblock.FormElement.ElementName);
                        var encryptedText = cryptographyService.Encrypt(oldText.ToString());
                        submission.Data.Add(elementblock.FormElement.ElementName, encryptedText);
                    }
                }
            }
            return base.SaveToStorage(formIden, submission);

        }
        public override IEnumerable<PropertyBag> LoadSubmissionFromStorage(FormIdentity formIden, DateTime beginDate, DateTime endDate, bool finalizeOnly = false)
        {
            try
            {
                var cryptographyService = new CryptographyService();
                var propertyBags = base.LoadSubmissionFromStorage(formIden, beginDate, endDate, finalizeOnly);
                var form = formIden.GetFormBlock();
                var elements = form.ElementsArea.FilteredItems;
                foreach (var contentAreaItem in elements)
                {
                    var elementblock = _contentLoader.Service.Get<ElementBlockBase>(contentAreaItem.ContentLink);
                    if (ShouldEncryptElement(elementblock))
                    {
                        foreach (var propertyBag in propertyBags)
                        {
                            var oldValue = propertyBag[elementblock.FormElement.ElementName];
                            if (oldValue != null)
                            {
                                string newValue;
                                if (cryptographyService.TryDecrypt(oldValue.ToString(), out newValue))
                                {
                                    _log.Information("Decrypted text");
                                    propertyBag.Remove(elementblock.FormElement.ElementName);
                                    propertyBag.Add(elementblock.FormElement.ElementName, newValue);
                                }
                                else
                                {
                                    _log.Error("Failed to decrypt element text");
                                }

                            }

                        }
                    }
                }
                return propertyBags;
            }
            catch (Exception ex)
            {
                _log.Error("Error while decrypting", ex);
                throw;
            }
        }
        private bool ShouldEncryptElement(ElementBlockBase element)
        {
            var encryptionAttribute = Attribute.GetCustomAttribute(element.GetType(),
            typeof(EncryptedFormsElementAttribute));
            if (encryptionAttribute == null)
            {
                _log.Information("Element lacks attribute for encryption");
            }
            else
            {
                _log.Information("Element has attribute for encryption");
                return true;
            }
            return false;
        }
    }

    [ContentType(GUID = "95A98808-4BFC-43FE-B723-4FD2F7E01234")]
    [EncryptedFormsElement]
    public class EncryptedTextBoxElementBlock : TextboxElementBlock
    {
    }

    public class EncryptedFormsElementAttribute : Attribute
    {

    }

    /// <summary>
    /// Remember to set crypto key before trying to encrypt / decrypt anything.
    /// </summary>
    public class CryptographyService
    {
        /// <summary>
        /// Array for password salt
        /// </summary>
        public byte[] PasswordKeyByteArray { get; set; }

        /// <summary>
        /// Set this to a tough to break string before trying to encrypt
        /// </summary>
        public string CryptoKey { get; set; }

        public CryptographyService()
        {
            CryptoKey = "ASDLKFJALSDKFJAKDFJ";
            PasswordKeyByteArray = new byte[] {0x4b, 0x49, 0xa1, 0x6e, 0x11, 0x4d,
            0x58, 0x45, 0x76, 0x61, 0x62, 0x45, 0xa5};
        }
        //Encrypt a byte array into a byte array using a key and an IV 
        public byte[] Encrypt(byte[] clearData, byte[] key, byte[] iv)
        {
            // Create a MemoryStream to accept the encrypted bytes 
            var ms = new MemoryStream();
            var alg = Rijndael.Create();
            alg.Key = key;
            alg.IV = iv;
            var cs = new CryptoStream(ms,
            alg.CreateEncryptor(), CryptoStreamMode.Write);
            cs.Write(clearData, 0, clearData.Length);
            cs.Close();
            byte[] encryptedData = ms.ToArray();
            return encryptedData;
        }

        public string Encrypt(string clearText)
        {
            if (string.IsNullOrEmpty(clearText))
            {
                return clearText;
            }
            byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);
            var pdb = new Rfc2898DeriveBytes(CryptoKey, PasswordKeyByteArray);
            byte[] encryptedData = Encrypt(clearBytes, pdb.GetBytes(32), pdb.GetBytes(16));
            return Convert.ToBase64String(encryptedData);

        }
        /// <summary>
        /// Given wrong key this may cast an exception of type CryptographicException with message "Padding is invalid and cannot be removed"
        /// Decrypt a byte array into a byte array using a key and an IV 
        /// Uses Decrypt(byte[], byte[], byte[]) 
        /// </summary>
        /// <returns></returns>
        public byte[] Decrypt(byte[] cipherData,
            byte[] key, byte[] iv)
        {
            var ms = new MemoryStream();
            var alg = Rijndael.Create();
            alg.Key = key;
            alg.IV = iv;
            var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write);
            cs.Write(cipherData, 0, cipherData.Length);
            cs.Close();
            byte[] decryptedData = ms.ToArray();

            return decryptedData;
        }
        /// <summary>
        /// Given wrong key this may cast an exception of type CryptographicException with message "Padding is invalid and cannot be removed"
        /// Decrypt a string into a string using a password 
        /// Uses Decrypt(byte[], byte[], byte[]) 
        /// </summary>
        /// <param name="cipherText"></param>
        /// <returns></returns>
        public bool TryDecrypt(string cipherText, out string result)
        {
            if (string.IsNullOrEmpty(cipherText))
            {
                result = cipherText;
                return false;
            }
            try
            {
                byte[] cipherBytes = Convert.FromBase64String(cipherText);
                var pdb = new Rfc2898DeriveBytes(CryptoKey, PasswordKeyByteArray);
                byte[] decryptedData = Decrypt(cipherBytes, pdb.GetBytes(32), pdb.GetBytes(16));
                result = Encoding.Unicode.GetString(decryptedData);
                return true;
            }
            catch (Exception)
            {
                result = null;
                return false;
            }
        }
    }

...and the view for the custom element:

@using EPiServer.Forms.Core
@using EPiServerSiteV9.Business
@model EPiServerSiteV9.Business.EncryptedTextBoxElementBlock

<div class="Form__Element FormTextbox" data-epiforms-element-name="@Model.FormElement.ElementName">
    <label for="@Model.FormElement.Guid">
        @{
            var label = Model.FormElement.SourceContent.Property["Label"];
        }
        @label
    </label>
    <input type="text" name="@Model.FormElement.ElementName" class="FormTextbox__Input" id="@Model.FormElement.Guid" />


    <span data-epiforms-linked-name="@Model.FormElement.ElementName" class="Form__Element__ValidationError" style="display: none;">*</span>
</div>

...and the registration of the new storage:

....
 private static void ConfigureContainer(ConfigurationExpression container)
        {
            container.For<IPermanentStorage>().Use<EncryptedFormsDataStorage>();
#151032
Jul 07, 2016 12:39
Vote:
 

Daniel,

Wow thank you, I didn't expect a full working solution. This works perfectly, thanks very much.

#151048
Jul 08, 2016 10:45
Vote:
 

Daniel, I am in your fan club, after this solution :)

#151054
Edited, Jul 08, 2016 16:41
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.