将 CryptoStream 解密为 MemoryStream

Decrypting CryptoStream into MemoryStream

我已经编写了一个过程,其中文件被加密并上传到 Azure,然后下载过程必须被解密,这导致 "Padding is invalid and cannot be removed" 错误或 "Length of the data to decrypt is invalid." 错误。

我在网上尝试了很多解决方案,包括 ,但其中 none 似乎有效,我最终只是在这两个错误之间来回跳动。加密过程使用与解密相同的 key/IV 对,并且由于它会解密一部分流,我觉得它工作正常 - 它最终会因上述错误而死亡。

这是我的代码,有什么想法吗?请注意,这三个变体(cryptoStream.CopyTo(decryptedStream)do {}while)并不是 运行 在一起的——它们在这里是为了展示我已经尝试过的所有选项失败了。

byte[] encryptedBytes = null;

using (var encryptedStream = new MemoryStream())
{
    //download from Azure
    cloudBlockBlob.DownloadToStream(encryptedStream);

    //reset positioning for reading it back out
    encryptedStream.Position = 0;

    encryptedBytes = encryptedStream.ConvertToByteArray();
}

//used for the blob stream from Azure
using (var encryptedStream = new MemoryStream(encryptedBytes))
{
    //stream where decrypted contents will be stored
    using (var decryptedStream = new MemoryStream())
    {
        using (var aes = new RijndaelManaged { KeySize = 256, Key = blobKey.Key, IV = blobKey.IV })
        {
            using (var decryptor = aes.CreateDecryptor())
            {
                //decrypt stream and write it to parent stream
                using (var cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read))
                {
                    //fails here with "Length of the data to decrypt is invalid." error
                    cryptoStream.CopyTo(decryptedStream);

                    int data;

                    //fails here with "Length of the data to decrypt is invalid." error after it loops a number of times,
                    //implying it is in fact decrypting part of it, just not everything
                    do
                    {
                        data = cryptoStream.ReadByte();
                        decryptedStream.WriteByte((byte)cryptoStream.ReadByte());
                    } while (!cryptoStream.HasFlushedFinalBlock);

                    //fails here with "Length of the data to decrypt is invalid." error after it loops a number of times,
                    //implying it is in fact decrypting part of it, just not everything
                    while ((data = cryptoStream.ReadByte()) != -1)
                    {
                        decryptedStream.WriteByte((byte)data);
                    }
                }
            }
        }

        //reset position in prep for reading
        decryptedStream.Position = 0;
        return decryptedStream.ConvertToByteArray();
    }
}

其中一个评论提到想知道ConvertToByteArray是什么,只是一个简单的扩展方法:

/// <summary>
/// Converts a Stream into a byte array.
/// </summary>
/// <param name="stream">The stream to convert.</param>
/// <returns>A byte[] array representing the current stream.</returns>
public static byte[] ConvertToByteArray(this Stream stream)
{
    byte[] buffer = new byte[16 * 1024];
    using (MemoryStream ms = new MemoryStream())
    {
        int read;
        while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
        {
            ms.Write(buffer, 0, read);
        }
        return ms.ToArray();
    }
}

尽管代码从未达到此目的 - 它在我达到这一点之前就死了。

在各种博客上来回反复之后,我发现上面的代码中确实有几个错误让我很头疼。首先,加密过程错误地写入了数组——它用 CryptoStream 实例包装,但实际上并没有利用它,所以我将未加密的数据写入 Azure。这是正确的方法(fileKey 是我创建的用于生成 Key/IV 对的自定义 class 的一部分,因此无论在哪里引用都可以更改为内置过程来自 RijndaelManaged 或任何你用来得到 key/IV 对的东西):

using (var aes = new RijndaelManaged { KeySize = 256, Key = fileKey.Key, IV = fileKey.IV })
{
    using (var encryptedStream = new MemoryStream())
    {
        using (ICryptoTransform encryptor = aes.CreateEncryptor())
        {
            using (CryptoStream cryptoStream = new CryptoStream(encryptedStream, encryptor, CryptoStreamMode.Write))
            {
                using (var originalByteStream = new MemoryStream(file.File.Data))
                {
                    int data;
                    while ((data = originalByteStream.ReadByte()) != -1)
                        cryptoStream.WriteByte((byte)data);
                }
            }
        }

        var encryptedBytes = encryptedStream.ToArray();
        return encryptedBytes;
    }
}

其次,由于我的加密过程涉及多个步骤(每个文件共有三个密钥 - 容器、文件名和文件本身),当我尝试解密时,我使用了错误的密钥(在我引用 blobKey解密,其实是加密文件名的密钥,而不是文件本身。正确的解密方法是:

//used for the blob stream from Azure
using (var encryptedStream = new MemoryStream(encryptedBytes))
{
    //stream where decrypted contents will be stored
    using (var decryptedStream = new MemoryStream())
    {
        using (var aes = new RijndaelManaged { KeySize = 256, Key = blobKey.Key, IV = blobKey.IV })
        {
            using (var decryptor = aes.CreateDecryptor())
            {
                //decrypt stream and write it to parent stream
                using (var cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read))
                {
                    int data;

                    while ((data = cryptoStream.ReadByte()) != -1)
                        decryptedStream.WriteByte((byte)data);
                }
            }
        }

        //reset position in prep for reading
        decryptedStream.Position = 0;
        return decryptedStream.ConvertToByteArray();
    }
}

我研究过 Azure 加密扩展 (http://www.stefangordon.com/introducing-azure-encryption-extensions/),但它比我感兴趣的更以本地文件为中心 - 我这边的一切都只是 streams/in-memory,并且正在改装该实用程序的工作量将超过其价值。

希望这可以帮助任何希望在零依赖基础文件系统的情况下加密 Azure blob 的人!

晚会有点晚了,但如果这对发现此主题的人有用:

以下对我来说效果很好。

internal static byte[] AesEncryptor(byte[] key, byte[] iv, byte[] payload)
    {
        using (var aesAlg = Aes.Create())
        {
            aesAlg.Mode = CipherMode.CBC;

            aesAlg.Padding = PaddingMode.PKCS7;

            var encryptor = aesAlg.CreateEncryptor(key, iv);

            var encrypted = encryptor.TransformFinalBlock(payload, 0, payload.Length);

            return iv.Concat(encrypted).ToArray();
        }
    }

并解密:

internal static byte[] AesDecryptor(byte[] key, byte[] iv, byte[] payload)
    {
        using (var aesAlg = Aes.Create())
        {
            aesAlg.Mode = CipherMode.CBC;

            aesAlg.Padding = PaddingMode.PKCS7;

            var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

            return decryptor.TransformFinalBlock(payload, 0, payload.Length);
        }
    }

这适用于 encrypting/decrypting 从 hex 解码到 byte[] 的固定长度十六进制字符串以及使用 Encoding.UTF8.GetBytes() 解码时的 utf8 可变长度字符串。