Customizing Data Masking and Anonymization

Engage comes with features which helps processes to mask and anonymize any data deemed sensitive. This is done by a Data Masking (DM) and Anonymization framework that is built in the product. The built in DM framework helps process designers to mask data related to Credit Card (CC), Email, SSN and any string (which falls in the Default category) by using out of the box available masking patterns. The masking patterns for the built in masking strategy is as follows 

  1. Credit Card – Masks all but last four digits.  E.g. 1334-1234-1234-1234 is masked as xxxx-xxxxx-xxxx-1234
  2. Email – Masks alternate characters in the email address and masks all of domain name. E.g. sony@gmail.com is masked as sxnx@xxxx.com
  3. SSN – masks all but last four characters. E.g. - 123-12-1234 is masked as xxx-xx-1234
  4. Default – masks alternate characters. E.g. Hello, world is masked as Hxlx0x xoxlx. Note: space is treated as a character and is masked as well.

 

As is the wont of frameworks, DM is flexible enough to add new masking strategies or even modify existing strategy patterns or even to replace the masking character (‘x’). Here is a guide how to go about implementing custom masking framework.

  

Customize Data Masking by implementing the interface

DM in AE is implemented using an interface IDataMaskAdapter. The library that implements this is placed in \\Plugins\DataMasking\ folder. This is applicable to Studio, Engage and robot. Customized library needs do the same. Implement the interface and replace existing library in the above mentioned folder.

 

To implement custom DM, implement the IDataMaskAdapter available in Utilities.Interface library.  A reference to MaskingPattern object available in Utilities.Models.Studio also needs to be added.

  

Find attached an implementation of the above interface.

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Utilities.Interfaces.DataMasking;
using Utilities.Models.Studio;

namespace CustomMaskingUsingInterface
{
    public class CustomMasking : IDataMaskAdapter
    {
        protected Dictionary<String, IMask> _dataMaskStrategies;
        protected List<MaskingPattern> _maskingPatterns;

        public const string CreditCard = "CC";
        public const string Email = "EMAIL";
        public const string SSN = "SSN";
        public const string Default = "Default";
        public const string Phone = "PH";

        public List<MaskingPattern> MaskingPatterns { get { return _maskingPatterns; } }

        public CustomMasking()
        {
            //Adding Masking patterns to list
            AddDataMaskingPatterns();
            //Adding masking pattern class to dictionary
            AddMaskingStrategies();
        }

        void AddDataMaskingPatterns()
        {
            _maskingPatterns = new List<MaskingPattern>();

            _maskingPatterns.Add(

            new MaskingPattern
            {
                Pattern = "Masks all but last four digits. E.g. xxxx-xxxx-xxxx-1234",
                Type = CreditCard
            });
            _maskingPatterns.Add(new MaskingPattern
            {
                Pattern = "Masks name and domain. E.g. abc@gmail.com becomes axc@xxxx.com",
                Type = Email
            });
            _maskingPatterns.Add(new MaskingPattern
            {
                Pattern = "Masks all but last four digits of SSN. E.g. 123-12-1234 becomes xxx-xx-1234",
                Type = SSN
            });
            _maskingPatterns.Add(new MaskingPattern
            {
                Pattern = "Masks alternate characters. E.g. example becomes exaxpxe",
                Type = Default
            });
            _maskingPatterns.Add(new MaskingPattern
            {
                Pattern = "Masks mid 4 characters. E.g. 9876543210 becomes 987xxxx210",
                Type = Phone
            });
        }

        void AddMaskingStrategies()
        {
            _dataMaskStrategies = new Dictionary<string, IMask>();

            _dataMaskStrategies.Add(CreditCard, new CCMasking());
            _dataMaskStrategies.Add(Email, new EmailMasking());
            _dataMaskStrategies.Add(SSN, new SSNMasking());
            _dataMaskStrategies.Add(Default, new DefaultMask());
            _dataMaskStrategies.Add(Phone, new PhoneMasking());
        }

        public string Mask(string dataMaskType, string strToMask) =>
            _dataMaskStrategies[dataMaskType].Mask(strToMask);
    }
    //Defining an Interface
    public interface IMask
    {
        string Mask(string strToMask);
    }

    //Creating a abstract class which inherits IMask interface
    public abstract class BaseStrategy : IMask
    {
        //Masking character
        protected char MaskChar = 'x';
        public abstract string Mask(string mask);
    }

    //Mask credit card
    public class CCMasking : BaseStrategy
    {
        /// <summary>
        /// Masks all but last four characters and hyphen
        /// Input e.g. 1111-1111-1111-1111
        /// Ouput xxxx-xxxx-xxxx-1111
        /// Input 1234-123456-1234 
        /// Output xxxx-xxxxxx-1234
        /// </summary>
        public override string Mask(string ccNumber)
        {
            if (ccNumber.Length < 4)
                throw new ArgumentException($"Length of credit card number {ccNumber.Length}, is incorrect");

            var unMask = ccNumber.Substring(ccNumber.Length - 4, 4);//get last four digits
            ccNumber = ccNumber.Substring(0, ccNumber.Length - 4); //rest of the characters
            StringBuilder maskedStr = new StringBuilder();

            for (int i = 0; i < ccNumber.Length; i++)
            {
                maskedStr.Append(ccNumber[i] == '-' ? '-' : base.MaskChar);//mask all char with 'x' unless it is a hyphen used for separation
            }
            return maskedStr.ToString() + unMask;
        }
    }

