[Unity] C# archive and encryption

I changed my job, now it is developed purely in C#, and it is a stand-alone game, so I have to study the archive method of C# again. After a period of time to solve various problems, it is now stable, and the veterans who need it can refer to it.

1. Import ProtoBuf

https://github.com/protocolbuffers/protobuf/releases/
Download the required language, unzip it and import it into your own directory.

2. Agreement statement

[ProtoContract]
    public class DataRoot
    {
    
    
        [ProtoMember(1)]
        public int sA;
        [ProtoMember(2)]
        public string sB;
        [ProtoMember(3)]
        public float[] sC;
        [ProtoMember(4)]
        public DataA sD;
        [ProtoMember(5)]
        public List<DataA> sE;
    }

    [ProtoContract]
    public class DataA
    {
    
    
        [ProtoMember(1)]
        public int sA1;
        [ProtoMember(2)]
        public int sA2;
    }
  • ProtoContract declares the class to be serialized
  • ProtoMember declares the member to be serialized

3. Archive

data generation

    DataRoot data = new DataRoot()
    {
    
    
        sA = 0,
        sB = "AAA",
        sC = new float[2] {
    
     1, 2 },
        sE = null,
    };
    data.sD = new DataA();

    Debug.Log("保存存档完成");

Package and save

    //将内容进行封包
    File.Delete(Application.persistentDataPath + "/Save.dat");
    FileStream stream = File.OpenWrite(Application.persistentDataPath + "/Save.dat");
    Serializer.Serialize(stream, data);
    stream.Dispose();
  • ProtoBuf.Serializer.Serialize The serialization method that comes with protobuf converts data into byte[]
  • The writing method of protobuf is to overwrite bytes.length into the file, and finally use ProtoReader.TO_EOF as the terminator to add to the next bit. That is to say, the bytes.length of the file is always kept at the maximum length. In the project, I don’t know why, after the byte[] of the archive becomes shorter, there is always a problem with this terminator, which leads to an error in reading the archive, so I simply delete the original archive file every time I archive.

4. Load file

read and unpack

    private void TryDeserialize(out DataRoot data)
    {
    
    
        if (File.Exists(Application.persistentDataPath + "/Save.dat"))
        {
    
    
			
			//↓↓↓这一部分比较特殊 根据需要判断是否要添加↓↓↓
            Support.SetInstanceFunc((Type type) =>
            {
    
    
                if (Type.GetType(type.FullName) == null)
                {
    
    
                    return null;
                }

                return Activator.CreateInstance(type
#if !(CF || SILVERLIGHT || WINRT || PORTABLE || NETSTANDARD1_3 || NETSTANDARD1_4)
                        , nonPublic: true
#endif
                    );
            });
            //↑↑↑这一部分比较特殊 根据需要判断是否要添加↑↑↑
            
            //对内容进行解包
            FileStream stream = File.OpenRead(Application.persistentDataPath + "/Save.dat");
            data = Serializer.Deserialize<DataRoot>(stream);
            stream.Dispose();
        }
        else
        {
    
    
            data = null;
        }
		
		//↓↓↓这一部分比较特殊 根据需要判断是否要添加↓↓↓
		Support.SetInstanceFunc(null);
		//↑↑↑这一部分比较特殊 根据需要判断是否要添加↑↑↑
    }
  • ProtoBuf.Serializer.Deserialize<T> The deserialization method that comes with protobuf converts byte[] into data
    private static Func<Type, object> instanceFunc;
    public static void SetInstanceFunc(Func<Type, object> func)
    {
    
    
        instanceFunc = func;
    }

    public static object CreateInstance(Type type)
    {
    
    
        if (instanceFunc != null)
        {
    
    
            object obj = instanceFunc(type);
            if (obj != null)
            {
    
    
                return obj;
            }
        }
        // ReSharper disable once AssignNullToNotNullAttribute
        if (Type.GetType(type.FullName) == null)
        {
    
    
            return _appDomain.Instantiate(type.FullName);
        }

        return Activator.CreateInstance(type
#if !(CF || SILVERLIGHT || WINRT || PORTABLE || NETSTANDARD1_3 || NETSTANDARD1_4)
            , nonPublic: true
#endif
        );
  • Because our project involves hot update of C# code, the archive manager is in the hot update assembly, and protobuf is placed in a deeper assembly because of other content needs, and this assembly does not refer to hot update assembly, so when unpacking, the class in the hot-updated assembly cannot be found. I had no choice but to add a method to the ProtoBuf.Support class. In the CreateInstance method of Support, check out the instantiated method, and instantiate it from the hot-updated assembly when unpacking.

Data Recovery

    public void Load()
    {
    
    
        TryDeserialize(out DataRoot data);

        if (data != null)
        {
    
    
            int _sA = data.sA;
            string _sB = data.sB;
            float[] _sC = data.sC;
            DataA _sD = data.sD;
            List<DataA> _sE = data.sE;
            if (_sE != null)
            {
    
    
                //TODO
            }
            Debug.Log("加载存档完成");
        }
        else
        {
    
    
            //TODO 没有存档,可以执行如注册或新手引导之类的方法
        }
    }
  • For List and ArrayList, if the length of the data is 0 when archiving, the unpacked data is null, pay attention to judge this situation.

5. Encryption

AESCryptionUtility

Here is a code that uses AES encryption and decryption, which supports encryption and decryption of string and byte[]. When encrypting and decrypting, you need to pass in a string of length 32 as the key.

    /// <summary>
    /// AES加解密字符串
    /// </summary>
    public static class AESCryptionUtility
    {
    
    
        /// <summary>
        /// AES加密 String
        /// </summary>
        /// <param name="str">被加密的明文</param>
        /// <param name="key">密钥</param>
        /// <returns>密文</returns>
        public static string Encrypt(string str, string key)
        {
    
    
            MemoryStream mStream = new MemoryStream();
            RijndaelManaged aes = new RijndaelManaged();

            byte[] plainbytes = Encoding.UTF8.GetBytes(str);
            byte[] bKey = new byte[32];
            Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(bKey.Length)), bKey, bKey.Length);

            aes.Mode = CipherMode.ECB;
            aes.Padding = PaddingMode.PKCS7;
            aes.KeySize = 128;
            //aes.Key = _key;
            aes.Key = bKey;
            //aes.IV = _iV;
            CryptoStream cryptoStream = new CryptoStream(mStream, aes.CreateEncryptor(), CryptoStreamMode.Write);
            try
            {
    
    
                cryptoStream.Write(plainbytes, 0, plainbytes.Length);
                cryptoStream.FlushFinalBlock();
                return Convert.ToBase64String(mStream.ToArray());
            }
            finally
            {
    
    
                cryptoStream.Close();
                mStream.Close();
                aes.Clear();
            }
        }

        /// <summary>
        /// AES加密 Bytes
        /// </summary>
        /// <param name="bytes">被加密的明文bytes</param>
        /// <param name="key">密钥</param>
        /// <returns>密文</returns>
        public static byte[] Encrypt(byte[] bytes, string key)
        {
    
    
            MemoryStream mStream = new MemoryStream();
            RijndaelManaged aes = new RijndaelManaged();

            byte[] bKey = new byte[32];
            Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(bKey.Length)), bKey, bKey.Length);

            aes.Mode = CipherMode.ECB;
            aes.Padding = PaddingMode.PKCS7;
            aes.KeySize = 128;
            //aes.Key = _key;
            aes.Key = bKey;
            //aes.IV = _iV;
            CryptoStream cryptoStream = new CryptoStream(mStream, aes.CreateEncryptor(), CryptoStreamMode.Write);
            try
            {
    
    
                cryptoStream.Write(bytes, 0, bytes.Length);
                cryptoStream.FlushFinalBlock();
                return mStream.ToArray();
            }
            finally
            {
    
    
                cryptoStream.Close();
                mStream.Close();
                aes.Clear();
            }
        }

        /// <summary>
        /// AES解密 String
        /// </summary>
        /// <param name="str">被加密的明文</param>
        /// <param name="key">密钥</param>
        /// <returns>明文</returns>
        public static string Decrypt(string str, string key)
        {
    
    
            byte[] encryptedbytes = Convert.FromBase64String(str);
            byte[] bKey = new byte[32];
            Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(bKey.Length)), bKey, bKey.Length);

            MemoryStream mStream = new MemoryStream(encryptedbytes);
            //mStream.Write( encryptedbytes, 0, encryptedbytes.Length );
            //mStream.Seek( 0, SeekOrigin.Begin );
            RijndaelManaged aes = new RijndaelManaged();
            aes.Mode = CipherMode.ECB;
            aes.Padding = PaddingMode.PKCS7;
            aes.KeySize = 128;
            aes.Key = bKey;
            //aes.IV = _iV;
            CryptoStream cryptoStream = new CryptoStream(mStream, aes.CreateDecryptor(), CryptoStreamMode.Read);
            try
            {
    
    
                byte[] tmp = new byte[encryptedbytes.Length + 32];
                int len = cryptoStream.Read(tmp, 0, encryptedbytes.Length + 32);
                byte[] ret = new byte[len];
                Array.Copy(tmp, 0, ret, 0, len);
                return Encoding.UTF8.GetString(ret);
            }
            finally
            {
    
    
                cryptoStream.Close();
                mStream.Close();
                aes.Clear();
            }
        }

        /// <summary>
        /// AES解密 Bytes
        /// </summary>
        /// <param name="bytes">被加密的明文bytes</param>
        /// <param name="key">密钥</param>
        /// <returns>明文</returns>
        public static byte[] Decrypt(byte[] bytes, string key)
        {
    
    
            byte[] bKey = new byte[32];
            Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(bKey.Length)), bKey, bKey.Length);

            MemoryStream mStream = new MemoryStream(bytes);
            //mStream.Write( encryptedbytes, 0, encryptedbytes.Length );
            //mStream.Seek( 0, SeekOrigin.Begin );
            RijndaelManaged aes = new RijndaelManaged();
            aes.Mode = CipherMode.ECB;
            aes.Padding = PaddingMode.PKCS7;
            aes.KeySize = 128;
            aes.Key = bKey;
            //aes.IV = _iV;
            CryptoStream cryptoStream = new CryptoStream(mStream, aes.CreateDecryptor(), CryptoStreamMode.Read);
            try
            {
    
    
                byte[] tmp = new byte[bytes.Length + 32];
                int len = cryptoStream.Read(tmp, 0, bytes.Length + 32);
                byte[] ret = new byte[len];
                Array.Copy(tmp, 0, ret, 0, len);
                return ret;
            }
            finally
            {
    
    
                cryptoStream.Close();
                mStream.Close();
                aes.Clear();
            }
        }
    }

