0

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)

suriel
  • 191
  • 1
  • 10
  • As soon as you figure out what do you mean "encrypt" you should be able to find stream that implements it and use code similar to compression like https://stackoverflow.com/questions/18212311/c-sharp-xml-compression. In current state post is not really answerable - please [edit] to clarify what kind of encryption you are looking for (preferable pointing to C#/.Net implementation). – Alexei Levenkov Apr 03 '20 at 07:19

1 Answers1

0

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();
}
Oguz Ozgul
  • 6,809
  • 1
  • 14
  • 26
  • This works like a charm, thank you very much, BUT: since I'm using a FileStream instead of a StreamWriter to serialize the data, I cannot set the encoding of the xml-file anymore, so it won't be utf-16 anmore. How to fix that? Additionally CodeAnalysis gives me warnings: CreateAesManaged is not disposed while encryptedFileStream can get disposed more than once? – suriel Apr 03 '20 at 12:23
  • I am updating my answer now. – Oguz Ozgul Apr 03 '20 at 13:16
  • I tried your update and at least the uft-16-problem is solved, thanks a lot. I still get CodeAnalysis-warnings about objects disposed multiple times (encryptedFileStream and cryptoStream) and objects not disposed (the AesManaged-object of CreateAesManaged). I tried https://learn.microsoft.com/en-us/visualstudio/code-quality/ca2202?view=vs-2019 to solve the first problem but I still get the warning. I'm quite unsure if it may be a false positive? – suriel Apr 03 '20 at 20:18
  • The multiple times disposal is because we both declare it with using and use it as base stream for crypto stream which may dispose our underlying stream when being disposed itself. – Oguz Ozgul Apr 03 '20 at 20:20
  • AES not disposes is because we return it from a method and there is no guarantee that the consumer of it will dispose it, I think. Just declare and initialize it in-line if required – Oguz Ozgul Apr 03 '20 at 20:22
  • well... ok. I got rid of the multiple-disposed-warnings by applying the microsoft-trick mentioned in the above link and I simply supressed the warning concerning AES-not-disposed. Seems to me its working very good. Thank you very much – suriel Apr 03 '20 at 22:35