重用ICryptoTransform对象 [英] Reuse ICryptoTransform objects

查看:116
本文介绍了重用ICryptoTransform对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个用于加密文本数据的类.我正在尝试尽可能重用ICryptoTransform对象.但是,第二次尝试使用同一对象时,我得到了部分错误解密的数据.我认为第一个方块是错误的,但其余的看起来似乎还可以(使用更长的文本进行了测试).

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

我将类简化为以下内容:

I stripped down the class to 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 have written 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 are reusable but I can't understand how. Can someone help me please?

正如@bartonjs指出的那样,如果我将包含以上代码的项目重新定位到.NET 4.6(或更高版本),则可以使用 System.AppContext.TryGetSwitch

As @bartonjs pointed out, if I retarget my project containing the codes above to .NET 4.6 (or above) 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();
}

然后我可以在主应用程序的app.config中设置此开关,就像@bartonjs的答案一样.

Then I can set this switch in the main application's app.config, as in @bartonjs' answer.

推荐答案

您缺少的是.NET Framework中的错误(和错误修正):).

What you're missing is the bug (and bugfix) in .NET Framework :).

有一个 Microsoft Connect问题有关同一问题;尤其是AesCryptoServiceProvider.CreateDecryptor()返回一个表示CanReuseTransform=true,但似乎行为不正确的对象.

There's a Microsoft Connect Issue about this same problem; specifically that AesCryptoServiceProvider.CreateDecryptor() returns an object that says CanReuseTransform=true, but doesn't seem to behave correctly.

.NET 4.6.2版本中修复了该错误,但在

The bug was fixed in the .NET 4.6.2 release, but is guarded behind a retargeting change. That means that in order to see the fix you need to

  1. 安装.NET Framework 4.6.2或更高版本.
  2. 将主可执行文件的最低框架版本更改为4.6.2或更高版本.

如果您安装了较新的框架,但是要使可执行文件的目标框架版本较低,则需要将开关Switch.System.Security.Cryptography.AesCryptoServiceProvider.DontCorrectlyResetDecryptor设置为false.

If you have the newer framework installed, but want to keep your executable targeting a lower version of the framework you need to set the switch Switch.System.Security.Cryptography.AesCryptoServiceProvider.DontCorrectlyResetDecryptor to false.

AppContext类文档(在备注"下):

一旦定义并记录了该开关,调用者就可以通过使用注册表,向其应用程序配置文件添加AppContextSwitchOverrides元素或调用

Once you define and document the switch, callers can use it by using the registry, by adding an AppContextSwitchOverrides element to their application configuration file, or by calling the AppContext.SetSwitch(String, Boolean) method programmatically.

对于配置文件(your.exe.config):

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

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

这篇关于重用ICryptoTransform对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