Skip to Content
DocsTinysaveEncryption & Security

Encryption & Security

TinySave includes built-in AES-256 encryption to protect save files from tampering and cheating. This guide covers how to enable and configure encryption.

Why Use Encryption?

Without encryption, save files are stored as plain JSON text that can be easily read and modified by players. Encryption is important for:

  • Preventing cheating - Players can’t edit save files to give themselves unlimited resources
  • Protecting competitive games - Leaderboards and multiplayer games need tamper-proof saves
  • Maintaining game balance - Preserve intended difficulty and progression
  • Professional polish - Players expect security in commercial games

Quick Setup

Enable encryption in two steps:

Step 1: Configure Encryption Key

using UnityEngine; using TinySave.Runtime; public class GameInit : MonoBehaviour { void Awake() { var config = SaveManagerConfig.CreateDefault(); config.EncryptionEnabled = true; config.EncryptionKey = "your-secret-key-here"; SaveManager.Configure(config); } }

Step 2: Use SaveManager Normally

// Save and load work exactly the same await SaveManager.SaveAsync("slot1"); await SaveManager.LoadAsync("slot1"); // Save files are now encrypted automatically

That’s it! All save files are now encrypted with AES-256.

SaveManagerConfig

The SaveManagerConfig class controls encryption settings:

public class SaveManagerConfig { public bool EncryptionEnabled { get; set; } public string EncryptionKey { get; set; } public string StorageBasePath { get; set; } public string FileExtension { get; set; } = "tsf"; }

Properties

  • EncryptionEnabled - Enable/disable encryption (default: false)
  • EncryptionKey - The encryption key (required when EncryptionEnabled is true)
  • StorageBasePath - Custom save location (optional)
  • FileExtension - File extension for save files (default: “tsf”)

Creating a Configuration

// Create default config var config = SaveManagerConfig.CreateDefault(); // Enable encryption config.EncryptionEnabled = true; config.EncryptionKey = "MySecureKey123"; // Optional: Custom save location config.StorageBasePath = "C:/MyGameSaves"; // Optional: Custom file extension config.FileExtension = "sav"; // Apply configuration SaveManager.Configure(config);

Validation

SaveManagerConfig validates your settings:

var config = SaveManagerConfig.CreateDefault(); config.EncryptionEnabled = true; // config.EncryptionKey is null try { SaveManager.Configure(config); } catch (InvalidOperationException ex) { // Error: "Encryption is enabled but no encryption key was provided" Debug.LogError(ex.Message); }

TinySaveSettings

For persistent settings across sessions, use TinySaveSettings which stores configuration in PlayerPrefs:

using TinySave.Runtime; // Access the singleton settings var settings = TinySaveSettings.defaultSettings; // Configure encryption settings.encryptionEnabled = true; settings.encryptionKey = "MySecureKey123"; // Optional: Custom save path settings.storageBasePath = "/custom/path"; // Settings are automatically saved to PlayerPrefs // Apply to SaveManager var config = settings.ToConfig(); SaveManager.Configure(config);

Properties

public class TinySaveSettings { public bool encryptionEnabled { get; set; } public string encryptionKey { get; set; } public string storageBasePath { get; set; } }

Persistence

TinySaveSettings automatically persists to PlayerPrefs:

// Changes are saved immediately TinySaveSettings.defaultSettings.encryptionEnabled = true; // Saved to PlayerPrefs // Manually reload from PlayerPrefs TinySaveSettings.ReloadFromPrefs();

Choosing an Encryption Key

Your encryption key is critical for security. Follow these guidelines:

Good Keys

// Long, random, unique config.EncryptionKey = "k8Jx2Pq9vM4nW7sZ1bY5cR3dF6hT0gL"; // Mix of characters config.EncryptionKey = "MyGame_2024_Secure!Key#9876"; // Generated config.EncryptionKey = System.Guid.NewGuid().ToString();

Bad Keys

// Too short config.EncryptionKey = "123"; // Common words config.EncryptionKey = "password"; // Predictable config.EncryptionKey = "MyGameName";

Best Practices

  1. Use a long key - At least 16 characters
  2. Make it unique - Don’t reuse keys from other projects
  3. Keep it secret - Never commit keys to public repositories
  4. Don’t hardcode in shipping builds - Use obfuscation or runtime generation
  5. Keep it consistent - Same key must be used for save and load

Key Management Strategies

Development Build

For development, hardcode the key for convenience:

#if UNITY_EDITOR config.EncryptionKey = "dev-key-12345"; #else config.EncryptionKey = GetProductionKey(); #endif

Production Build

For production, use more secure approaches:

Option 1: Environment Variable

