Skip to content

Instantly share code, notes, and snippets.

@Sov3rain
Created February 11, 2025 15:41
Show Gist options
  • Save Sov3rain/5152170491f8295ef1d09718e14f29b2 to your computer and use it in GitHub Desktop.
Save Sov3rain/5152170491f8295ef1d09718e14f29b2 to your computer and use it in GitHub Desktop.
SecurePlayerPrefs is a secure wrapper for Unity's PlayerPrefs that provides encrypted data storage for sensitive information in Unity applications.
using System;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
public static class SecurePlayerPrefs
{
private static readonly string _encryptionKey = DeriveKey();
public static void SetString(string key, string value)
{
string encryptedValue = Encrypt(value, _encryptionKey);
PlayerPrefs.SetString(key, encryptedValue);
}
public static string GetString(string key, string defaultValue = "")
{
string encryptedValue = PlayerPrefs.GetString(key, "");
if (string.IsNullOrEmpty(encryptedValue))
return defaultValue;
return Decrypt(encryptedValue, _encryptionKey) ?? defaultValue;
}
public static void SetInt(string key, int value)
{
SetString(key, value.ToString());
}
public static int GetInt(string key, int defaultValue = 0)
{
string stringValue = GetString(key, defaultValue.ToString());
if (int.TryParse(stringValue, out int result))
{
return result;
}
return defaultValue;
}
public static void SetFloat(string key, float value)
{
SetString(key, value.ToString(CultureInfo.InvariantCulture));
}
public static float GetFloat(string key, float defaultValue = 0)
{
string stringValue = GetString(key, defaultValue.ToString(CultureInfo.InvariantCulture));
if (float.TryParse(stringValue, out float result))
{
return result;
}
return defaultValue;
}
private static string Encrypt(string value, string encryptionKey)
{
try
{
byte[] keyArray;
byte[] toEncryptArray = Encoding.UTF8.GetBytes(value);
// MD5 to calculate the hash value of the key.
MD5CryptoServiceProvider md5 = new();
keyArray = md5.ComputeHash(Encoding.UTF8.GetBytes(encryptionKey));
md5.Clear();
// TripleDES is a symmetric block cipher for encryption.
TripleDESCryptoServiceProvider tdes = new();
tdes.Key = keyArray;
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = tdes.CreateEncryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
tdes.Clear();
return Convert.ToBase64String(resultArray);
}
catch (Exception e)
{
Debug.LogError("Encryption failed: " + e.Message);
return null;
}
}
private static string Decrypt(string encryptedValue, string encryptionKey)
{
try
{
byte[] keyArray;
byte[] toDecryptArray = Convert.FromBase64String(encryptedValue);
// MD5 to calculate the hash value of the key.
MD5CryptoServiceProvider md5 = new();
keyArray = md5.ComputeHash(Encoding.UTF8.GetBytes(encryptionKey));
md5.Clear();
// TripleDES is a symmetric block cipher for encryption.
TripleDESCryptoServiceProvider tdes = new();
tdes.Key = keyArray;
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = tdes.CreateDecryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toDecryptArray, 0, toDecryptArray.Length);
tdes.Clear();
return Encoding.UTF8.GetString(resultArray);
}
catch (Exception e)
{
Debug.LogError("Decryption failed: " + e.Message);
return null;
}
}
private static string DeriveKey()
{
string saltKey = $"{nameof(SecurePlayerPrefs).ToLower()}:{nameof(saltKey).ToLower()}";
string salt = PlayerPrefs.GetString(saltKey, "");
if (string.IsNullOrEmpty(salt))
{
salt = GenerateSalt();
PlayerPrefs.SetString(saltKey, salt);
PlayerPrefs.Save();
}
string deviceId = SystemInfo.deviceUniqueIdentifier;
string combined = salt + deviceId;
using var sha256 = SHA256.Create();
byte[] keyBytes = Encoding.UTF8.GetBytes(combined);
byte[] hashBytes = sha256.ComputeHash(keyBytes);
return Convert.ToBase64String(hashBytes);
}
private static string GenerateSalt() => Convert.ToBase64String(Guid.NewGuid().ToByteArray())
.Replace("/", "_")
.Replace("+", "-")
.Replace("=", "");
}

SecurePlayerPrefs

SecurePlayerPrefs is a secure wrapper for Unity's PlayerPrefs that provides encrypted data storage for sensitive information in Unity applications. It uses TripleDES encryption with device-specific key derivation to protect stored data from tampering and unauthorized access.

Features

  • Encrypted storage of strings, integers, and floating-point values
  • Device-specific encryption keys
  • Automatic salt generation and management
  • Fallback to default values if decryption fails
  • Compatible with Unity's PlayerPrefs API style

Installation

  1. Copy the SecurePlayerPrefs.cs file into your Unity project's Scripts folder

Usage

Basic Usage

// Storing values
SecurePlayerPrefs.SetString("playerName", "John Doe");
SecurePlayerPrefs.SetInt("highScore", 1000);
SecurePlayerPrefs.SetFloat("playerHealth", 100.5f);

// Retrieving values
string name = SecurePlayerPrefs.GetString("playerName", "Default Name");
int score = SecurePlayerPrefs.GetInt("highScore", 0);
float health = SecurePlayerPrefs.GetFloat("playerHealth", 100f);

Storing Complex Objects

You can store complex objects by serializing them to JSON:

// Create and store a complex object
var playerData = new PlayerData
{
    Name = "John Doe",
    Score = 1000
};
string json = JsonUtility.ToJson(playerData);
SecurePlayerPrefs.SetString("playerData", json);

// Retrieve and deserialize the object
string savedJson = SecurePlayerPrefs.GetString("playerData");
PlayerData loadedData = JsonUtility.FromJson<PlayerData>(savedJson);

Security Features

  • Encryption: Uses TripleDES encryption in ECB mode with PKCS7 padding
  • Key Derivation:
    • Generates a unique salt for each installation
    • Combines salt with device-specific identifier
    • Uses SHA256 to derive the final encryption key
  • Device-Specific: Data encrypted on one device cannot be decrypted on another

Technical Details

  • The encryption key is derived from:
    • A randomly generated salt (stored in PlayerPrefs)
    • The device's unique identifier
  • The salt is generated once per installation and reused thereafter
  • Data is stored in Base64 format after encryption
  • Uses standard .NET cryptography libraries

Limitations

  • Does not provide secure storage for the salt (stored in plain text in PlayerPrefs)
  • Uses ECB mode which may not be suitable for all security requirements
  • Relies on Unity's SystemInfo.deviceUniqueIdentifier which may change in some circumstances
  • No built-in key rotation mechanism

Example Implementation

public class GameManager : MonoBehaviour
{
    private void SaveGameState()
    {
        var gameState = new GameState
        {
            Level = currentLevel,
            Score = playerScore,
            LastSaved = DateTime.Now.ToString()
        };
        
        string json = JsonUtility.ToJson(gameState);
        SecurePlayerPrefs.SetString("gameState", json);
    }

    private GameState LoadGameState()
    {
        string json = SecurePlayerPrefs.GetString("gameState", "");
        return string.IsNullOrEmpty(json) 
            ? new GameState() 
            : JsonUtility.FromJson<GameState>(json);
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment