Search code examples
c#stringencryptioncryptostreamdeflatestream

Read string from CryptoStream correctly?


I try write and read back string from file. Use this code for the write:

...
Aes aes = Aes.Create();
                aes.KeySize = 256;
                aes.BlockSize = 128;
                aes.Mode = CipherMode.CBC;
                aes.Padding = PaddingMode.PKCS7;
                Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(passwordBytes, salt, 185000, HashAlgorithmName.SHA512);
                aes.Key = key.GetBytes(aes.KeySize / 8);
                aes.IV = key.GetBytes(aes.BlockSize / 8);
                createstream.Write(salt, 0, salt.Length);

                using (DeflateStream compresstream = new DeflateStream(createstream, CompressionLevel.Optimal))
                {
                    using (CryptoStream cryptostream = new CryptoStream(compresstream, aes.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        byte[] str = Encoding.UTF8.GetBytes("TestString")!;
                        byte[] strl = BitConverter.GetBytes(str.Length);
                        cryptostream.Write(strl, 0, 4);
                        cryptostream.Write(str, 0, str.Length);

                        byte[] str1 = Encoding.UTF8.GetBytes("TestString2")!;
                        byte[] strl1 = BitConverter.GetBytes(str1.Length);
                        cryptostream.Write(strl1, 0, 4);
                        cryptostream.Write(str1, 0, str1.Length);
                    }
                }

And this for the read back:

...
readstream.Read(salt, 0, salt.Length);
...
 Aes aes = Aes.Create();
                aes.KeySize = 256;
                aes.BlockSize = 128;
                aes.Mode = CipherMode.CBC;
                aes.Padding = PaddingMode.PKCS7;
                Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(passwordBytes, salt, 185000, HashAlgorithmName.SHA512);
                aes.Key = key.GetBytes(aes.KeySize / 8);
                aes.IV = key.GetBytes(aes.BlockSize / 8);

                using (DeflateStream decompresstream = new DeflateStream(readstream, CompressionMode.Decompress))
                {
                    using (CryptoStream decryptostream = new CryptoStream(decompresstream, aes.CreateDecryptor(), CryptoStreamMode.Read))
                    {
                        byte[] str = new byte[4];
                        decryptostream.Read(str, 0, str.Length);
                        int size = BitConverter.ToInt32(str, 0);
                        byte[] strl = new byte[size];
                        decryptostream.Read(strl, 0, size);
                        string result = Encoding.UTF8.GetString(strl);

                        byte[] str1 = new byte[4];
                        decryptostream.Read(str1, 0, str1.Length);
                        int size1 = BitConverter.ToInt32(str1, 0);
                        byte[] strl1 = new byte[size1];
                        decryptostream.Read(strl1, 0, size1);
                        string result1 = Encoding.UTF8.GetString(strl1);

                        Console.WriteLine($"String: {result} {result1}");

                        Console.ReadLine();
                    }
                }

My problem is...after reading back, i get this result: String: TestString TestStrin. Why cryptostream is cut my string?

If i write the digits, the CryptoStream is read correctly for all. How to right write or read string correctly use CryptoStream?


Solution

  • Try switching to ReadExactly (available since ) from Read:

    using (DeflateStream decompresstream = new DeflateStream(readstream, CompressionMode.Decompress))
    using (CryptoStream decryptostream =
           new CryptoStream(decompresstream, aes.CreateDecryptor(), CryptoStreamMode.Read))
    {
        byte[] str = new byte[4];
        decryptostream.ReadExactly(str, 0, str.Length);
        int size = BitConverter.ToInt32(str, 0);
        byte[] strl = new byte[size];
        decryptostream.ReadExactly(strl, 0, strl.Length);
        string result = Encoding.UTF8.GetString(strl);
    
        byte[] str1 = new byte[4];
        decryptostream.ReadExactly(str1, 0, str1.Length);
        int size1 = BitConverter.ToInt32(str1, 0);
        byte[] strl1 = new byte[size1];
        decryptostream.ReadExactly(strl1, 0, strl1.Length);
            
        string result1 = Encoding.UTF8.GetString(strl1);
    
        Console.WriteLine($"String: {result} {result1}");
    }
    

    If you are running earlier version then you will need to implement it yourself for example via while cycle. Something along these lines:

    public static class StreamExts
    {
        public static void ReadExactlyOrTillEnd(this Stream stream, byte[] buffer)
        {
            if (buffer.Length == 0)
            {
                return;
            }
    
            var read = -1;
            var total = 0;
            while (total < buffer.Length && read != 0)
            {
                read = stream.Read(buffer, total, buffer.Length - total);
                total += read;
            }
        }
    }
    

    Read more: