Secured PlayerPrefs для Unity3d

в 1:59, , рубрики: Песочница, метки: , , , ,

Недавно возникла необходимость защитить сохранения игроков во избежание накрутки рекордов и игровой валюты. Прошерстив форумы в интернете, я нашел либо платные плагины (хоть цена и была невысокой, внутренний жлоб требовал продолжать поиски), либо «тяжелые» способы защиты — сохранения приходилось часто сверять, из-за этого во время сверки были заметны «тормоза» на компьютере и, уж тем более, на телефоне.

Почитав пару статеек о способах шифрования и испробовав еще столько же в игре, я подумал, что неплохим вариантом является DES Encryption. Сказано — сделано. В итоге сохранения в памяти решил представить следующим образом:

key: md5(key)
value: encrypt(value)

Где в подсчете хэша участвует секретный код, устанавливаемый разработчиком и уникальный идентификатор устройства (на случай переноса сохранений из другого), а значение шифруется через алгоритм DES.

В конце концов написал класс SPlayerPrefs, который можно полностью использовать вместо стандартного PlayerPrefs. В случае, если данные были повреждены, методы Get вернут дефолтные значения.

Вот исходный код этого класса SPlayerPrefs.cs:

Заголовок спойлера

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

public class SPlayerPrefs {
	
	public static void SetString(string key, string value) {
		PlayerPrefs.SetString(md5(key), encrypt(value));
	}

	public static string GetString(string key, string defaultValue) {
		if (!HasKey (key))
			return defaultValue;
		try {
			string s = decrypt(PlayerPrefs.GetString(md5(key)));
			return s;
		}
		catch {
			return defaultValue;
		}
	}
	
	public static string GetString(string key) {
		return GetString(key, "");
	}

	public static void SetInt(string key, int value) {
		PlayerPrefs.SetString(md5(key), encrypt(value.ToString()));
	}
	
	public static int GetInt(string key, int defaultValue) {
		if (!HasKey (key))
			return defaultValue;
		try {
			string s = decrypt(PlayerPrefs.GetString(md5(key)));
			int i = int.Parse(s);
			return i;
		}
		catch {
			return defaultValue;
		}
	}
	
	public static int GetInt(string key) {
		return GetInt(key, 0);
	}
	
	
	public static void SetFloat(string key, float value) {
		PlayerPrefs.SetString(md5(key), encrypt(value.ToString()));
	}
	
	
	public static float GetFloat(string key, float defaultValue) {
		if (!HasKey (key))
			return defaultValue;
		try {
			string s = decrypt(PlayerPrefs.GetString(md5(key)));
			float f = float.Parse(s, System.Globalization.CultureInfo.InvariantCulture);
			return f;
		}
		catch {
			return defaultValue;
		}
	}
	
	public static float GetFloat(string key) {
		return GetFloat(key, 0);
	}

	public static bool HasKey(string key) {
		return PlayerPrefs.HasKey(md5(key));
	}

	public static void DeleteAll() {
		PlayerPrefs.DeleteAll();
	}

	public static void DeleteKey(string key) {
		PlayerPrefs.DeleteKey(md5(key));
	}

	public static void Save() {
		PlayerPrefs.Save ();
	}

	/*
	 * Обязательно смените этот секретный код и числа в массивах
	 */
	private static string secretKey = "secret";
	private static byte[] key = new byte[8] {22, 41, 18, 47, 38, 217, 65, 64};
	private static byte[] iv = new byte[8] {34, 68, 46, 43, 50, 87, 2, 105};

	private static string encrypt(string s)
	{
		byte[] inputbuffer = Encoding.Unicode.GetBytes(s);
		byte[] outputBuffer = DES.Create().CreateEncryptor(key, iv).TransformFinalBlock(inputbuffer, 0, inputbuffer.Length);
		return System.Convert.ToBase64String(outputBuffer);
	}
	
	private static string decrypt(string s) {
		byte[] inputbuffer = System.Convert.FromBase64String(s);
		byte[] outputBuffer = DES.Create().CreateDecryptor(key, iv).TransformFinalBlock(inputbuffer, 0, inputbuffer.Length);
		return Encoding.Unicode.GetString(outputBuffer);
	}
	
	private static string md5(string s) {
		byte[] hashBytes = new MD5CryptoServiceProvider().ComputeHash(new UTF8Encoding().GetBytes(s + secretKey + SystemInfo.deviceUniqueIdentifier));
		string hashString = "";
		for (int i = 0; i < hashBytes.Length; i++) {
			hashString += System.Convert.ToString(hashBytes[i], 16).PadLeft(2, '0');
		}
		return hashString.PadLeft(32, '0');
	}
}

Если где-то что-то неверно, то уведомите, пожалуйста. Ничего великого в этом деле нет, просто думаю, что поможет читающим пользователям сэкономить немного времени.


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js