加密 .NET 二进制序列化流

Posted

技术标签:

【中文标题】加密 .NET 二进制序列化流【英文标题】:Encrypt .NET binary serialization stream 【发布时间】:2015-03-01 07:27:51 【问题描述】:

我正在学习 C# 中的加密,但遇到了麻烦。我有一些Rijndael encryption code,它与字符串完美配合。但现在我正在研究序列化,BinaryWriter 写入类的数据而没有任何保护。我正在使用this code to test;有没有办法“加密类”或类似的东西?

为了澄清这个问题,这是我的代码:

FileStream file = new FileStream(Environment.CurrentDirectory + @"\class.dat", FileMode.Create);
using (BinaryWriter sw = new BinaryWriter(file))

    byte[] byt = ConverteObjectEmByte(myVarClass);
    sw.Write(byt);

这就是我的阅读方式:

MyClass newMyVarClass;
FileStream file = new FileStream(Environment.CurrentDirectory + @"\class.dat", FileMode.Open);
using (BinaryReader sr = new BinaryReader(file))

    // 218 is the size of the byte array that I've tested (byt)
    myNewVarClass = (MyClass)ConverteByteEmObject(sr.ReadBytes(218));

谢谢!

【问题讨论】:

你可以在 ConverteObjectEmByte 和 ConverteByteEmObject 中使用CryptoStream。在链接中有 Rijndael 的示例... 【参考方案1】:

在传递到不同的流对象时,您可以将多个流链接在一起,将输出从一个传递到另一个的输入,而不是将转换为 byte[] 作为中间步骤。

这种方法在这里很有意义,因为您将链接在一起

二进制序列化 => 加密 => 写入文件

考虑到这一点,您可以将ConvertObjectEmByte 更改为:

public static void WriteObjectToStream(Stream outputStream, Object obj)

    if (object.ReferenceEquals(null, obj))
    
        return;
    

    BinaryFormatter bf = new BinaryFormatter();
    bf.Serialize(outputStream, obj);

同样,ConvertByteEmObject 可以变成:

public static object ReadObjectFromStream(Stream inputStream)

    BinaryFormatter binForm = new BinaryFormatter();
    object obj = binForm.Deserialize(inputStream);
    return obj;

要添加加密/解密,我们可以编写函数来创建CryptoStream 对象,我们可以将这些对象与这些二进制序列化函数链接起来。我下面的示例函数看起来与您链接到的文章中的 Encrypt/Decrypt 函数有点不同,因为 IV (Initialization Vector) 现在是随机生成并写入流(并从另一端的流中读取)。重要的是,IV 对于您加密的每个数据块都是唯一的,以确保安全,您还应该使用像 RNGCryptoServiceProvider 这样的用于加密目的的随机数生成器,而不是像 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;

最后,我们可以将它们粘合在一起:

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

请注意,我们将file 流对象传递给CreateEncryptionStream(和CreateDecryptionStream),然后将cryptoStream 对象传递给WriteObjectToStream(和ReadObjectfromStream)。您还会注意到流的作用域在 using 块内,因此当我们完成它们时会自动清理它们。

这是完整的测试程序:

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

【讨论】:

非常感谢!!我在这里进行了测试,它可以完美地与不止一个类一起使用。只是一个问题,出于安全原因,我更改了cryptoKey,但我不知道这个bIV是什么,这是可变的吗?再次,谢谢! :D 这是个好问题。为了安全起见,您加密的每个文件的 IV 应该是唯一的(不要再次使用相同的 IV)。我已经更新了示例以显示它是随机生成并写入文件和从文件中读取的,这将是通常的方法。 我尝试了更改,但我不明白如何生成新的 IV。怎么办? 您是否看到我的示例的更新,我在其中随机生成了 IV?【参考方案2】:

我不确定 .Net 库是否已更改或只是代码错误。我不能直接运行软件编写的代码。 从那以后,我根据答案更改了代码,以便可以正确使用它。这是一个例子。

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

【讨论】:

cryptoKey 的长度由加密算法的 KeySize 决定。 256(位)的 KeySize 产生 32(字节)的密钥。如果你想要一个 64 字节的密钥,你必须选择一个 KeySize 为 512 的算法。

以上是关于加密 .NET 二进制序列化流的主要内容,如果未能解决你的问题,请参考以下文章

IOS 序列化与反序列化NSKeyedUnarchiver

C#.NET对象深拷贝

Java IO流--对象流及对象序列化机制的理解

对象序列化

JAVA 对象序列化

java——解决java.io.StreamCorruptedException: invalid stream header: xxx