Search code examples
c#encryptionserializationxmlserializerixmlserializable

Encrypted XML file when using IXmlSerializable (c#)


How can I write a XML-File to which I serialize using IXmlSerializable in an encrypted way?

I (de-)serialize my data (a structure of nodes containig nodes, just like filesystemfolders) into a xml-File:

public class DataNodeCollection : List<DataNode>, IXmlSerializable
{
    internal void Serialize()
    {
        string sFilename = getFilename();
        using (var writer = new StreamWriter(sFilename, false, Encoding.Unicode))
        {
            var serializer = new XmlSerializer(this.GetType(), new XmlRootAttribute("SystemNodes"));
            serializer.Serialize(writer, this);
            writer.Flush();
        }
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("FileFormatVersion", CurrentFormatVersion.ToString(CultureInfo.InvariantCulture));

        foreach (DataNode elem in this)
        {
            var innerSerializer = new XmlSerializer(typeof(DataNode), new XmlRootAttribute(elem.Name));
            innerSerializer.Serialize(writer, elem);
        }
    }
}

public class DataNode : IXmlSerializable
{
        private IDictionary<string, string> _mapAttributes = new Dictionary<string, string>();
        private IList<DataNode> _subNodes = new List<DataNode>();

        public string Name { get; protected set; }

        public void WriteXmlXmlWriter writer)
        {
            foreach (string sKey in _mapAttributes.Keys)
            {
                writer.WriteAttributeString(sKey, _mapAttributes[sKey]);
            }

            foreach (DataNode node in _subNodes)
            {
                var innerSerializer = new XmlSerializer(typeof(DataNode), new XmlRootAttribute(node.Name));
                innerSerializer.Serialize(writer, node);
            }
        }
}

The code above shows the serialilzation-code, deserialisation is omitted because I don't think its needed to get the problem.

So how can I write the file encrypted and decrypt it before deserialising? The encryption/decryption should happen in memory (I don't want to write an unencrypted file first and read it back to encrypt it)

edit: With "encryption" I mean the file should not be human readable or parseable by other programs without knowing how to decrypt it (symmetric key)


Solution

  • UPDATE 1:

    Here is the same implementation but as two methods supporting Unicode encoding and probably mitigating the code analysis issues.

    static void SerializeToEncryptedXmlFile(object graph, string filePath)
    {
        using (FileStream encryptedFileStream = File.Create(filePath))
        {
            using (AesManaged aesManaged = CreateAesManaged())
            {
                using
                (
                    CryptoStream cryptoStream = new CryptoStream
                    (
                        encryptedFileStream, CreateAesManaged().CreateEncryptor(), CryptoStreamMode.Write
                    )
                )
                {
                    using (StreamWriter unicodeStreamWriter = new StreamWriter(cryptoStream, Encoding.Unicode))
                    {
                        {
                            new XmlSerializer(typeof(CharacterData)).Serialize(unicodeStreamWriter, CharacterData.RandomInstance);
                            // If you dont use a using statement for the cryptoStream,
                            // Don't forget to call FlushFinalBlock yourself
                            // Or you will have padding problems.
                            // cryptoStream.FlushFinalBlock();
                        }
                    }
                }
            }
        }
    }
    
    public static TResult DeserializeFromEncryptedXmlFile<TResult>(string filePath)
    {
        using (FileStream encryptedFileStream = File.OpenRead(filePath))
        {
            using (AesManaged aesManaged = CreateAesManaged())
            {
                using
                (
                    CryptoStream cryptoStream = new CryptoStream
                    (
                        encryptedFileStream, aesManaged.CreateDecryptor(), CryptoStreamMode.Read
                    )
                )
                {
                    using (StreamReader unicodeStreamReader = new StreamReader(cryptoStream))
                    {
                        return (TResult)new XmlSerializer(typeof(CharacterData)).Deserialize(unicodeStreamReader);
                    }
                }
            }
        }
    }
    

    And the usage is as follows:

    SerializeToEncryptedXmlFile(CharacterData.RandomInstance, "c:\\temp\\enc.xml");
    CharacterData instance = DeserializeFromEncryptedXmlFile<CharacterData>("c:\\temp\\enc.xml");
    

    ORIGINAL ANSWER:

    To achieve complete encryption, pass a CryptoStream instance to the XmlSerializer.

    Here is a sample using AesManaged covering both encryption and decryption.

    Note: CharacterData is some XML serializable class which is not relevant here.

    // Returns AesManaged with 256 bit key, 128 bit IV, PKCS7 padding and using CBC mode
    private static AesManaged CreateAesManaged()
    {
        return new AesManaged()
        {
            Key = Encoding.ASCII.GetBytes("This is the key%This is the key%"),
            IV = Encoding.ASCII.GetBytes("This is the IV%%")
        };
    }
    
    static void Main(string[] args)
    {
        // Serialization / Encryption:
        using (FileStream encryptedFileStream = File.Create("C:\\temp\\enc.xml"))
        {
            using
            (
                CryptoStream cryptoStream = new CryptoStream
                (
                    encryptedFileStream, CreateAesManaged().CreateEncryptor(), CryptoStreamMode.Write
                )
            )
            {
                new XmlSerializer(typeof(CharacterData)).Serialize(cryptoStream, CharacterData.RandomInstance);
                // If you dont use a using statement for the cryptoStream,
                // Don't forget to call FlushFinalBlock yourself
                // Or you will have padding problems.
                // cryptoStream.FlushFinalBlock();
            }
        }
    
        // De-Serialization / Decryption:
        using (FileStream encryptedFileStream = File.OpenRead("C:\\temp\\enc.xml"))
        {
            using
            (
                CryptoStream cryptoStream = new CryptoStream
                (
                    encryptedFileStream, CreateAesManaged().CreateDecryptor(), CryptoStreamMode.Read
                )
            )
            {
                CharacterData instance = (CharacterData)new XmlSerializer(typeof(CharacterData)).Deserialize(cryptoStream);
            }
        }
    
        Console.ReadLine();
    }