使用 C# 中的 ProtoBuf-Net 库将类数据保存到加密文件

Posted

技术标签:

【中文标题】使用 C# 中的 ProtoBuf-Net 库将类数据保存到加密文件【英文标题】:Saving classes data to an encrypted file using ProtoBuf-Net library in C# 【发布时间】:2021-09-03 23:10:53 【问题描述】:

我正在开发一个使用 ProtoBuf-Net 库将类数据保存到二进制文件的应用程序。我希望保存文件的内容是人类无法读取的,但是当我在 Notepad++ 中打开这个二进制文件时,可以读取类中填充的值。我应该在下面的代码中添加什么来加密类数据并使所有内容都无法被人类读取?

using ProtoBuf;
using System.IO;

namespace TestProtoBufNet

    class Program
    
        static void Main(string[] args)
        
            var person = new Person
            
                Id = 12345,
                Name = "Fred",
                Address = new Address
                
                    Line1 = "Flat 1",
                    Line2 = "The Meadows"
                
            ;
            using (var file = File.Create("C:\\Temp\\person.bin"))
            
                Serializer.Serialize(file, person);
            
        

        [ProtoContract]
        class Person
        
            [ProtoMember(1)]
            public int Id  get; set; 
            [ProtoMember(2)]
            public string Name  get; set; 
            [ProtoMember(3)]
            public Address Address  get; set; 
        

        [ProtoContract]
        class Address
        
            [ProtoMember(1)]
            public string Line1  get; set; 
            [ProtoMember(2)]
            public string Line2  get; set; 
        
    

【问题讨论】:

加密它? docs.microsoft.com/en-us/dotnet/standard/security/… Protobuf 没有加密。它们是完全独立的东西,protobuf-net 没有任何相同的加密设置。Json.NET 和 XmlSerializer 没有加密设置。如果您想加密某些东西,那么正如@TheGeneral 所说:加密它。但是:分开。如果您使用的加密 API 提供了 Stream API,那么您可以在 FileStream 和序列化程序之间使用该 Stream,然后就完成了。 【参考方案1】:

根据我发现的@TheGeneral 评论和this article,我可以构建以下解决方案:

using ProtoBuf;
using System;
using System.IO;
using System.Security.Cryptography;

namespace TestProtoBufNet

    class Program
    
        // IMPORTANT:
        //  This cryptographic key is defined in code for demonstration purposes.
        //  Production keys should be stored in a secure location,
        //  (such as Azure Key Vault or AWS KMS) or protected using .NET's 
        //  ProtectedData class.
        private static readonly byte[] secretKey = new byte[]
                               
                                87, 167, 103, 151, 197, 100, 254, 130,
                                74,  59,  51,  28,  26, 230,   7,  97,
                                137, 224,  69,  23,  51, 110,   3,  37,
                                157,  41,  12,  12, 158,  24,  30,  86
                               ;

        static void Main(string[] args)
        
            Person person = new Person
            
                Id = 12345,
                Name = "Fred",
                Address = new Address
                
                    Line1 = "Flat 1",
                    Line2 = "The Meadows"
                
            ;

            string filePath = "C:\\Temp\\person.bin";
            CryptoSerializer<Person> cryptoSerializer = new CryptoSerializer<Person>(secretKey);

            //Write person class data to a binary file.
            using (FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate))
            
                cryptoSerializer.Serialize(person, fileStream);
            

            //Reads the binary file and displays its contents in the console.
            using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
            
                Person personRead = cryptoSerializer.Deserialize(fileStream);

                Console.WriteLine("Data read from " + filePath + ":");
                Console.WriteLine("\tId\t:\t" + personRead.Id);
                Console.WriteLine("\tName\t:\t" + personRead.Name);
                Console.WriteLine("\tLine1\t:\t" + personRead.Address.Line1);
                Console.WriteLine("\tLine1\t:\t" + personRead.Address.Line2);
                Console.Write("Press any key to close...");
                Console.ReadLine();
            
        

        [ProtoContract]
        class Person
        
            [ProtoMember(1)]
            public int Id  get; set; 
            [ProtoMember(2)]
            public string Name  get; set; 
            [ProtoMember(3)]
            public Address Address  get; set; 
        

        [ProtoContract]
        class Address
        
            [ProtoMember(1)]
            public string Line1  get; set; 
            [ProtoMember(2)]
            public string Line2  get; set; 
        
    

    /// <summary>
    /// Serializer to encrypt/decrypt objects using AES.
    /// </summary>
    /// <typeparam name="T">Type of object to serialize/deserialize.</typeparam>
    public class CryptoSerializer<T>
    
        private byte[] _secretKey;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="secretKey">
        /// Secret key. Legal AES keys are 16, 24, or 32 bytes long.
        /// </param>
        public CryptoSerializer(byte[] secretKey)
        
            _secretKey = secretKey;
        

        /// <summary>
        /// Serialization callback that can be registered with 
        /// a cache using CacheBuilder.SetSerialization
        /// </summary>
        public void Serialize(T obj, Stream stream)
        
            // The first 16 bytes of the serialized stream is the 
            // AES initialization vector. (An IV does not need to be
            // secret, but the same IV should never be used twice with
            // the same key.)
            byte[] iv = GenerateRandomBytes(16);
            stream.Write(iv, 0, 16);

            using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
            
                aes.Key = _secretKey;
                aes.IV = iv;

                CryptoStream cryptoStream = new CryptoStream(stream,
                                                             aes.CreateEncryptor(),
                                                             CryptoStreamMode.Write);

                // Using protobuf-net for serialization
                // (but any serializer can be used to write to this CryptoStream).
                ProtoBuf.Serializer.Serialize(cryptoStream, obj);

                cryptoStream.FlushFinalBlock();
            
        

        /// <summary>
        /// Deserialization callback that can be registered with 
        /// a cache using CacheBuilder.SetSerialization
        /// </summary>
        public T Deserialize(Stream stream)
        
            // First 16 bytes is the initialization vector.
            byte[] iv = new byte[16];
            stream.Read(iv, 0, 16);

            using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
            
                aes.Key = _secretKey;
                aes.IV = iv;

                CryptoStream cryptoStream = new CryptoStream(stream,
                                                             aes.CreateDecryptor(),
                                                             CryptoStreamMode.Read);

                return ProtoBuf.Serializer.Deserialize<T>(cryptoStream);
            
        

        // This RNG is thread safe. Used to generate IV.
        private static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

        private static byte[] GenerateRandomBytes(int length)
        
            byte[] randomBytes = new byte[length];
            rng.GetBytes(randomBytes);
            return randomBytes;
        
    


【讨论】:

以上是关于使用 C# 中的 ProtoBuf-Net 库将类数据保存到加密文件的主要内容,如果未能解决你的问题,请参考以下文章

C++ 中的 Google ProtoBuf 与 C# (UDP) 中的 Protobuf-net 聊天

C# protobuf-net - 默认值覆盖 protobuf 数据中的值

使用映射库将嵌套对象映射到 C# 中的自定义对象

C# Protobuf-net:如何从网络流中连续反序列化?

关于在 C# 中使用 Protobuf-Net

protobuf-net - 反引号、字典和 .proto 文件