Search code examples
c#.netlinq-to-xmldpapi

updating XDocument with DPAPI not working?


I am new to C# and cryptography but I want to secure some data like an account with DPAPI in a C# project. I tryed some ways to do it but the data passed is XDocument and have to stay as it.

I tryed to pass a string and modify it with no problem but when it comes to the XML Data it is broken.

I am using the sample of MS dotnet standard.

This code works (initalization of the file)

        byte[] toEncrypt = null;
        
        byte[] entropy = CreateRandomEntropy();

        FileStream fStream = new FileStream("Data.dat", FileMode.OpenOrCreate);
        var length = new System.IO.FileInfo("Data.dat").Length;
        if (length == 0)
        {
            XDocument doc =
              new XDocument(
                new XElement("data",
                  new XElement("global"),
                  new XElement("accounts")
                )
              );
            toEncrypt = UnicodeEncoding.ASCII.GetBytes(doc.ToString());

            EncryptDataToStream(toEncrypt, entropy, DataProtectionScope.CurrentUser, fStream);
        }
        fStream.Close();

Then I update this previous sample with some data:

fStream = new FileStream("Data.dat", FileMode.OpenOrCreate);

        byte[] decryptData = DecryptDataFromStream(entropy, DataProtectionScope.CurrentUser, fStream, 2);

        string xml = UnicodeEncoding.ASCII.GetString(decryptData);

        XDocument xmlData = XDocument.Parse(xml);

        int maxId = 0;

        if (xmlData.Descendants("account").Any())
        {
            maxId = xmlData.Descendants("account")
               .Max(x => (int)x.Attribute("id"));
        }

        maxId++;

        var compteElement = new XElement("account",
            new XAttribute("id", maxId),
            new XElement("login", "monemail@home.fr"),
            new XElement("label", "compte TEST")
            );

        xmlData.Element("data").Element("accounts").Add(compteElement);

        MemoryStream ms = new MemoryStream();
        var settings = new XmlWriterSettings()
        {
            Indent = true
        };
        using (var writer = XmlWriter.Create(ms, settings))
        {
            xmlData.WriteTo(writer);
            writer.Flush();

            StreamReader sr = new StreamReader(ms);
            ms.Seek(0, SeekOrigin.Begin);
            String content = sr.ReadToEnd();

            byte[] BytedxmlData = UnicodeEncoding.ASCII.GetBytes(content);
            int bytesWritten = EncryptDataToStream(BytedxmlData, entropy, DataProtectionScope.CurrentUser, fStream);
        }

        fStream.Flush();

        fStream.Close();

And I try to read the data:

fStream = new FileStream("Data.dat", FileMode.Open);

        byte[] decryptData2 = DecryptDataFromStream(entropy, DataProtectionScope.CurrentUser, fStream, 2);

        Console.WriteLine("Decrypted data: " + UnicodeEncoding.ASCII.GetString(decryptData2));

        fStream.Close();

Data.dat grown each times of the byte it isi being added at each update. Some I think it is being populated correctly but when I read it, I get only the first record the initialize the file and anyupdate.

Here are the encrypt and decrypt methods:

public static int EncryptDataToStream(byte[] Buffer, byte[] Entropy, DataProtectionScope Scope, Stream S)
{
    if (Buffer == null)
        throw new ArgumentNullException("Buffer");
    if (Buffer.Length <= 0)
        throw new ArgumentException("Buffer");
    if (Entropy == null)
        throw new ArgumentNullException("Entropy");
    if (Entropy.Length <= 0)
        throw new ArgumentException("Entropy");
    if (S == null)
        throw new ArgumentNullException("S");

    int length = 0;

    byte[] encryptedData = ProtectedData.Protect(Buffer, Entropy, Scope);

    if (S.CanWrite && encryptedData != null)
    {
        S.Write(encryptedData, 0, encryptedData.Length);

        length = encryptedData.Length;
    }

    return length;
}

public static byte[] DecryptDataFromStream(byte[] Entropy, DataProtectionScope Scope, Stream S, int Length)
{
    if (S == null)
        throw new ArgumentNullException("S");
    if (Length <= 0)
        throw new ArgumentException("Length");
    if (Entropy == null)
        throw new ArgumentNullException("Entropy");
    if (Entropy.Length <= 0)
        throw new ArgumentException("Entropy");

    byte[] inBuffer = new byte[S.Length];
    byte[] outBuffer;

    if (S.CanRead)
    {
        S.Read(inBuffer, 0, inBuffer.Length);

        outBuffer = ProtectedData.Unprotect(inBuffer, Entropy, Scope);
    }
    else
    {
        throw new IOException("Could not read the stream.");
    }

    return outBuffer;
}

Solution

  • Thanks to @jdweng I was from with the encoding. Got to change UnicodeEncoding.ASCII to Encoding.UTF-8