【问题标题】:Can I use AES in CTR mode in .NET?我可以在 .NET 的 CTR 模式下使用 AES 吗?
【发布时间】:2011-09-16 11:53:57
【问题描述】:

.NET 的 AES 不直接实现 CTR。它只实现了 CBC、CFB、CTS、ECB 和 OFB。

我可以使用其中任何一种模式并围绕它们安全地实现 CTR,还是我需要完全使用不同的库?

【问题讨论】:

  • 为什么一定要使用点击率?它比CBC有什么优势?
  • 除了不允许随机访问之外,其他流密码也不是令人尴尬的并行,因为一个操作需要前一个操作才能完成。 CTR 模式解决了这一限制; CTR 实现可以高度并行。

标签: .net aes encryption


【解决方案1】:

Bouncy Castle 的对称加密实现似乎支持 CTR:

  • 对称密钥算法:AES、Blowfish、Camellia、CAST5、CAST6、DESede、DES、GOST28147、HC-128、HC-256、IDEA、NaccacheStern、RC2、RC4、RC5-32、RC5-64、RC6、Rijndael 、蛇、鲣鱼、TEA/XTEA、Twofish 和 VMPC。
  • 对称键模式:CBC、CFB、CTS、GOFB、OFB、OpenPGPCFB 和 SIC(又名 CTR)。

http://www.bouncycastle.org/csharp/

【讨论】:

    【解决方案2】:

    是的,您可以在 ECB 模式下使用 .NET 的 AES 和一个您自己初始化和递增的计数器来为每个加密的块构建一个 CTR。

    WinZipAes 加密流就是一个例子,它是开源 DotNetZip 的一部分。
    WinZip 指定对加密的 ZIP 文件使用 AES 加密,在 CTR 模式下使用 AES。 DotNetZip 使用 ECB 和计数器实现 CTR 模式。

    请参阅here 了解一些 cmets。

    【讨论】:

      【解决方案3】:

      您需要做的就是在 ECB 模式下使用带有密钥(无填充,无 IV)的 AES 来加密 128 位计数器。然后将纯文本与计数器的加密输出进行异或运算。对于每个块,计数器都会递增。由于 XOR 运算符的特性,加密和解密是相同的。

      您可以在此处找到 AES128 CTR 模式的实现(我自己的):

      https://gist.github.com/hanswolff/8809275

      应该很容易使用。

      【讨论】:

      • 最后一个字节没有使用你的实现被解密。会有什么问题?
      • +1 - 有关基于此的紧凑型独立代码,请参阅my answer
      【解决方案4】:

      基于code by @quadfinity 的紧凑型独立实现。

      (尽管在原始代码中命名了类) 它可以使用任何密钥大小:128、192 和 256。只需提供正确大小的 keysalt 必须有 128 位(16 字节)。

      该方法适用于加密和解密。

      public static void AesCtrTransform(
          byte[] key, byte[] salt, Stream inputStream, Stream outputStream)
      {
          SymmetricAlgorithm aes =
              new AesManaged { Mode = CipherMode.ECB, Padding = PaddingMode.None };
      
          int blockSize = aes.BlockSize / 8;
      
          if (salt.Length != blockSize)
          {
              throw new ArgumentException(
                  "Salt size must be same as block size " +
                  $"(actual: {salt.Length}, expected: {blockSize})");
          }
      
          byte[] counter = (byte[])salt.Clone();
      
          Queue<byte> xorMask = new Queue<byte>();
      
          var zeroIv = new byte[blockSize];
          ICryptoTransform counterEncryptor = aes.CreateEncryptor(key, zeroIv);
      
          int b;
          while ((b = inputStream.ReadByte()) != -1)
          {
              if (xorMask.Count == 0)
              {
                  var counterModeBlock = new byte[blockSize];
      
                  counterEncryptor.TransformBlock(
                      counter, 0, counter.Length, counterModeBlock, 0);
      
                  for (var i2 = counter.Length - 1; i2 >= 0; i2--)
                  {
                      if (++counter[i2] != 0)
                      {
                          break;
                      }
                  }
      
                  foreach (var b2 in counterModeBlock)
                  {
                      xorMask.Enqueue(b2);
                  }
              }
      
              var mask = xorMask.Dequeue();
              outputStream.WriteByte((byte)(((byte)b) ^ mask));
          }
      }
      

      如果要加密或解密文件,请使用 File.OpenRead 表示 inputStreamFile.Create 表示 outputStream

      using (Stream inputStream = File.OpenRead("file.in"))
      using (Stream outputStream = File.Create("file.out"))
      {
          AesCtrTransform(key, salt, inputStream, outputStream);
      }
      

      另见PowerShell version of the code

      【讨论】:

      • 对此的轻微调整包括添加一个标志以允许计数器循环 (i2) 的 for 循环从零开始。这将允许与 libtomcrypt 的 CTR_COUNTER_LITTLE_ENDIAN 标志匹配。
      • @MartinPrikryl 我的问题更多是关于进行逆运算。这种方法实际上不允许解密。但是在调整了这段代码之后,我设法做到了。您只需拨打CreateDecryptor 而不是CreateEncryptor
      【解决方案5】:

      使用 AES/CTR/NoPadding 算法进行加密和解密,该算法带有一个全零的 16 字节初始化向量 (IV) 和一个使用加密安全生成器的一次性 256 位 AES 解密密钥。

      使用@martin 代码中的AesCtrTransform method,我有以下用法示例。 请注意,我在这里将初始化向量 (IV) 字节数组留空,但如果您想让事情更安全 (Learn more: is it safe to reuse IV),您应该填充它,但是您必须将 IV 以及密钥存储在某处。

      const string text = "Hello world";
      var key = new byte[32];
      var initializationVector = new byte[16];
      
      using (var random = new RNGCryptoServiceProvider())
      {
          random.GetNonZeroBytes(key);
      }
      
      string output;
      string outputEncrypted;
      
      using (var outputEncryptedStream = new MemoryStream())
      {
          using (var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(text)))
          {
              AesCtrTransform(key, initializationVector, inputStream, outputEncryptedStream);
          }
      
          outputEncryptedStream.Position = 0;
          using (var reader = new StreamReader(outputEncryptedStream, Encoding.UTF8, true, 1024, true))
          {
              outputEncrypted = reader.ReadToEnd();
          }
          outputEncryptedStream.Position = 0;
      
          using (var outputDecryptedStream = new MemoryStream())
          {
              AesCtrTransform(key, initializationVector, outputEncryptedStream, outputDecryptedStream);
      
              outputDecryptedStream.Position = 0;
              using (var reader = new StreamReader(outputDecryptedStream))
              {
                  output = reader.ReadToEnd();
              }
          }
      }
      
      Assert.IsTrue(!string.IsNullOrEmpty(outputEncrypted));
      Assert.AreEqual(text, output);
      

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-16
      • 1970-01-01
      • 1970-01-01
      • 2012-09-13
      • 1970-01-01
      • 2022-10-13
      相关资源
      最近更新 更多