1

I encounter an error when using the AesCryptoServiceProvider to Encrypt some files config. The summary code is below

private static byte[] secretKey =     {
                                                        (byte)0x63, (byte)0x23, (byte)0xdf, (byte)0x2a,
                                                        (byte)0x59, (byte)0x1a, (byte)0xac, (byte)0xcc,
                                                        (byte)0x50, (byte)0xfa, (byte)0x0d, (byte)0xcc,
                                                        (byte)0xff, (byte)0xfd, (byte)0xda, (byte)0xf0                                           
                                                    };                          

private static byte[] iv = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

public static byte[] EncryptStringToBytes(String plainText, byte[] secretKey, byte[] IV)
{
    try
    {
        // Check arguments. 
        if (plainText == null || plainText.Length <= 0)
            throw new ArgumentNullException("plainText");
        if (secretKey == null || secretKey.Length <= 0)
            throw new ArgumentNullException("secretKey");
        if (IV == null || IV.Length <= 0)
            throw new ArgumentNullException("secretKey");

        byte[] encrypted;

        // Create an AesCryptoServiceProvider object 
        // with the specified key and IV. 
        using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
        {
            aesAlg.Key = secretKey;
            aesAlg.IV = IV;

            // Create a decrytor to perform the stream transform.
            ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

            // Create the streams used for encryption. 
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        swEncrypt.Write(plainText);
                    }
                    encrypted = msEncrypt.ToArray();
                }
            }
        }
        return encrypted;
    }
    catch (System.Exception ex)
    {
        LogWriter.Instance.LogError(ClassName, "EncryptStringToBytes()", ex.Message + ";\tplainText=" + plainText + ";\t" + ex.StackTrace);
        return null;
    }
}

int main()
{
    byte[] encryptText = EncryptStringToBytes("some plain text", secretKey, iv);
    if (encryptText != null)
    {
        try
        {
            File.WriteAllBytes(filePath, encryptText);
        }
        catch (Exception ex)
        {
            LogWriter.Instance.LogError(ClassName, "SaveBuffToFile()", ex.Message + ";\tFilePath=" + path + ";\t" + ex.StackTrace);
        }
    }
}

In the main function, I Encrypt the plain text and save the result to the file by calling File.WriteAllBytes(filePath, encryptText);. But sometime the content file contains all Null character ('\0'). The bellow image is the content of file when I open with HexaEditor

enter image description here

This error happen about once a month for the app running 8 hours per day.

I considered that the file may be corrupted. But I think that this case is not caused by corruption, because there are 10 config files in a folder, but only have 7 files that using Encryption is encountered this error, whereas 3 files that saved by plain text (not using Encryption) has never encountered this error.

So I think the problem caused by AesCryptoServiceProvider. Anyone please help me.

Thank you!

TTGroup
  • 3,575
  • 10
  • 47
  • 79
  • 1
    Here is question about similar problem I think (corrupted zero-bytes-filled file): https://stackoverflow.com/q/49260358/5311735. Not answered though. – Evk Mar 14 '18 at 07:36
  • 2
    I can't see anything *obviously* incorrect in this method. I don't know what chain of reasoning led to your suspicions being drawn to the `AesCryptoServiceProvider` class. My own suspicions would be around some form of race on shared variables. Difficult to tell without seeing the wider context. – Damien_The_Unbeliever Mar 15 '18 at 10:59
  • 1
    Are the files created the correct size, but simply all NUL bytes? Since you have the data in memory before you write it to the file, you can add some error checks to the data you're about to write. Is it the correct size? Is it all NUL bytes? Since this is an intermittent problem, I'd add some sort of logging when you write these files, and maybe even read them back in and verify they were written correctly. In my experience, finding the root cause of these kinds of problems is hard. The easiest way to find out what's happening is to have your code log it. I'd add logging at every step. – Andrew Henle Mar 15 '18 at 18:56
  • @AndrewHenle: Thank you. The file size is correct, but it contains all NUL bytes. Maybe I will do as you said to work around this bug. – TTGroup Mar 17 '18 at 03:18
  • @Damien_The_Unbeliever: As I mentioned in last question. because? There are 10 config files in a folder, but only have 7 files that using Encryption is encountered this error, whereas 3 files that saved by plain text (not using Encryption) has never encountered this error." – TTGroup Mar 17 '18 at 03:20
  • This answer indicates it may be caused by shutdowns...https://stackoverflow.com/a/52751216/5198140 – Richardissimo Oct 11 '18 at 05:49

1 Answers1

1

