.NET 到 Web 加密互操作性填充问题
.NET to web crypto interoperability padding issue
我正在尝试在 .NET 中加密一些数据(使用 C#),然后在浏览器中使用网络加密 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# 代码加密的数据只是在浏览器中出现奇怪的异常错误。
AES要求输入的长度是16的倍数,所以必须应用padding,这就是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();
}
}
MarkovskI 的回答虽然不是最终的解决方案,但帮助我找到了根本原因。由于我的 using
语句的排列方式,问题最终成为 CryptoStream
未完成将数据写入 MemoryStream
。
有两种可能的解决方案。
1) 在返回 MemoryStream's
数组之前调用 FlushFinalBlock()
。
csEncrypt.Write(data, 0, data.Length);
csEncrypt.FlushFinalBlock();
return msEncrypt.ToArray();
只是调用 Flush()
(我试过)没有用。这是有道理的,因为在计算和写入填充之前需要确保所有数据都已写入。
2) 嵌套内部 using
语句以确保 CryptoStream
在调用读取 MemoryStream
之前已被处理掉。这内部必须在 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();
}
}
我正在尝试在 .NET 中加密一些数据(使用 C#),然后在浏览器中使用网络加密 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# 代码加密的数据只是在浏览器中出现奇怪的异常错误。
AES要求输入的长度是16的倍数,所以必须应用padding,这就是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();
}
}
MarkovskI 的回答虽然不是最终的解决方案,但帮助我找到了根本原因。由于我的 using
语句的排列方式,问题最终成为 CryptoStream
未完成将数据写入 MemoryStream
。
有两种可能的解决方案。
1) 在返回 MemoryStream's
数组之前调用 FlushFinalBlock()
。
csEncrypt.Write(data, 0, data.Length);
csEncrypt.FlushFinalBlock();
return msEncrypt.ToArray();
只是调用 Flush()
(我试过)没有用。这是有道理的,因为在计算和写入填充之前需要确保所有数据都已写入。
2) 嵌套内部 using
语句以确保 CryptoStream
在调用读取 MemoryStream
之前已被处理掉。这内部必须在 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();
}
}