Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to encrypt and save a binary stream after serialization and read it back?

I am having some problems in using CryptoStream when I want to encrypt a binary stream after binary serialization and save it to a file. I am getting the following exception

System.ArgumentException : Stream was not readable.

Can anybody please show me how to encrypt a binary stream and save it to a file and deserialize it back correctly?

The code is as follows:

class Program
    {
        public static void Main(string[] args)
        {
            var b = new B {Name = "BB"};
            WriteFile<B>(@"C:\test.bin", b, true);
            var bb = ReadFile<B>(@"C:\test.bin", true);

            Console.WriteLine(b.Name == bb.Name);
            Console.ReadLine();
        }

        public static T ReadFile<T>(string file, bool decrypt)
        {
            T bObj = default(T);
            var _binaryFormatter = new BinaryFormatter();
            Stream buffer = null;

            using (var stream = new FileStream(file, FileMode.OpenOrCreate))
            {
                if(decrypt)
                {
                    const string strEncrypt = "*#4$%^.++q~!cfr0(_!#$@$!&#&#*&@(7cy9rn8r265&$@&*E^184t44tq2cr9o3r6329";
                    byte[] dv = {0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF};
                    CryptoStream cs;
                    DESCryptoServiceProvider des = null;

                    var byKey = Encoding.UTF8.GetBytes(strEncrypt.Substring(0, 8));
                    using (des = new DESCryptoServiceProvider())
                    {
                        cs = new CryptoStream(stream, des.CreateEncryptor(byKey, dv), CryptoStreamMode.Read);
                    }
                    buffer = cs;
                }
                else
                    buffer = stream;
                try
                {
                    bObj = (T) _binaryFormatter.Deserialize(buffer);
                }
                catch(SerializationException ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }

            return bObj;
        }

        public static void WriteFile<T>(string file, T bObj, bool encrypt)
        {
            var _binaryFormatter = new BinaryFormatter();
            Stream buffer;

            using (var stream = new FileStream(file, FileMode.Create))
            {
                try
                {
                    if(encrypt)
                    {
                        const string strEncrypt = "*#4$%^.++q~!cfr0(_!#$@$!&#&#*&@(7cy9rn8r265&$@&*E^184t44tq2cr9o3r6329";
                        byte[] dv = {0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF};
                        CryptoStream cs;
                        DESCryptoServiceProvider des = null;

                        var byKey = Encoding.UTF8.GetBytes(strEncrypt.Substring(0, 8));
                        using (des = new DESCryptoServiceProvider())
                        {
                            cs = new CryptoStream(stream, des.CreateEncryptor(byKey, dv), CryptoStreamMode.Write);
                            buffer = cs;
                        }
                    }
                    else
                        buffer = stream;

                    _binaryFormatter.Serialize(buffer, bObj);
                    buffer.Flush();
                }
                catch(SerializationException ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }
    }

    [Serializable]
    public class B
    {
        public string Name {get; set;}
    }

It throws the serialization exception as follows

The input stream is not a valid binary format. The starting contents (in bytes) are: 3F-17-2E-20-80-56-A3-2A-46-63-22-C4-49-56-22-B4-DA ...

like image 506
Anindya Chatterjee Avatar asked Jan 03 '11 11:01

Anindya Chatterjee


2 Answers

If you do it like this, it should work:

// A: encrypting when writing
// 1. create backing storage stream. In your case a file stream
using(Stream innerStream = File.Create(path))
// 2. create a CryptoStream in write mode
using(Stream cryptoStream = new CryptoStream(innerStream, encryptor, CryptoStreamMode.Write))
{
    // 3. write to the cryptoStream
    binaryFormatter.Serialize(cryptoStream, obj);
}

// B: decrypting when reading
// 1. create backing storage stream. In your case a file stream
using(Stream innerStream = File.Open(path, FileMode.Open))
// 2. create a CryptoStream in read mode
using(Stream cryptoStream = new CryptoStream(innerStream, decryptor, CryptoStreamMode.Read))
{
    // 3. read from the cryptoStream
    obj = binaryFormatter.Deserialize(cryptoStream);
}

There are a couple of problems with your code:

  1. You're using an encryptor when reading. This was probably a typo, but it should be a decryptor.

  2. You are flushing the buffer, but that is not enough when using a CryptoStream. Encryptors and decryptors work on blocks of a fixed size. The last block may not have that size, so it needs special treatment. The last block is the one written before the stream is closed, not flushed. Flushing on a CryptoStream does nothing useful, because it cannot write anything of size less than the input block size of the encryptor/decryptor, unless it is the last thing to be written. And on top of this, in general you should always close your streams, no matter what. The using statement is the recommended way of doing it:

    using(buffer)
        _binaryFormatter.Serialize(buffer, bObj);
    
like image 117
R. Martinho Fernandes Avatar answered Oct 18 '22 07:10

R. Martinho Fernandes


There is a great example on how to do this in the MSDN documentation: CryptoStream MSDN it's in the "Examples" section.

The procedure is basically this:

  1. create cryptostream (empty stream)
  2. write contents to cryptostream (encrypt)
  3. save cryptostream to file
  4. create cryptostream from file contents
  5. read from cryptostream (decrypt)
like image 35
Mervin Avatar answered Oct 18 '22 07:10

Mervin