    //Mask Email
    public class EmailMasking : BaseStrategy
    {
        /// <summary>
        /// Input e.g. abc@xyx.com
        /// Output axc@xxx.com
        /// </summary>
        public override string Mask(string email)
        {
            //input abc@xyz.com
            var indexofat = email.IndexOf('@');

            var name = new DefaultMask().Mask(email.Substring(0, indexofat)); //output abc            
            var domain = email.Substring(indexofat + 1, email.Length - indexofat - 1); //output xyz.com
            domain = domain.Substring(0, domain.IndexOf('.')); //output xyz
            return name + '@' + new String(base.MaskChar, domain.Length) + ".com"; //new string line of code masks entire domain name. gmail will become xxxx //output axc@xxx.com
        }
    }

    //Default masking. i.e alternative character masking
    public class DefaultMask : BaseStrategy
    {
        /// <summary>
        /// Input e.g abcd
        /// Output axcx
        /// </summary>
        public override string Mask(string s)
        {
            StringBuilder mask = new StringBuilder();
            for (int i = 0; i < s.Length; i++)
            {
                mask.Append(i % 2 == 0 ? s[i] : base.MaskChar);
            }
            return mask.ToString();
        }
    }

    //SSN Masking
    public class SSNMasking : BaseStrategy
    {
        public override string Mask(string ssn)
        {
            var ssnPattern = @"^\d{3}-\d{2}-\d{4}$";

            if (!Regex.IsMatch(ssn, ssnPattern)) throw new ArgumentException($"Invalid SSN format {ssn}");

            var toMask = ssn.Substring(0, 7);
            var unMask = ssn.Substring(7, 4);

            StringBuilder maskedStr = new StringBuilder();

            for (int i = 0; i < toMask.Length; i++)
            {
                maskedStr.Append(toMask[i] == '-' ? '-' : base.MaskChar);
            }

            return maskedStr.ToString() + unMask;
        }
    }

    //Phone Number Masking
    public class PhoneMasking : BaseStrategy
    {
        /// <summary>
        /// Input e.g. 9876543210
        /// Output 987xxxx210
        /// </summary>
        public override string Mask(string phNumber)
        {
            if (phNumber.Length != 10)
                throw new ArgumentException($"Length of phone number {phNumber.Length}, is incorrect");

            var mask = phNumber.Substring(3, 4);//get mid four digits
            string first = phNumber.Substring(0, 3); //get first three digit
            string last = phNumber.Substring(0, 3); //get last three digit
            StringBuilder maskedStr = new StringBuilder();

            for (int i = 0; i < mask.Length; i++)
            {
                maskedStr.Append(base.MaskChar);//mask all char with 'x'
            }
            return first + maskedStr.ToString() + last;
        }
    }
}

Customize Data Masking extending the existing implementation

Class diagram of the default implementation. 

  

  

Inherit DataMaskAdapter and override existing properties/methods to extend the custom implementation. Add new strategies to _dataMaskStrategies and masking patterns to _maskingPatterns dictionaries. Call the new implementations from the Mask method. Custom written newer strategies need not follow the existing approach where they implement IMask interface.

 

Find a sample attached which adds a new Phone Number masking to the existing strategies.

 

using EV.AE.DataMaskingPlugin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Utilities.Models.Studio;

namespace CustomMaskingUsingAdapter
{
    public class CustomMasking : DataMaskAdapter
    {
        public const string Phone = "Phone";
        public override List<MaskingPattern> MaskingPatterns => base.MaskingPatterns;//This returns existing masking pattern list from Adapter class
        private Dictionary<String, IMask> DataMaskStrategies => base._dataMaskStrategies;//This returns existing masking dictionary from Adapter class
        public CustomMasking()
        {
            //Adding new masking to the list
            MaskingPatterns.Add(new MaskingPattern
            {
                Pattern = "Masks mid 4 digits. E.g. 987xxxx210",
                Type = Phone
            });
            //Adding new masking class to the dictionary
            DataMaskStrategies.Add(Phone, new PhoneMasking());            
        }
        public override string Mask(string dataMaskType, string strToMask) =>
            _dataMaskStrategies[dataMaskType].Mask(strToMask);
    }
    //Mask Phone Number
    public class PhoneMasking : BaseStrategy
    {
        /// <summary>
        /// Input e.g. 9876543210
        /// Output 987xxxx210
        /// </summary>
        public override string Mask(string phNumber)
        {
            if (phNumber.Length != 10)
                throw new ArgumentException($"Length of phone number {phNumber.Length}, is incorrect");

            base.MaskChar = '*';
            var mask = phNumber.Substring(3, 4);//get mid four digits
            string first = phNumber.Substring(0, 3); //get first three digit
            string last = phNumber.Substring(0, 3); //get last three digit
            StringBuilder maskedStr = new StringBuilder();

            for (int i = 0; i < mask.Length; i++)
            {
                maskedStr.Append(base.MaskChar);//mask all char with 'x'
            }
            return first + maskedStr.ToString() + last;
        }
    }
}

 

NOTE:

Before replacing the existing library, move it into another folder outside plugins. This way application will be able to load modified as well as existing masking patterns. 

  

Customize Anonymization by implementing the interface

Data anonymization in Engage is implemented using an interface IDataAnonymizer. The library that implements this is placed in \\Plugins\ DataAnonymizer\ folder. This is applicable to AutomationStudio, Engage and robot. Customized library needs do the same. Implement the interface and replace existing library in the above mentioned folder. The library shipped anonymizes the input using SHA512 algorithm.

 

To implement custom Data Anonymization, implement the IDataAnonymizer available in Utilities.Interface library.