We need to integrate with a third party, who have decided that implementing a federated trust is too complex and thus have just given us a user name and password.
Since the integration is done via a couple of plug-ins and custom activities we've decided to store the password in Dynamics CRM, the only problem is that there is no out of the box way of storing the passwords that would allow a relatively simple automated deployment.
Sure, we can register plug-in and use the secure configuration but that means deploying the solution and then re-registering the plug-ins with the secure data but this wasn't suitable.
We ruled out symmetric encryption as we would just have the same problem but for the encryption key, so the obvious choice was asymmetric encryption, the problem is that asymmetric encryption is not really suitable for large amounts of data, so we settled on the recommend way of using asymmetric encryption to encrypt the encryption key of a symmetric encryption scheme.
The thing is, the .NET framework sort of includes this in the form of the
EncryptedXml class, which can use a certificate to encrypt and decrypt Xml documents and we can use this for storing a password for instance.
A sample of how to use this class can be seen below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using log4net;
namespace Encryption
{
public class Encryption
{
ILog logger;
X509Certificate2 certificate;
public Encryption(string certificateThumbprint)
{
logger = LogManager.GetLogger("Encryption");
certificate = GetCert(certificateThumbprint);
}
public string Encrypt(string plaintext)
{
XmlDocument Doc = new XmlDocument();
Doc.LoadXml(string.Format("<sensitivedata>{0}</sensitivedata>", HttpUtility.HtmlEncode(plaintext)));
Doc.PreserveWhitespace = true;
XmlElement toEncrypt = Doc.GetElementsByTagName("sensitivedata")[0] as XmlElement;
EncryptedXml eXml = new EncryptedXml();
EncryptedData edElement = eXml.Encrypt(toEncrypt, certificate);
EncryptedXml.ReplaceElement(toEncrypt, edElement, false);
return Doc.OuterXml;
}
public string Decrypt(string encryptedtext)
{
XmlDocument Doc = new XmlDocument();
Doc.LoadXml(encryptedtext);
EncryptedXml exml = new EncryptedXml(Doc);
exml.DecryptDocument();
string plaintext = HttpUtility.HtmlDecode(XDocument.Parse(Doc.OuterXml).Element("sensitivedata").Value);
return plaintext;
}
private X509Certificate2 GetCert(string thumbprint)
{
X509Certificate2 cert = null;
X509Store store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
try
{
X509Certificate2Collection certCollection = store.Certificates;
cert = certCollection.Cast<X509Certificate2>().Where(c => c.Thumbprint.Equals(thumbprint)).Single();
}
catch (Exception ex)
{
logger.ErrorFormat("An error occurred looking for certificate with thumbprint: {0}.\nException:{1}."
, thumbprint, ex);
}
finally
{
store.Close();
}
return cert;
}
}
}
The
thing to note is that sensitive data to be encrypted needs to be valid xml data, in other words, if your sensitive data contains ampersands it will not be parsed, which is why I use the HttpUtility class to encode and decode the sensitive data.
It does seem a little bit fiddly but it is quite almost all done by the framework, so it's really less code to maintain and the extra text that needs to be stored is not a consideration as it's a single record.
Furthermore, it would be trivial to modify and return a plaintext Xml Document for processing instead of the value of an element, but the value of an element is what I needed.