【问题标题】:Reuse ICryptoTransform objects重用 ICryptoTransform 对象
【发布时间】:2017-09-21 10:53:52
【问题描述】:

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

我将课程精简为以下内容:

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();
        }
    }
}

我写了一个示例使用测试来演示这个问题:

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 :(");
}

我错过了什么?文档表明这些对象是可重用的,但我不明白如何。有人可以帮帮我吗?

编辑:

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

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 的回答一样。

【问题讨论】:

  • 我忘了提到附加的测试通过了,如果 _decryptorTransform 和 _encryptorTransform 总是重新创建(不管“if”子句)。我只是希望它通过,即使我不是每次都重新创建它们。
  • 一种可能的解决方法是在数据开头填充 32 个零,然后在解密后删除填充的字节。测试表明这种方法比重新创建对象快 3 倍。

标签: c# cryptography cryptostream .net-4.6.2


【解决方案1】:

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

Microsoft Connect Issue 也有同样的问题;特别是 AesCryptoServiceProvider.CreateDecryptor() 返回一个对象,上面写着CanReuseTransform=true,但似乎行为不正确。

该错误已在 .NET 4.6.2 版本中修复,但在 retargeting change 后面被保护。这意味着为了看到修复,您需要

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

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

来自AppContext class documentation(在“备注”下):

定义并记录开关后,调用者可以通过注册表、将 AppContextSwitchOverrides 元素添加到其应用程序配置文件或以编程方式调用 AppContext.SetSwitch(String, Boolean) 方法来使用它。

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

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

【讨论】:

  • 感谢您的全面回答!出于兼容性原因,我们不打算重新定位我们的程序集(目前它是 .NET 4.5.2)。出于这个原因,我还不能使用 System.AppContext (它是 .NET 4.6+)。但是,在我们升级到 4.6.2 之前,我关闭了加密转换的重用。
  • 对于其他使用 ASP.NET 应用程序的人,请确保 webconfig 中的 &lt;httpRuntime targetFramework="4.6.2" /&gt; 面向 4.6.2。我的设置为 4.5.1,导致我的解密被破坏。
  • 我遇到了 Azure 云服务的问题。似乎 AES 实例没有被重用,结果是解密损坏。即使是升级框架版本为 4.6.2 的全新香草 AzureCloud 服务项目也会发生这种情况。这发生在进行本地调试时。我也尝试过上面提到的 AppContext 切换,但无济于事。
  • Microsoft Connect 已停用,这是新的文档链接:github.com/microsoft/dotnet/blob/master/Documentation/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-02
  • 2010-09-28
  • 2010-10-25
  • 2012-04-13
  • 2015-03-09
  • 2011-05-22
相关资源
最近更新 更多