NOTE: Thanks to Damien_The_Unbeliever and his testing, this answer has been found to be WRONG and may be deleted soon. Until then, it should help others avoid going down this path.


You may have a race condition with the ToArray() call on your MemoryStream.

The code you posted calls that method outside of the using statement for StreamWriter, so that object will have flushed any buffers it may be using to the underlying CryptoStream by that point, but the call is inside the using statement for CryptoStream, which means there is no guarantee that that object will have flushed any buffers to the underlying MemoryStream by that point.

That would explain the intermittent nature of the bug:

In the cases where you get the desired result, it might be that the CryptoStream's buffer is being automatically flushed to the MemoryStream before the call to ToArray(), just by luck.

In the cases where you're getting the erroneous result, the ToArray() call is returning the data from the MemoryStream's internal buffer (which is most likely being initialized to zeros) before the MemoryStream has received all (or any) of the data from the CryptoStream.

If that is all correct, you can make the buffer flushing deterministic and guarantee a correct result every time by calling FlushFinalBlock() on the CryptoStream object before accessing the data from its underlying MemoryStream.

After that change, the code would be:

[...]

// Create the streams used for encryption. 
using ( MemoryStream msEncrypt = new MemoryStream() ) {

    using ( CryptoStream csEncrypt = new CryptoStream( msEncrypt, encryptor, CryptoStreamMode.Write ) ) {

        using ( StreamWriter swEncrypt = new StreamWriter( csEncrypt ) ) {

            swEncrypt.Write( plainText );

        }

        csEncrypt.FlushFinalBlock();
        encrypted = msEncrypt.ToArray();

    }

}

[...]

See:

CryptoStream.HasFlushedFinalBlock

This StackOverflow answer is tangentially related to your issue: https://stackoverflow.com/a/19742806

John K
  • 661
  • 6
  • 10
  • I'm not sure this logic does work out. The `StreamWriter` constructor being used is one in which the `StreamWriter` "takes ownership" of the stream that's passed to it. So when you `Dispose`/`Close` it, the underlying stream is also `Close`d. – Damien_The_Unbeliever Mar 15 '18 at 10:32
  • If you take OPs code and put a breakpoint on the `msEncrypt.ToArray();` line you'll see that `HasFlushedFinalBlock` is already reported to be `true`, due to what I mentioned in my previous comment. – Damien_The_Unbeliever Mar 15 '18 at 10:42
  • I see your point, but if the `StreamWriter` is `Close`ing the underlying CryptoStream upon `Dispose`al (which happens when the StreamWriter `using` statement finishes), and CryptoStream in turn `Close`s *its* underlying `MemoryStream` when it's `Dispose`d of, then the code should never work -- the whole chain of streams/writers would be invalid as soon as the innermost `using` scope is exited. But that's obviously not happening because the code works for him most of the time, so I must be missing something. – John K Mar 15 '18 at 10:52
  • Your logic is correct about the chain of closing - but [`MemoryStream.ToArray`](https://msdn.microsoft.com/en-us/library/system.io.memorystream.toarray(v=vs.110).aspx): "This method works when the MemoryStream is closed" – Damien_The_Unbeliever Mar 15 '18 at 10:55
  • I just saw your second comment, and that by itself doesn't disprove the theory because if the CryptoStream is sometimes -- by luck -- being flushed before he's using it, then that property would return `true`, as you're seeing. I'm not saying my diagnosis isn't wrong -- you might be right overall -- but that property returning `true` in a particular case doesn't necessarily prove that. – John K Mar 15 '18 at 10:58
  • No, flushes before the stream is either closed or has `FlushFinalBlock` called would *not* set the `HasFlushedFinalBlock` property to true because that would render the stream unusable for any further use. – Damien_The_Unbeliever Mar 15 '18 at 11:00
  • D'oh! You're right, of course, `ToArray()` can be called even after the MemoryStream is `Close`d. It's 4 AM here, and I should have known better than try to think at this hour. – John K Mar 15 '18 at 11:03
  • Oh, I see what you're saying about that property, and that makes perfect sense, too. – John K Mar 15 '18 at 11:05
  • It seems my theory is wrong, then, and that something else is causing the problem he's having. What is the right way to handle this on SO: should I just delete my answer, or put a note at the top that it's incorrect to help others not go down the same wrong path that I did? – John K Mar 15 '18 at 11:12
  • Thank you all! I tried to write a small code to test this case. I make a for loop for many thousands times, that Run the Encrypt and check output data is Null (check about 100 first bytes) and write log. But there is not any log. So I think the Encrypt is not problem – TTGroup Mar 17 '18 at 03:07