找回密码
 立即注册
查看: 1107|回复: 5

[简易教程] 如何加密你的游戏数据变量

[复制链接]
发表于 2018-12-25 11:05 | 显示全部楼层 |阅读模式
Unity 3D: How to Secure Your Player Preferences
1st May 2014 by @developingsoft

Have you ever wondered why people seem to get really high scores on the iOS leaderboards? Its probably because the developer forgot to encrypt their Unity player preferences. In this article I will show you a simple way to encrypt the player preferences so that users can’t cheat or bypass in-app purchases.


Unity PlayerPrefs Screenshot

Figure 1: A before and after of Unity player preferences on iOS

AES, DES or 3DES Encryption?
The most secure encryption algorithm today is AES 256. For the purpose of encrypting the player preferences I recommend DES. The reason is because it means you don’t have to get an export license when submitting your game to iTunes.

The Code
In this example there are two classes. DESEncryption for handling the Encrypt/Decrypt functionality and SecurePlayerPrefs which is a secure implementation of the Unity engines built in PlayerPrefs.

Encryption Class
using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;

public interface IEncryption
{
    string Encrypt(string plainText, string password);
    bool TryDecrypt(string cipherText, string password, out string plainText);
}

public class DESEncryption : IEncryption
{
    const int Iterations = 1000;

    public string Encrypt(string plainText, string password)
    {
        if (plainText == null)
        {
            throw new ArgumentNullException("plainText");
        }

        if (string.IsNullOrEmpty(password))
        {
            throw new ArgumentNullException("password");
        }

        // create instance of the DES crypto provider
        var des = new DESCryptoServiceProvider();

        // generate a random IV will be used a salt value for generating key
        des.GenerateIV();

        // use derive bytes to generate a key from the password and IV
        var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, des.IV, Iterations);

        // generate a key from the password provided
        byte[] key = rfc2898DeriveBytes.GetBytes(8);

        // encrypt the plainText
        using (var memoryStream = new MemoryStream())
        using (var cryptoStream = new CryptoStream(memoryStream, des.CreateEncryptor(key, des.IV), CryptoStreamMode.Write))
        {
            // write the salt first not encrypted
            memoryStream.Write(des.IV, 0, des.IV.Length);

            // convert the plain text string into a byte array
            byte[] bytes = Encoding.UTF8.GetBytes(plainText);

            // write the bytes into the crypto stream so that they are encrypted bytes
            cryptoStream.Write(bytes, 0, bytes.Length);
            cryptoStream.FlushFinalBlock();

            return Convert.ToBase64String(memoryStream.ToArray());
        }
    }

    public bool TryDecrypt(string cipherText, string password, out string plainText)
    {
        // its pointless trying to decrypt if the cipher text
        // or password has not been supplied
        if (string.IsNullOrEmpty(cipherText) ||
            string.IsNullOrEmpty(password))
        {
            plainText = "";
            return false;
        }

        try
        {   
            byte[] cipherBytes = Convert.FromBase64String(cipherText);

            using (var memoryStream = new MemoryStream(cipherBytes))
            {
                // create instance of the DES crypto provider
                var des = new DESCryptoServiceProvider();

                // get the IV
                byte[] iv = new byte[8];
                memoryStream.Read(iv, 0, iv.Length);

                // use derive bytes to generate key from password and IV
                var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, iv, Iterations);

                byte[] key = rfc2898DeriveBytes.GetBytes(8);

                using (var cryptoStream = new CryptoStream(memoryStream, des.CreateDecryptor(key, iv), CryptoStreamMode.Read))
                using (var streamReader = new StreamReader(cryptoStream))
                {
                    plainText = streamReader.ReadToEnd();
                    return true;
                }
            }
        }
        catch(Exception ex)
        {
            // TODO: log exception
            Console.WriteLine(ex);

            plainText = "";
            return false;
        }
    }
}
The above DESEncryption class is a DES implementation that has two methods. Encrypt and TryDecrypt. The Encrypt method generates a random IV so that the data stored on disk is always different, even if the data to encrypt is the same. It does this by generating a random key from the IV and user supplied password. The IV is then stored with the encrypted data so that it can be used by the TryDecrypt function.

