Thursday, November 14, 2013

Encrypt XML Content with X.509 Certificates

On MSDN, there is some help how to encrypt XML elements with certificates, for example here: How To: Encrypt XML Element with X.509 Certificates

However, what is missing, is advice about how to encrypt XML content, i.e. in the famous XML example
<root>
  <creditcard>
    <number>19834209</number>
    <expiry>02/02/2002</expiry>
  </creditcard>
</root>


you might not want to decrypt the whole <creditcard> element, but rather its two subnodes <number> and <expiry>. This is necessary when the http://www.w3.org/2001/04/xmlenc#Content standard is used.


I just reuse the code from the how-to-article and add specific lines for the content-encryption:

For Encryption, you use this method:

public static XmlDocument Encrypt(XmlDocument doc, string elementToEncryptName, X509Certificate2 cert)
{
    // Check the arguments.
    if (doc == null)
        throw new ArgumentNullException("doc");
    if (elementToEncryptName == null)
        throw new ArgumentNullException("elementToEncryptName");
    if (cert == null)
        throw new ArgumentNullException("cert");

    ////////////////////////////////////////////////
    // Find the specified element in the XmlDocument
    // object and create a new XmlElemnt object.
    ////////////////////////////////////////////////
    XmlElement elementToEncrypt = doc.GetElementsByTagName(elementToEncryptName)[0] as XmlElement;

    // Throw an XmlException if the element was not found.
    if (elementToEncrypt == null)
    {
        throw new XmlException("The specified element was not found");

    }

    //////////////////////////////////////////////////
    // Create a new instance of the EncryptedXml class
    // and use it to encrypt the XmlElement with the
    // a new random symmetric key.
    //////////////////////////////////////////////////

    // Create a 256 bit Rijndael key.
    RijndaelManaged sessionKey = new RijndaelManaged();
    sessionKey.KeySize = 128;

    // Set RSA crypto provider to certificate's public key
    RSACryptoServiceProvider alg = (RSACryptoServiceProvider) cert.PublicKey.Key;

    // Encrypt the content of the XML element
    EncryptedXml eXml = new EncryptedXml();
    byte[] encryptedElement = eXml.EncryptData(Encoding.UTF8.GetBytes(elementToEncrypt.InnerXml), sessionKey);

    ////////////////////////////////////////////////
    // Construct an EncryptedData object and populate
    // it with the desired encryption information.
    ////////////////////////////////////////////////

    EncryptedData edElement = new EncryptedData();
    edElement.Type = EncryptedXml.XmlEncElementContentUrl;
    //edElement.Id = encryptionElementId;

    // Create an EncryptionMethod element so that the
    // receiver knows which algorithm to use for decryption.
    edElement.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES128Url);

    // Encrypt the session key and add it to an EncryptedKey element.
    EncryptedKey ek = new EncryptedKey();

    byte[] encryptedKey = EncryptedXml.EncryptKey(sessionKey.Key, alg, true);

    ek.CipherData = new CipherData(encryptedKey);

    ek.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncRSAOAEPUrl);

    edElement.KeyInfo.AddClause(new KeyInfoEncryptedKey(ek));

    //// Create a new KeyInfo element.
    KeyInfo ki = new KeyInfo();
    ki.AddClause(new KeyInfoX509Data(cert, X509IncludeOption.EndCertOnly));

    // Add the KeyInfoName element to the
    // EncryptedKey object.
    ek.KeyInfo = ki;

    // Add the encrypted element data to the
    // EncryptedData object.
    edElement.CipherData.CipherValue = encryptedElement;

    ////////////////////////////////////////////////////
    // Replace the element from the original XmlDocument
    // object with the EncryptedData element.
    ////////////////////////////////////////////////////
    EncryptedXml.ReplaceElement(elementToEncrypt, edElement, true);
    sessionKey.Clear();

    return doc;
}


The decryption can be taken from the MSDN example:
public static XmlDocument Decrypt(XmlDocument doc)
{
    // Check the arguments. 
    if (doc == null)
        throw new ArgumentNullException("doc");

    // Decrypt the XML document, with the help of the private key from the certification store
    EncryptedXml exml = new EncryptedXml(doc);
    exml.DecryptDocument();

    return doc;
}


The calling code will be:

X509Certificate2 cert = new X509Certificate2("xxx.pfx", "xxx");
var encrypted = Encrypt(xmlDoc, "creditcard", cert);
var decrypted = Decrypt(encrypted);