Reusing ICryptoTransform Objects

I have a class that is used to encrypt text data. I am trying to reuse ICryptoTransform objects where possible. However, the second time when I try to use the same object, I get partially incorrectly decrypted data. I think the first block is wrong, but it seems to be okay (tested it with longer texts).

I split the class into the following:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace Sample.Crypto
{
    public class EncryptedStreamResolver : IDisposable
    {
        private AesCryptoServiceProvider _cryptoProvider;
        private ICryptoTransform _encryptorTransform;
        private ICryptoTransform _decryptorTransform;

        private ICryptoTransform EncryptorTransform
        {
            get
            {
                if (null == _encryptorTransform || !_encryptorTransform.CanReuseTransform)
                {
                    _encryptorTransform?.Dispose();
                    _encryptorTransform = _cryptoProvider.CreateEncryptor();
                }
                return _encryptorTransform;
            }
        }

        private ICryptoTransform DecryptorTransform
        {
            get
            {
                if (null == _decryptorTransform || !_decryptorTransform.CanReuseTransform)
                {
                    _decryptorTransform?.Dispose();
                    _decryptorTransform = _cryptoProvider.CreateDecryptor();
                }
                return _decryptorTransform;
            }
        }

        public EncryptedStreamResolver()
        {
            GenerateCryptoProvider();
        }

        public Stream OpenRead(string rawPath)
        {
            return new CryptoStream(File.OpenRead(rawPath + ".crypto"), DecryptorTransform, CryptoStreamMode.Read);
        }

        public Stream OpenWrite(string rawPath)
        {
            return new CryptoStream(File.OpenWrite(rawPath + ".crypto"), EncryptorTransform, CryptoStreamMode.Write);
        }

        private void GenerateCryptoProvider(string password = "totallysafepassword")
        {
            _cryptoProvider = new AesCryptoServiceProvider();
            _cryptoProvider.BlockSize = _cryptoProvider.LegalBlockSizes.Select(ks => ks.MaxSize).Max();
            _cryptoProvider.KeySize = _cryptoProvider.LegalKeySizes.Select(ks => ks.MaxSize).Max();
            _cryptoProvider.IV = new byte[_cryptoProvider.BlockSize / 8];
            _cryptoProvider.Key = new byte[_cryptoProvider.KeySize / 8];

            var pwBytes = Encoding.UTF8.GetBytes(password);
            for (var i = 0; i < _cryptoProvider.IV.Length; i++)
                _cryptoProvider.IV[i] = pwBytes[i % pwBytes.Length];
            for (var i = 0; i < _cryptoProvider.Key.Length; i++)
                _cryptoProvider.Key[i] = pwBytes[i % pwBytes.Length];
        }

        public void Dispose()
        {
            _encryptorTransform?.Dispose();
            _decryptorTransform?.Dispose();
            _cryptoProvider?.Dispose();
        }
    }
}

      

I wrote a sample usage test to demonstrate the problem:

public void Can_reuse_encryptor()
{
    const string message = "Secret corporate information here.";
    const string testFilePath1 = "Foo1.xml";
    const string testFilePath2 = "Foo2.xml";
    var sr = new EncryptedStreamResolver();

    // Write secret data to file
    using (var writer = new StreamWriter(sr.OpenWrite(testFilePath1)))
        writer.Write(message);

    // Read it back and compare with original message
    using (var reader = new StreamReader(sr.OpenRead(testFilePath1)))
        if (!message.Equals(reader.ReadToEnd()))
            throw new Exception("This should never happend :(");

    // Write the same data again to a different file
    using (var writer = new StreamWriter(sr.OpenWrite(testFilePath2)))
        writer.Write(message);

    // Read that back and compare
    using (var reader = new StreamReader(sr.OpenRead(testFilePath2)))
        if (!message.Equals(reader.ReadToEnd()))
            throw new Exception("This should never happend :(");
}

      

What am I missing? The documentation suggests that these objects can be reused, but I can't figure out how to do this. Can anyone help me?

EDIT:

As @bartonjs pointed out, if I migrate my project containing the codes above to .NET 4.6 (or higher), I can use System.AppContext.TryGetSwitch like this:

var reuseTransform = false;
if (null == _decryptorTransform ||
    !(AppContext.TryGetSwitch("Switch.System.Security.Cryptography.AesCryptoServiceProvider.DontCorrectlyResetDecryptor", out reuseTransform) && reuseTransform && _decryptorTransform.CanReuseTransform))
{
    _decryptorTransform?.Dispose();
    _decryptorTransform = _cryptoProvider.Createdecryptor();
}

      

Then I can set this switch to the main app.config as in @ bartonjs answer.

+3


source to share


1 answer


What you are missing is a bug (and bug) in the .NET Framework :).

There's a Problem with Microsoft Connect about this same issue; in particular, that AesCryptoServiceProvider.CreateDecryptor () returns an object that says CanReuseTransform=true

, but doesn't seem to behave correctly.

The bug was fixed in the .NET 4.6.2 release, but is protected by a reassignment change . This means that in order to see the correction, you need to

  • Install .NET Framework 4.6.2 or newer.
  • Change the minimum framework version of your main executable to 4.6.2 or higher.

If you have a new platform installed but want your executable targeting below, you need to set the radio button Switch.System.Security.Cryptography.AesCryptoServiceProvider.DontCorrectlyResetDecryptor

to false

.



From AppContext Class Documentation (under Remarks):

After defining and documenting the switch, callers can use it by using the registry by adding an AppContextSwitchOverrides element to their application configuration file, or by calling AppContext.SetSwitch (String, Boolean) programmatically.

For the config file (your.exe.config):

<configuration>
  <runtime>
    <AppContextSwitchOverrides
      value="Switch.System.Security.Cryptography.AesCryptoServiceProvider.DontCorrectlyResetDecryptor=false" />
  </runtime>
</configuration>

      

+4


source







All Articles