【问题标题】:.NET to web crypto interoperability padding issue.NET 到 Web 加密互操作性填充问题
【发布时间】:2018-01-30 22:33:05
【问题描述】:

我正在尝试在 .NET 中加密一些数据(使用 C#),然后在浏览器中使用 web-crypto API(反之亦然)对其进行解密。由于某种原因,每一方的加密输出略有不同。浏览器输出在数据末尾附加了额外的 16 个字节,我找不到导致它的原因。

进行加密的 .NET 代码是:

public static byte[] Encrypt(byte[] data)
{
    using (var aes = new AesManaged())
    {
        aes.Mode = CipherMode.CBC;
        aes.Padding = PaddingMode.PKCS7;
        var key = new byte[] { 168, 126, 39, 25, 51, 65, 246, 41, 228, 56, 66, 237, 5, 8, 211, 102, 250, 16, 99, 12, 204, 14, 162, 126, 166, 140, 15, 124, 194, 186, 141, 111 };
        var iv = new byte[] { 188, 227, 223, 253, 171, 64, 82, 150, 10, 130, 159, 79, 68, 134, 192, 50 };

        using (var encryptor = aes.CreateEncryptor(key, iv))
        using (var msEncrypt = new MemoryStream())
        using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
        {
            csEncrypt.Write(data, 0, data.Length);
            return msEncrypt.ToArray();
        }
    }
}

JavaScript 代码如下(假设缺少的变量是正确的):

function encrypt(data, key) {
    window.crypto.subtle.encrypt(
        {
            name: "AES-CBC",
            iv: Uint8Array.from([188, 227, 223, 253, 171, 64, 82, 150, 10, 130, 159, 79, 68, 134, 192, 50]);
        },
        key,
        data 
    )
    .then(function(d){
        var encryptedBytes = new Uint8Array(d);
        // encryptedBytes had an additional 16 bytes added to the end of it for the same input.
        console.log(encryptedBytes);
    });
}

加密字节与 C# 版本中的字节匹配,但会在末尾添加额外的 16 个字节。这是填充问题吗?从我能找到的文档来看,PKCS7 似乎是正确的填充模式。

我认为浏览器版本可能将初始化向量附加到数据的末尾,但我检查了它并没有匹配。

尝试解密由 C# 代码加密的数据只是在浏览器中出现奇怪异常的错误。

【问题讨论】:

    标签: .net encryption aes padding webcrypto-api


    【解决方案1】:

    Markovsk 我的回答虽然不是最终解决方案,但帮助我发现了根本原因。问题最终是由于我的using 语句的排列方式,CryptoStream 没有完成将数据写入MemoryStream

    有两种可能的解决方案。

    1) 在返回MemoryStream's 数组之前调用FlushFinalBlock()

    csEncrypt.Write(data, 0, data.Length);
    csEncrypt.FlushFinalBlock();
    return msEncrypt.ToArray();
    

    只是打电话给Flush()(我试过)没有用。这是有道理的,因为它需要确保在计算和写入填充之前已写入所有数据。

    2) 嵌套一个内部 using 语句以确保在调用读取 MemoryStream 之前已处理掉 CryptoStream。这必须在Dispose 方法中调用FlushFinalBlock()

    using (var aes = new AesManaged())
    {
        aes.Mode = CipherMode.CBC;
        aes.Padding = PaddingMode.PKCS7;
        var key = new byte[] { 168, 126, 39, 25, 51, 65, 246, 41, 228, 56, 66, 237, 5, 8, 211, 102, 250, 16, 99, 12, 204, 14, 162, 126, 166, 140, 15, 124, 194, 186, 141, 111 };
        var iv = new byte[] { 188, 227, 223, 253, 171, 64, 82, 150, 10, 130, 159, 79, 68, 134, 192, 50 };
    
        using (var encryptor = aes.CreateEncryptor(key, iv))
        using (var msEncrypt = new MemoryStream())
        {
            using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
            {
                csEncrypt.Write(data, 0, data.Length);
            }
    
            return msEncrypt.ToArray();
        }
    }
    

    【讨论】:

      【解决方案2】:

      AES 要求输入的长度是 16 的倍数,因此您必须应用填充,这是 PKCS#7 的来源和额外数据的来源,这是 .Net 实现中的一个问题。如果您想删除需要从最终块中删除的填充,可以使用以下方法:

      public class NoPaddingTransformWrapper : ICryptoTransform
      {
          private ICryptoTransform _mTransform;
          public NoPaddingTransformWrapper(ICryptoTransform symmetricAlgoTransform)
          {
              _mTransform = symmetricAlgoTransform ?? throw new ArgumentNullException(nameof(symmetricAlgoTransform));
          }
      
          public bool CanReuseTransform => _mTransform.CanReuseTransform;
      
          public bool CanTransformMultipleBlocks => _mTransform.CanTransformMultipleBlocks;
      
          public int InputBlockSize => _mTransform.InputBlockSize;
      
          public int OutputBlockSize => _mTransform.OutputBlockSize;
      
          public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
          {
              return _mTransform.TransformBlock(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
          }
      
          public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
          {
              if (inputCount % _mTransform.InputBlockSize == 0)
              {
                  return _mTransform.TransformFinalBlock(inputBuffer, inputOffset, inputCount);
              }
              byte[] lastBlocks = new byte[inputCount / _mTransform.InputBlockSize + _mTransform.InputBlockSize];
              Buffer.BlockCopy(inputBuffer, inputOffset, lastBlocks, 0, inputCount);
              byte[] result = _mTransform.TransformFinalBlock(lastBlocks, 0, lastBlocks.Length);
              Debug.Assert(inputCount < result.Length);
              Array.Resize(ref result, inputCount);
              return result;
          }
      
          public void Dispose()
          {
              _mTransform.Dispose();
          }
      }
      

      【讨论】:

      • 谢谢,但我认为我的 .NET 代码需要添加 16 字节的数据,而不是删除它。或者我在这里遗漏了一些东西。在 .NET 代码中设置填充模式不会强制它添加填充吗?另外,增加 16 个字节不是意味着它已经是 16 的倍数了吗?
      • 我认为你的答案是解决我需要的相反问题,但它让我更接近了。问题是 C# 代码没有在应该添加填充时。对于 108 字节的样本输入数据集,我们应该添加 4 字节的填充,但尽管设置 aes.Padding = PaddingMode.PKCS7,但永远不会添加该数据。如果我在 .NET 代码中手动添加填充,则输出与 Web 版本匹配。但这似乎很奇怪,所以 .NET API 肯定缺少一些东西。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多