Question

Following feedback from Alexei, a simplification of the question:

How do I use a buffered Stream approach to convert the contents of a CryptoStream (using ToBase64Transform) into a StreamWriter (Unicode encoding) without using Convert.ToBase64String()?

Note: Calling Convert.ToBase64String() throws OutOfMemoryException, hence the need for a buffered/Stream approach to the conversion.

Was it helpful?

Solution

You probably should implement custom Stream, not a TextWriter. It is much easier to compose streams than writers (like pass your stream to compressed stream).

To create custom stream - derive from Stream and implement at least Write and Flush (and Read if you need R/W stream). The rest is more or less optional and depends on you additional needs, regular copy to other stream does not need anything else.

  • In constructor get inner stream passed to you for writing to. Base64 is always producing ASCII characters, so it should be easy to write output as UTF-8 with or without BOM directly to a stream, but if you want to specify encoding you can wrap inner stream with StreamWriter internally.

  • In your Write implementation buffer data till you get enough bytes to have block of multiple of 3 bytes (i.e. 300) and call Convert.ToBase64String on that portion. Make sure not to loose not-yet-converted portion. Since Base64 converts 3 bytes to 4 characters converting in blocks of multiple of 3 size will never have =/== padding at the end and can be concatenated with next block. So write that converted portion into inner stream/writer. Note that you want to limit block size to something relatively small like 3*10000 to avoid allocation of your blocks on large objects heap.

  • In Flush make sure to convert the last unwritten bytes (this will be the only one with = padding at the end) and write it to the stream too.

  • For reading you may need to be more careful as in Base64 white spaces are allowed, so you can't read fixed number of characters and convert to bytes. The easiest approach would be to read by character from StreamReader and convert each 4 non-space ones to bytes.

Note: you can consider writing/reading Base64 by hand directly from bytes. It will give you some performance benefits, but may be hard if you are not good with bit shifting.

OTHER TIPS

Please try using following to encrypt. I am using fileName/filePath as input. You can adjust it as per your requirement. Using this I have encrypted over 1 gb file successfully without any out of memory exception.

public bool EncryptUsingStream(string inputFileName, string outputFileName)
        {
            bool success = false;

            // here assuming that you already have key
            byte[] key = new byte[128];

            SymmetricAlgorithm algorithm = SymmetricAlgorithm.Create();
            algorithm.Key = key;

            using (ICryptoTransform transform = algorithm.CreateEncryptor())
            {
                CryptoStream cs = null;
                FileStream fsEncrypted = null;
                try
                {
                    using (FileStream fsInput = new FileStream(inputFileName, FileMode.Open, FileAccess.Read))
                    {
                        //First write IV 
                        fsEncrypted = new FileStream(outputFileName, FileMode.Create, FileAccess.Write);
                        fsEncrypted.Write(algorithm.IV, 0, algorithm.IV.Length);

                        //then write using stream
                        cs = new CryptoStream(fsEncrypted, transform, CryptoStreamMode.Write);
                        int bytesRead;
                        int _bufferSize = 1048576; //buggersize = 1mb; 
                        byte[] buffer = new byte[_bufferSize];
                        do
                        {
                            bytesRead = fsInput.Read(buffer, 0, _bufferSize);
                            cs.Write(buffer, 0, bytesRead);
                        } while (bytesRead > 0);

                        success = true;

                    }
                }
                catch (Exception ex)
                {
                    //handle exception or throw.
                }
                finally
                {
                    if (cs != null)
                    {                       
                        cs.Close();
                        ((IDisposable)cs).Dispose();                    

                        if (fsEncrypted != null)
                        {
                            fsEncrypted.Close();
                        }
                    }

                }
            }
            return success;
        }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top