AES 填充阻止发送最后一个块
AES padding prevents sending of the final block
我正在编写一个用于发送和接收 AES 加密数据的应用程序。我正在使用 CryptoStream
从 NetworkStream
写入 to/reads。最初我尝试使用这样的内置填充:
// sending
using (Aes aesAlg = Aes.Create())
{
aesAlg.Padding = PaddingMode.PKCS7; // built-in padding
// set other AES params
using (MemoryStream ms = new MemoryStream())
using (ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV))
using (CryptoStream csEncrypt = new CryptoStream(networkStream, encryptor, CryptoStreamMode.Write, true))
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, data);
byte[] bytes = ms.ToArray();
await csEncrypt.WriteAsync(bytes, 0, bytes.Length);
csEncrypt.FlushFinalBlock(); // this should add padding and send all remaining data
}
}
// receiving
using (Aes aesAlg = Aes.Create())
{
aesAlg.Padding = PaddingMode.PKCS7; // built-in padding
// set other AES params
using (ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV))
using (CryptoStream csDecrypt = new CryptoStream(networkStream, decryptor, CryptoStreamMode.Read, true))
using (MemoryStream ms = new MemoryStream())
{
int totalBytesRead = 0;
while (totalBytesRead < messageLength) // read content until length
{
var toRead = Math.Min(buffer.Length, messageLength - totalBytesRead);
var nowRead = await csDecrypt.ReadAsync(buffer, 0, toRead ); // read from network there
totalBytesRead += nowRead;
await ms.WriteAsync(buffer, 0, nowRead);
}
ms.Position = 0;
received = (Data)formatter.Deserialize(ms); // deserialise the object
}
}
请注意,最后一个参数 new CryptoStream()
设置为 true
- 当 CryptoStream
被释放时,基本流 (networkStream
) 不会关闭。一件有趣的事情是,如果我不将其设置为 true
,那么接收就会开始正常工作。但是因为我需要 networkStream
保持打开状态,所以必须将其设置为 true
.
通过上述实现,接收流永远不会接收到所有数据 - 它会在最后一个 csDecrypt.ReadAsync()
上阻塞自己。根据我的理解,csEncrypt.FlushFinalBlock()
应该发送带有添加填充的最后一个块,但由于某种原因它没有发生。
因为这不起作用,所以我自己添加了填充,如下所示:
// sending
using (Aes aesAlg = Aes.Create())
{
aesAlg.Padding = PaddingMode.None; // no built-in padding
// set other AES params
using (MemoryStream ms = new MemoryStream())
using (ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV))
using (CryptoStream csEncrypt = new CryptoStream(networkStream, encryptor, CryptoStreamMode.Write, true))
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, data);
byte[] bytes = ms.ToArray();
await csEncrypt.WriteAsync(bytes, 0, bytes.Length);
// manually add padding (so the total number of bytes is divisible by 16 bytes/128 bits)
if (bytes.Length % 16 != 0)
await csEncrypt.WriteAsync(bytes, 0, 16 - (bytes.Length % 16)); // paddingdata
}
}
// receiving
using (Aes aesAlg = Aes.Create())
{
aesAlg.Padding = PaddingMode.None; // no built-in padding
using (ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV))
using (CryptoStream csDecrypt = new CryptoStream(networkStream, decryptor, CryptoStreamMode.Read, true))
using (MemoryStream ms = new MemoryStream())
{
int totalBytesRead = 0;
while (totalBytesRead < messageLength) // read content until length
{
var toRead = Math.Min(buffer.Length, messageLength - totalBytesRead);
// if it's the last buffer to be sent and the number of bytes is not divisible by 16 then add padding
if (messageLength - totalBytesRead <= 1024 && toRead % 16 != 0)
min += 16 - (toRead % 16);
var nowRead = await csDecrypt.ReadAsync(buffer, 0, toRead); // read from network there
totalBytesRead += nowRead;
await ms.WriteAsync(buffer, 0, nowRead);
}
ms.Position = 0;
received = (Data)formatter.Deserialize(ms); // deserialise the object
}
}
如果我自己添加填充,那么一切正常,并且接收函数不会在最后一个缓冲区上阻塞自己。我应该怎么做才能使内置填充起作用?
问题出在接收端。如果你不关闭流,那么解密例程就不知道最后一个块已经收到,所以它只是等待下一个块,而下一个块永远不会到来。如果正确实施,您现在所做的应该会起作用。但是你只需要在最后执行 unpadding - 虽然你当前的实现没有实现 PKCS#7 unpadding。
还有另一种可能更简洁的方法:从 确实 关闭的流中读取。显然你知道明文或密文的大小。所以你可以做的是使用一个 包装另一个流 的流,但它在读取密文的所有字节后关闭 - 当然,不会关闭底层发生这种情况时流式传输。这需要一些工作,但应该不会那么难,因为您只需实施 Read
方法(请参阅 Stream
class 文档了解原因)。
我认为第二个选项是最干净的,但您当然可以只读取必要的字节,然后直接使用 decryptor
来解密字节(逐个缓冲区)。与有界流技巧相比,这不是最好的技巧,但可能是最容易理解的技巧。
所以你有:三个选项:挑选和选择......好吧,很明显地实施。
我正在编写一个用于发送和接收 AES 加密数据的应用程序。我正在使用 CryptoStream
从 NetworkStream
写入 to/reads。最初我尝试使用这样的内置填充:
// sending
using (Aes aesAlg = Aes.Create())
{
aesAlg.Padding = PaddingMode.PKCS7; // built-in padding
// set other AES params
using (MemoryStream ms = new MemoryStream())
using (ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV))
using (CryptoStream csEncrypt = new CryptoStream(networkStream, encryptor, CryptoStreamMode.Write, true))
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, data);
byte[] bytes = ms.ToArray();
await csEncrypt.WriteAsync(bytes, 0, bytes.Length);
csEncrypt.FlushFinalBlock(); // this should add padding and send all remaining data
}
}
// receiving
using (Aes aesAlg = Aes.Create())
{
aesAlg.Padding = PaddingMode.PKCS7; // built-in padding
// set other AES params
using (ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV))
using (CryptoStream csDecrypt = new CryptoStream(networkStream, decryptor, CryptoStreamMode.Read, true))
using (MemoryStream ms = new MemoryStream())
{
int totalBytesRead = 0;
while (totalBytesRead < messageLength) // read content until length
{
var toRead = Math.Min(buffer.Length, messageLength - totalBytesRead);
var nowRead = await csDecrypt.ReadAsync(buffer, 0, toRead ); // read from network there
totalBytesRead += nowRead;
await ms.WriteAsync(buffer, 0, nowRead);
}
ms.Position = 0;
received = (Data)formatter.Deserialize(ms); // deserialise the object
}
}
请注意,最后一个参数 new CryptoStream()
设置为 true
- 当 CryptoStream
被释放时,基本流 (networkStream
) 不会关闭。一件有趣的事情是,如果我不将其设置为 true
,那么接收就会开始正常工作。但是因为我需要 networkStream
保持打开状态,所以必须将其设置为 true
.
通过上述实现,接收流永远不会接收到所有数据 - 它会在最后一个 csDecrypt.ReadAsync()
上阻塞自己。根据我的理解,csEncrypt.FlushFinalBlock()
应该发送带有添加填充的最后一个块,但由于某种原因它没有发生。
因为这不起作用,所以我自己添加了填充,如下所示:
// sending
using (Aes aesAlg = Aes.Create())
{
aesAlg.Padding = PaddingMode.None; // no built-in padding
// set other AES params
using (MemoryStream ms = new MemoryStream())
using (ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV))
using (CryptoStream csEncrypt = new CryptoStream(networkStream, encryptor, CryptoStreamMode.Write, true))
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, data);
byte[] bytes = ms.ToArray();
await csEncrypt.WriteAsync(bytes, 0, bytes.Length);
// manually add padding (so the total number of bytes is divisible by 16 bytes/128 bits)
if (bytes.Length % 16 != 0)
await csEncrypt.WriteAsync(bytes, 0, 16 - (bytes.Length % 16)); // paddingdata
}
}
// receiving
using (Aes aesAlg = Aes.Create())
{
aesAlg.Padding = PaddingMode.None; // no built-in padding
using (ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV))
using (CryptoStream csDecrypt = new CryptoStream(networkStream, decryptor, CryptoStreamMode.Read, true))
using (MemoryStream ms = new MemoryStream())
{
int totalBytesRead = 0;
while (totalBytesRead < messageLength) // read content until length
{
var toRead = Math.Min(buffer.Length, messageLength - totalBytesRead);
// if it's the last buffer to be sent and the number of bytes is not divisible by 16 then add padding
if (messageLength - totalBytesRead <= 1024 && toRead % 16 != 0)
min += 16 - (toRead % 16);
var nowRead = await csDecrypt.ReadAsync(buffer, 0, toRead); // read from network there
totalBytesRead += nowRead;
await ms.WriteAsync(buffer, 0, nowRead);
}
ms.Position = 0;
received = (Data)formatter.Deserialize(ms); // deserialise the object
}
}
如果我自己添加填充,那么一切正常,并且接收函数不会在最后一个缓冲区上阻塞自己。我应该怎么做才能使内置填充起作用?
问题出在接收端。如果你不关闭流,那么解密例程就不知道最后一个块已经收到,所以它只是等待下一个块,而下一个块永远不会到来。如果正确实施,您现在所做的应该会起作用。但是你只需要在最后执行 unpadding - 虽然你当前的实现没有实现 PKCS#7 unpadding。
还有另一种可能更简洁的方法:从 确实 关闭的流中读取。显然你知道明文或密文的大小。所以你可以做的是使用一个 包装另一个流 的流,但它在读取密文的所有字节后关闭 - 当然,不会关闭底层发生这种情况时流式传输。这需要一些工作,但应该不会那么难,因为您只需实施 Read
方法(请参阅 Stream
class 文档了解原因)。
我认为第二个选项是最干净的,但您当然可以只读取必要的字节,然后直接使用 decryptor
来解密字节(逐个缓冲区)。与有界流技巧相比,这不是最好的技巧,但可能是最容易理解的技巧。
所以你有:三个选项:挑选和选择......好吧,很明显地实施。