Encrypt .NET binary serialization stream

前端 未结 2 967
独厮守ぢ
独厮守ぢ 2021-01-06 09:19

I\'m studying encryption in C# and I\'m having trouble. I have some Rijndael encryption code and it\'s working perfectly with strings. But now I\'m studying serialization an

2条回答
  •  温柔的废话
    2021-01-06 09:28

    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);
            }
        }
    }
    

提交回复
热议问题