使用C#/.Net通过MemoryStream(byte[])对文件中的数据进行加密并在套接字上发送

本文关键字:加密 数据 套接字 文件 通过 MemoryStream Net byte 使用 | 更新日期: 2023-09-27 18:28:23

我们有一个套接字服务器,需要将加密的二进制文件传输到连接的客户端。当对文件的请求到达时,需要对文件进行加密,最好是而不要先在磁盘上制作文件的加密副本。

由于我们使用异步套接字API,因此不能使用sockets.NetworkStream。相反,我们需要使用带有byte[]缓冲区的Socket.BeginSend()作为输入。

一种看似直接的可能性是使用CryptoStream和MemoryStream作为加密内容的目的地。MemoryStream本身将使用byte[]缓冲区作为自己的数据存储库。

例如:

int blockSizeBytes = 1024 * 64;
byte [] plainData = new byte [blockSizeBytes];
byte [] encData = new byte [blockSizeBytes];
MemoryStream memory = new MemoryStream( encData );
ICryptoTransform encryptor = ... any ***CryptoServiceProvider.CreateEncryptor();
CryptoStream csEnc = new CryptoStream( memory, encryptor, CryptoStreamMode.Write );

一个文件可以一次读取并加密一个缓冲区,然后通过套接字发送到客户端。例如:

Socket clientSocket = ...; // connected peer.
int bytesRead = 0;
FileStream streamIn = new FileStream( strInputFile, FileMode.Open );
do
{
    bytesRead = streamIn.Read( plainData , 0, blockSizeBytes );
    if (bytesRead > 0)
    {
        csEnc.Write( plainData , 0, bytesRead );   // Write to crypto stream
        // At this point the underlying byte array encData will hold the most recently
        // encrypted buffer of data.
        // Ideally we would send the encData buffer over the socket to the client via 
        // the following pseudo-code:
        // 1) How can we determine the precise number of encoded bytes to send?
        clientSocket.BeginSend( encData, 0, bytesRead, ... /* other params */ );
        ...
        memory.Seek( 0, SeekOrigin.Begin );  // Reset memory stream back to start
    }
}
while (bytesRead > 0);
streamIn.Close();  // close intput file stream
outEnc.FlushFinalBlock();  // Deal with padding, etc.
// Send the final buffer with all the necessary padding to the client.
// 2) Again, how can we determine the exact number of encoded bytes to send?
clientSocket.BeginSend( encData, 0, ??how-many-bytes??, ... );
outEnc.Close();

出于测试目的,我们将编码的缓冲区写入文件,而不是套接字。尝试解密生成的文件时,会引发以下异常:CryptographicException:要解密的数据长度无效

从上面的第1)项和第2)项可以看出,我们不知道要传输到客户端(或保存到文件)的编码字节的确切数量。我们试着回忆。定位为FlushFinalBlock()之后的缓冲区大小,尽管没有成功。

请注意,当CryptoStream使用FileStream进行输出时,即既不使用MemoryStream也不使用byte[]缓冲区时,生成的文件会被正常加密,然后成功解密。不过,我们的目标是能够在没有输出流的情况下直接写出加密的byte[]缓冲区。

如果带有byte[]缓冲区的MemoryStream不可行,是否有其他替代方案可以增量加密缓冲区并在套接字上转发它们?

使用C#/.Net通过MemoryStream(byte[])对文件中的数据进行加密并在套接字上发送

我已经在两种clientSocket.BeginSend(...)方法中使用memory.Position作为Count参数进行了测试,并且能够成功地往返(即加密然后解密加密的数据)。如果这对您不起作用,那么值得提供一个完整的、自包含的、可编译的示例代码来演示这个问题。

经过Iridium的澄清,以下是一个功能齐全的代码块,它使用带有byte[]缓冲区的MemoryStream来加密输入文件中的二进制数据,并将生成的加密数据写入输出文件:

...
RijndaelManaged rjndl = new RijndaelManaged() 
{ KeySize = 128, BlockSize = 128, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 };
int blockSizeBytes = 1024 * 64; // blockSizeBytes can be any arbitrary size.
byte [] data = new byte [blockSizeBytes];
byte [] encData = new byte [blockSizeBytes];
int bytesRead = 0;
ICryptoTransform encryptor = rjndl.CreateEncryptor( keyIVBytes, keyIVBytes );
FileStream streamOut = new FileStream( strOutputFile, FileMode.Create );
FileStream streamIn = new FileStream( strInputFile, FileMode.Open );
MemoryStream memory = new MemoryStream( encData );
CryptoStream outEnc = new CryptoStream( memory, encryptor, CryptoStreamMode.Write );
do
{
    bytesRead = streamIn.Read( data, 0, blockSizeBytes );
    if (bytesRead > 0)
    {
        outEnc.Write( data, 0, bytesRead );
        streamOut.Write( encData, 0, (int)memory.Position );
        memory.Seek( 0, SeekOrigin.Begin );
    }
} while (bytesRead > 0);
streamIn.Close();
outEnc.FlushFinalBlock();
streamOut.Write( encData, 0, (int)memory.Position );
outEnc.Close();
streamOut.Close();
...