Encrypt .NET binary serialization stream

前端 未结 2 952
独厮守ぢ
独厮守ぢ 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);
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-06 09:50

    I'm not sure whether the .Net Library had changed or just the code is wrong. I can't directly run the code written by softwariness. Since that, I changed the code based on the answer so that it can be used correctly. Here's an example.

    public class CryptoSerialization
    {
        public static void WriteObjectToStream(Stream outputStream, object obj)
        {
            if (obj is null) throw new ArgumentNullException("obj can't be null");
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(outputStream, obj);
        }
    
        public static object ReadObjectFromStream(Stream inputStream)
        {
            BinaryFormatter bf = new BinaryFormatter();
            return bf.Deserialize(inputStream);
        }
    
        public static CryptoStream CreateEncryptionStream(Stream outputStream, byte[] Key, byte[] IV)
        {
            Rijndael rijndael = new RijndaelManaged();
    
            return new CryptoStream(outputStream, rijndael.CreateEncryptor(Key, IV), CryptoStreamMode.Write);
        }
    
        public static CryptoStream CreateDecryptionStream(Stream inputStream, byte[] Key, byte[] IV)
        {
            Rijndael rijndael = new RijndaelManaged();
    
            return new CryptoStream(inputStream, rijndael.CreateDecryptor(Key, IV), CryptoStreamMode.Read);
        }
    
        public static void EncryptObjectToFile(object obj, string path, byte[] Key, byte[] IV)
        {
            using FileStream file = new FileStream(path, FileMode.Create);
            using (CryptoStream cryptoStream = CreateEncryptionStream(file, Key, IV))
            {
                WriteObjectToStream(cryptoStream, obj);
            }
        }
    
        public static object DecryptObjectFromFile(string path, byte[] Key, byte[] IV)
        {
            using FileStream file = new FileStream(path, FileMode.Open);
            using (CryptoStream cryptoStream = CreateDecryptionStream(file, Key, IV))
            {
                return ReadObjectFromStream(cryptoStream);
            }
        }
    }
    
    [Serializable]
    public class Student
    {
        public string Name;
        public int Age;
    }
    
    static async Task Main(string[] args)
    {
        // the original string "[This is an example key string!]";
        // I don't know if the length of the string has to be 32, but when I tried 64, it went wrong.
        string cryptoKey = "W1RoaXMgaXMgYW4gZXhhbXBsZSBrZXkgc3RyaW5nIV0=";
        byte[] Key = Convert.FromBase64String(cryptoKey);
        byte[] IV = new byte[16];
        using (RNGCryptoServiceProvider rngcsp = new RNGCryptoServiceProvider())
        {
            rngcsp.GetBytes(IV);
        }
        //same as
        //Rijndael rijndael = new RijndaelManaged();
        //rijndael.GenerateIV();
        //byte[] iv = rijndael.IV;
        List<Student> students = new List<Student>() { new Student { Name = "John", Age = 10 }, new Student { Name = "Marry", Age = 15 } };
        CryptoSerialization.EncryptObjectToFile(students, Environment.CurrentDirectory + @"\testCrypto.dat", Key, IV);
    
        List<Student> newStudents = (List<Student>)CryptoSerialization.DecryptObjectFromFile(Environment.CurrentDirectory + @"\testCrypto.dat", Key, IV);
    
        newStudents.ForEach((stu) =>
        {
            Console.WriteLine(stu.Name + ", " + stu.Age);
        });
        Console.ReadKey();
    }
    
    0 讨论(0)
提交回复
热议问题