Related parameters

    private readonly string cryptionKey = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
    private bool isCrtption;
  • cryptionKey AES key, random characters with a length of 32 bits
  • isCrtption Whether to encrypt, used as archive Debug

Archive encryption

	//加密
    if (isCrtption)
    {
    
    
        //将内容进行封包
        FileStream stream = File.OpenWrite(Application.persistentDataPath + "/tempS");
        Serializer.Serialize(stream, data);
        stream.Dispose();
        //加载文件Bytes
        FileStream reader = new FileStream(Application.persistentDataPath + "/tempS", FileMode.Open, FileAccess.Read);
        byte[] fileBytes = new byte[reader.Length];
        reader.Read(fileBytes, 0, fileBytes.Length);
        reader.Dispose();
        //将内容加密
        fileBytes = AESCryptionUtility.Encrypt(fileBytes, cryptionKey);
        //保存存档
        File.Delete(Application.persistentDataPath + "/Save.dat");
        FileStream writer = new FileStream(Application.persistentDataPath + "/Save.dat", FileMode.OpenOrCreate, FileAccess.Write);
        writer.Write(fileBytes, 0, fileBytes.Length);
        writer.Dispose();
        //清理
        File.Delete(Application.persistentDataPath + "/tempS");
    }
    else
    {
    
    
        //将内容进行封包
        File.Delete(Application.persistentDataPath + "/Save.dat");
        FileStream stream = File.OpenWrite(Application.persistentDataPath + "/Save.dat");
        Serializer.Serialize(stream, data);
        stream.Dispose();
    }
  1. First serialize and save the data to a temporary file to prevent problems when operating on the same file.
  2. Read byte[] in temporary file
  3. Encrypt the content
  4. Save the encrypted byte[] to the real archive file
  5. delete temporary files

archive decryption


	//解密
	if (isCrtption)
    {
    
    
        //加载文件Bytes
        FileStream reader = new FileStream(Application.persistentDataPath + "/Save.dat", FileMode.Open, FileAccess.Read);
        byte[] fileBytes = new byte[reader.Length];
        reader.Read(fileBytes, 0, fileBytes.Length);
        reader.Dispose();
        //将内容解密
        fileBytes = AESCryptionUtility.Decrypt(fileBytes, cryptionKey);
        //写道临时文件中
        FileStream writer = new FileStream(Application.persistentDataPath + "/tempS", FileMode.OpenOrCreate, FileAccess.Write);
        writer.Write(fileBytes, 0, fileBytes.Length);
        writer.Dispose();
        //对内容进行解包
        FileStream stream = File.OpenRead(Application.persistentDataPath + "/tempS");
        data = Serializer.Deserialize<DataRoot>(stream);
        stream.Dispose();
        //清理
        File.Delete(Application.persistentDataPath + "/tempS");
    }
    else
    {
    
    
        //对内容进行解包
        FileStream stream = File.OpenRead(Application.persistentDataPath + "/Save.dat");
        data = Serializer.Deserialize<DataRoot>(stream);
        stream.Dispose();
    }
  1. Read the encrypted byte[] first
  2. Decrypt the content
  3. Write the decrypted byte[] to a temporary file. Because if there is no further archive, it is necessary to ensure that the original archive is correct, so the content cannot be overwritten in the original archive.
  4. Deserialize the decrypted byte[] into real data
  5. delete temporary files

Guess you like

Origin blog.csdn.net/qql7267/article/details/128785873