Rather than converting to byte[]
as an intermediate step when passing to different stream objects you can chain multiple streams together, passing the output from one to the input of another.
This approach makes sense here, as you are chaining together
Binary Serialization => Encryption => Writing to File.
With this in mind, you can change ConvertObjectEmByte
to something like:
public static void WriteObjectToStream(Stream outputStream, Object obj)
{
if (object.ReferenceEquals(null, obj))
{
return;
}
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(outputStream, obj);
}
and similarly, ConvertByteEmObject
can become:
public static object ReadObjectFromStream(Stream inputStream)
{
BinaryFormatter binForm = new BinaryFormatter();
object obj = binForm.Deserialize(inputStream);
return obj;
}
To add in the encryption/decryption, we can write functions that create CryptoStream
objects that we can chain with these binary serialization functions. My example functions below look a bit different from the Encrypt
/Decrypt
functions in the article you linked to because the IV (Initialization Vector) is now generated randomly and written to the stream (and read from the stream on the other end). It's important that the IV is unique for each chunk of data you encrypt for security, and you should also use a random number generator intended for cryptographic purposes like RNGCryptoServiceProvider
, rather than a pseudo-random number generator like Random
.
public static CryptoStream CreateEncryptionStream(byte[] key, Stream outputStream)
{
byte[] iv = new byte[ivSize];
using (var rng = new RNGCryptoServiceProvider())
{
// Using a cryptographic random number generator
rng.GetNonZeroBytes(iv);
}
// Write IV to the start of the stream
outputStream.Write(iv, 0, iv.Length);
Rijndael rijndael = new RijndaelManaged();
rijndael.KeySize = keySize;
CryptoStream encryptor = new CryptoStream(
outputStream,
rijndael.CreateEncryptor(key, iv),
CryptoStreamMode.Write);
return encryptor;
}
public static CryptoStream CreateDecryptionStream(byte[] key, Stream inputStream)
{
byte[] iv = new byte[ivSize];
if (inputStream.Read(iv, 0, iv.Length) != iv.Length)
{
throw new ApplicationException("Failed to read IV from stream.");
}
Rijndael rijndael = new RijndaelManaged();
rijndael.KeySize = keySize;
CryptoStream decryptor = new CryptoStream(
inputStream,
rijndael.CreateDecryptor(key, iv),
CryptoStreamMode.Read);
return decryptor;
}
Finally, we can glue it together:
byte[] key = Convert.FromBase64String(cryptoKey);
using (FileStream file = new FileStream(Environment.CurrentDirectory + @"class.dat", FileMode.Create))
using (CryptoStream cryptoStream = CreateEncryptionStream(key, file))
{
WriteObjectToStream(cryptoStream, myVarClass);
}
MyClass newMyVarClass;
using (FileStream file = new FileStream(Environment.CurrentDirectory + @"class.dat", FileMode.Open))
using (CryptoStream cryptoStream = CreateDecryptionStream(key, file))
{
newMyVarClass = (MyClass)ReadObjectFromStream(cryptoStream);
}
Note that we pass the file
stream object to CreateEncryptionStream
(and CreateDecryptionStream
), and then pass the cryptoStream
object to WriteObjectToStream
(and ReadObjectfromStream
). You'll also notice that the streams are scoped inside using
blocks, so that they'll automatically be cleaned up when we're finished with them.
Here's the full test program:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
namespace CryptoStreams
{
class Program
{
[Serializable]
public class MyClass
{
public string TestValue
{
get;
set;
}
public int SomeInt
{
get;
set;
}
}
public static void WriteObjectToStream(Stream outputStream, Object obj)
{
if (object.ReferenceEquals(null, obj))
{
return;
}
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(outputStream, obj);
}
public static object ReadObjectFromStream(Stream inputStream)
{
BinaryFormatter binForm = new BinaryFormatter();
object obj = binForm.Deserialize(inputStream);
return obj;
}
private const string cryptoKey =
"Q3JpcHRvZ3JhZmlhcyBjb20gUmluamRhZWwgLyBBRVM=";
private const int keySize = 256;
private const int ivSize = 16; // block size is 128-bit
public static CryptoStream CreateEncryptionStream(byte[] key, Stream outputStream)
{
byte[] iv = new byte[ivSize];
using (var rng = new RNGCryptoServiceProvider())
{
// Using a cryptographic random number generator
rng.GetNonZeroBytes(iv);
}
// Write IV to the start of the stream
outputStream.Write(iv, 0, iv.Length);
Rijndael rijndael = new RijndaelManaged();
rijndael.KeySize = keySize;
CryptoStream encryptor = new CryptoStream(
outputStream,
rijndael.CreateEncryptor(key, iv),
CryptoStreamMode.Write);
return encryptor;
}
public static CryptoStream CreateDecryptionStream(byte[] key, Stream inputStream)
{
byte[] iv = new byte[ivSize];
if (inputStream.Read(iv, 0, iv.Length) != iv.Length)
{
throw new ApplicationException("Failed to read IV from stream.");
}
Rijndael rijndael = new RijndaelManaged();
rijndael.KeySize = keySize;
CryptoStream decryptor = new CryptoStream(
inputStream,
rijndael.CreateDecryptor(key, iv),
CryptoStreamMode.Read);
return decryptor;
}
static void Main(string[] args)
{
MyClass myVarClass = new MyClass
{
SomeInt = 1234,
TestValue = "Hello"
};
byte[] key = Convert.FromBase64String(cryptoKey);
using (FileStream file = new FileStream(Environment.CurrentDirectory + @"class.dat", FileMode.Create))
{
using (CryptoStream cryptoStream = CreateEncryptionStream(key, file))
{
WriteObjectToStream(cryptoStream, myVarClass);
}
}
MyClass newMyVarClass;
using (FileStream file = new FileStream(Environment.CurrentDirectory + @"class.dat", FileMode.Open))
using (CryptoStream cryptoStream = CreateDecryptionStream(key, file))
{
newMyVarClass = (MyClass)ReadObjectFromStream(cryptoStream);
}
Console.WriteLine("newMyVarClass.SomeInt: {0}; newMyVarClass.TestValue: {1}",
newMyVarClass.SomeInt,
newMyVarClass.TestValue);
}
}
}