string key = System.Environment.GetEnvironmentVariable("GAME_SAVE_KEY"); if (string.IsNullOrEmpty(key)) { key = "fallback-key"; // Default for testing } config.EncryptionKey = key;

Option 2: Runtime Generation

// Generate key based on device-specific data string deviceId = SystemInfo.deviceUniqueIdentifier; string gameId = Application.productName; config.EncryptionKey = $"{gameId}_{deviceId}";

Option 3: Obfuscated Key

// Simple obfuscation (use a proper obfuscator for production) private string GetEncryptionKey() { byte[] bytes = System.Convert.FromBase64String("TXlHYW1lS2V5MTIzNDU2Nzg="); return System.Text.Encoding.UTF8.GetString(bytes); } config.EncryptionKey = GetEncryptionKey();

IEncryption Interface

TinySave uses the IEncryption interface for encryption operations:

public interface IEncryption { string Encrypt(string plaintext); string Decrypt(string ciphertext); }

The default implementation uses AES-256:

public class AES256Encryption : IEncryption { public AES256Encryption(string key) { } public string Encrypt(string plaintext) { } public string Decrypt(string ciphertext) { } }

Custom Encryption

You can implement custom encryption:

using TinySave.Runtime; using System.Security.Cryptography; using System.Text; using System; public class CustomEncryption : IEncryption { private string key; public CustomEncryption(string encryptionKey) { key = encryptionKey; } public string Encrypt(string plaintext) { // Your encryption implementation byte[] bytes = Encoding.UTF8.GetBytes(plaintext); // ... apply encryption ... return Convert.ToBase64String(bytes); } public string Decrypt(string ciphertext) { // Your decryption implementation byte[] bytes = Convert.FromBase64String(ciphertext); // ... apply decryption ... return Encoding.UTF8.GetString(bytes); } }

Then use it with custom storage:

// Create custom storage with your encryption IStorage baseStorage = new JsonFileStore(); IEncryption encryption = new CustomEncryption("my-key"); IStorage encryptedStorage = new EncryptedStorage(baseStorage, encryption); // You'd need to configure SaveManager to use this storage // (This requires modifying SaveManager's internal storage)

IStorage Interface

TinySave uses the IStorage interface for file operations:

public interface IStorage { Task WriteTextAtomicAsync(string path, string content, CancellationToken ct = default); void WriteTextAtomic(string path, string content); Task<string> ReadTextAsync(string path, CancellationToken ct = default); string ReadText(string path); bool Exists(string path); void Delete(string path); string GetFullPath(string relativePath); }

Storage Implementations

TinySave includes two storage implementations:

JsonFileStore

Plain text storage (no encryption):

IStorage storage = new JsonFileStore(); // or IStorage storage = new JsonFileStore("/custom/path");

EncryptedStorage

Encrypted wrapper around any IStorage:

IStorage baseStorage = new JsonFileStore(); IEncryption encryption = new AES256Encryption("my-key"); IStorage encryptedStorage = new EncryptedStorage(baseStorage, encryption);

SaveManagerConfig.CreateStorage() handles this automatically:

public IStorage CreateStorage() { IStorage baseStorage = new JsonFileStore(StorageBasePath); if (EncryptionEnabled) { IEncryption encryption = new AES256Encryption(EncryptionKey); return new EncryptedStorage(baseStorage, encryption); } return baseStorage; }

Security Considerations

What Encryption Protects

  • File content is encrypted (can’t read save data without key)
  • Tampering is detectable (decryption fails if file is modified)
  • Casual cheating is prevented (players can’t edit JSON directly)

What Encryption Doesn’t Protect

  • Memory editing - Cheaters can still modify values in memory at runtime
  • Determined attackers - Encryption key can be extracted from your game binary
  • Code modification - Players can patch your game to bypass encryption
  • Replay attacks - Old save files can still be loaded

Additional Security Measures

For maximum security, combine encryption with:

  1. Server validation - Validate critical data on your server
  2. Checksums - Add hash verification to detect tampering
  3. Obfuscation - Hide encryption keys in compiled code
  4. Runtime checks - Validate data ranges and relationships
  5. Anti-cheat - Use professional anti-cheat solutions for multiplayer

Performance Impact

Encryption adds minimal overhead:

// Without encryption Capture=5ms, Serialize=12ms, IO=8ms, Total=25ms // With encryption Capture=5ms, Serialize=12ms, IO=10ms, Total=27ms

The encryption/decryption happens during I/O and is negligible compared to serialization.

Migrating to Encrypted Saves

If you’re adding encryption to an existing game:

Option 1: Automatic Migration

Load old unencrypted saves, re-save with encryption:

void MigrateToEncryption() { // Disable encryption temporarily var config = SaveManagerConfig.CreateDefault(); config.EncryptionEnabled = false; SaveManager.Configure(config); // Load old save bool loaded = await SaveManager.LoadAsync("slot1"); if (loaded) { // Enable encryption config.EncryptionEnabled = true; config.EncryptionKey = "your-new-key"; SaveManager.Configure(config); // Re-save with encryption await SaveManager.SaveAsync("slot1"); } }

Option 2: Check File Format

Detect encrypted vs unencrypted files:

bool IsEncrypted(string slot) { string path = SaveManager.GetSlotPath(slot); string content = File.ReadAllText(path); // Encrypted files are Base64, plain JSON starts with '{' return !content.TrimStart().StartsWith("{"); } async Task LoadWithEncryptionDetection(string slot) { bool encrypted = IsEncrypted(slot); var config = SaveManagerConfig.CreateDefault(); config.EncryptionEnabled = encrypted; if (encrypted) config.EncryptionKey = "your-key"; SaveManager.Configure(config); await SaveManager.LoadAsync(slot); }

Troubleshooting

”Decryption failed” Error

Cause: Wrong encryption key or corrupted file.

Solution:

try { await SaveManager.LoadAsync("slot1"); } catch (System.Security.Cryptography.CryptographicException ex) { Debug.LogError("Save file is corrupted or wrong encryption key"); // Delete corrupted file and start fresh SaveManager.DeleteSlot("slot1"); }

“No encryption key provided” Error

Cause: EncryptionEnabled is true but EncryptionKey is null.

Solution:

var config = SaveManagerConfig.CreateDefault(); config.EncryptionEnabled = true; config.EncryptionKey = "your-key"; // Don't forget this! SaveManager.Configure(config);

Save Files Are Plain Text

Cause: Encryption wasn’t configured before saving.

Solution:

// Configure encryption BEFORE first save void Awake() { var config = SaveManagerConfig.CreateDefault(); config.EncryptionEnabled = true; config.EncryptionKey = "your-key"; SaveManager.Configure(config); }

Next Steps

Last updated on