2017-04-24 29 views
2

我有一个用于加密文本数据的类。我试图在可能的情况下重用ICryptoTransform对象。但是,第二次尝试使用同一个对象时,我得到了部分不正确的解密数据。我认为第一个块是错的,但其余的似乎没问题(用较长的文本测试它)。重用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的答案中一样。

+0

我忘了提及,附加测试通过,如果_decryptorTransform和_encryptorTransform总是重新创建(不管“如果”子句)。我只是想让它通过,即使我不会每次都重新创建它们。 –

回答

3

你缺少的是.NET Framework中的bug(和bugfix):)。

关于这个问题有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> 
+0

谢谢您的全面回答!我们不打算为兼容性原因重新调整我们的程序集(目前它是.NET 4.5.2)。因此,我不能使用_System.AppContext_(它是.NET 4.6+)。不过,我关闭了加密转换的重用,直到我们升级到4.6.2。 –

+0

对于使用ASP.NET应用程序的其他人,请确保webconfig中的'定位到4.6.2。我的设置为4.5.1,导致我的解密被破坏。 – gorillapower

+0

我遇到了Azure云服务的问题。看来AES实例没有被重用,结果是破坏了解密。即使是升级框架版本为4.6.2的新鲜香草AzureCloud Services项目也会发生这种情况。这是在进行本地调试时发生的。我也试图做如上所述的AppContext切换,但无济于事。 – gorillapower