AES 填充阻止发送最后一个块

AES padding prevents sending of the final block

我正在编写一个用于发送和接收 AES 加密数据的应用程序。我正在使用 CryptoStreamNetworkStream 写入 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 来解密字节(逐个缓冲区)。与有界流技巧相比,这不是最好的技巧,但可能是最容易理解的技巧。

所以你有:三个选项:挑选和选择......好吧,很明显地实施。