SecurePlayerPrefs
using UnityEngine;
using MadPike.Security;
using System.Security.Cryptography;
using System.Text;

public static class SecurePlayerPrefs
{
    public static void SetString(string key, string value, string password)
    {
        var desEncryption = new DESEncryption();
        string hashedKey = GenerateMD5(key);
        string encryptedValue = desEncryption.Encrypt(value, password);
        PlayerPrefs.SetString(hashedKey, encryptedValue);
    }

    public static string GetString(string key, string password)
    {
        string hashedKey = GenerateMD5(key);
        if (PlayerPrefs.HasKey(hashedKey))
        {
            var desEncryption = new DESEncryption();
            string encryptedValue = PlayerPrefs.GetString(hashedKey);
            string decryptedValue;
            desEncryption.TryDecrypt(encryptedValue, password, out decryptedValue);
            return decryptedValue;
        }
        else
        {
            return "";
        }
    }

    public static string GetString(string key, string defaultValue, string password)
    {
        if (HasKey(key))
        {
            return GetString(key, password);
        }
        else
        {
            return defaultValue;
        }
    }

    public static bool HasKey(string key)
    {
        string hashedKey = GenerateMD5(key);
        bool hasKey = PlayerPrefs.HasKey(hashedKey);
        return hasKey;
    }

    /// <summary>
    /// Generates an MD5 hash of the given text.
    /// WARNING. Not safe for storing passwords
    /// </summary>
    /// <returns>MD5 Hashed string</returns>
    /// <param name="text">The text to hash</param>
    static string GenerateMD5(string text)
    {
        var md5 = MD5.Create();
        byte[] inputBytes = Encoding.UTF8.GetBytes(text);
        byte[] hash = md5.ComputeHash(inputBytes);

        // step 2, convert byte array to hex string
        var sb = new StringBuilder();
        for (int i = 0; i < hash.Length; i++)
        {
            sb.Append(hash[i].ToString("X2"));
        }
        return sb.ToString();
    }
}
The above SecurePlayerPrefs class shows the implementation of SetString and GetString. I will leave it to you to implement the remaining methods like GetFloat, SetFloat. This can be done quite easily by converting the value and storing it with the SetString method.

One thing worth mentioning about this class is the GenerateMD5 function. This is used to hash the key for each value so that if the user opens the player preferences file they will not be able to see a user friendly name of what the encrypted data is.

Example
Below is an example of the SecurePlayerPrefs in action:

using UnityEngine;
using System.Collections;

public class SecurePlayerPrefsTest : MonoBehaviour
{
    void Start ()
    {
        SecurePlayerPrefs.SetString("HelloKey", "Hello World", "password");
        PlayerPrefs.Save();


        string helloWorld = SecurePlayerPrefs.GetString("HelloKey", "password");
        Debug.Log(helloWorld);
    }
}
Conclusion
With the above code you should easily be able to encrypt your data stored in the player preferences. I would recommend still using the Unity PlayerPrefs for things that are not sensitive because it is quicker than using encryption.

The other thing I would say, is the data is never completely secure but it does make it harder for the majority of people to hack. Just remember to use a good password and obscure it somehow.
原文链接:https://developingsoftware.com/how-to-securely-store-data-in-unity-player-preferences/
发表于 2020-1-2 07:55 | 显示全部楼层
很不错
发表于 2020-1-2 07:38 | 显示全部楼层
好帖就是要顶
发表于 2020-1-2 07:37 | 显示全部楼层
顶顶多好
发表于 2020-1-2 07:31 | 显示全部楼层
真心顶
发表于 2020-1-2 07:16 | 显示全部楼层
LZ真是人才
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-22 12:18 , Processed in 0.105766 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表