binary-lockEncryption

Secure encryption without hardcoded keys in the code

Crystal Save uses AES-256-GCM to encrypt save data on all supported platforms. On WebGL/Linux it automatically falls back to BouncyCastle, which can add a small CPU cost.

How encryption works

By default, Crystal Save derives a per-user key from:

This means encrypted saves can only be decrypted when both the master secret and the same user id are available at runtime.

You can optionally disable Use User ID for Encryption to derive a global key (no per-user isolation). See below for trade-offs.

Key source options

You can choose where the master secret comes from at runtime:

1) Static asset (default)

  • Uses StaticMasterSecret (ScriptableObject).

  • The asset must be included in the build to decrypt local saves.

  • Best for offline play and simplest setups.

2) User passphrase

  • Uses PassphraseMasterSecretProvider.

  • Derives the master secret from a user-provided passphrase (PBKDF2).

  • You must prompt for the passphrase before Crystal Save initializes.

  • The Salt Base64 stored in the asset is not the passphrase.

  • Losing the passphrase means saves are unrecoverable.

Note: The legacy "Server-fetched" key source (client-side) is deprecated. If you need the key to never touch the client, use Cloud Crypto Mode = ServerSide.

Enabling encryption

  1. Create Save Settings: Create -> Crystal Save -> Settings -> Crystal Save Settings

  2. Enable Encryption in the Cryptography Settings section.

  3. Choose Key Source and assign the matching provider asset:

    • Static -> Static Master Secret

    • User Passphrase -> User Passphrase Provider

If no provider is assigned, encryption is disabled even if the toggle is on.

  1. (Optional) Choose Cloud Crypto Mode when Cloud Save is enabled:

    • ClientSide (default) -> encrypt/decrypt on the client.

    • ServerSide -> encrypt/decrypt on your server using a Server-Side Crypto Provider.

Use User ID for Encryption (optional)

The Use User ID for Encryption toggle controls whether the user id is part of key derivation.

  • ON (default): per-user keys. Best security and isolation.

  • OFF: all users share the same derived key. This makes saves portable across installs and Editor/build, but reduces isolation. If the master key leaks, all saves can be decrypted. Not recommended for shared cloud environments.

Important when NOT using authentication

If Unity Authentication is not installed or the player is not signed in, Crystal Save uses a per-install GUID stored in PlayerPrefs as the user id.

Implications:

  • Editor saves will not decrypt in a build by default (different user id).

  • Reinstalling or clearing PlayerPrefs changes the user id.

  • The per-install GUID is not stable: renaming your project/company/product, deleting PlayerPrefs, or changing Persistent Path Mode can change where data is stored or the GUID itself.

  • Encrypted saves are not portable across devices without a stable user id.

If you need portability, use Unity Authentication or disable Use User ID for Encryption (with the trade-offs described above).

Runtime requirement

Encrypted saves can only be loaded when the master secret is available at runtime:

  • Static key -> included in the build.

  • Passphrase -> entered by the user each session (unless stored securely).

  • Server-side crypto -> handled by your server; the client does not hold the master key.

Cloud-only storage vs server-side crypto

Crystal Save can run without local disk saves by enabling Cloud Save and setting Keep Local Mirror = false. This is cloud-only storage, but encryption/decryption still happens on the client, so the key must still be available at runtime.

If you need the key to never touch the client, you must move encryption/decryption to the server and use a custom backend/workflow.

Cloud Crypto Mode (Server-side)

When Cloud Save is enabled, you can set Cloud Crypto Mode = ServerSide and provide a Server-Side Crypto Provider. In this mode:

  • The client never receives the master key.

  • The provider calls your server to encrypt before upload and decrypt after download.

  • Keep Local Mirror is ignored to avoid local disk writes.

  • You must implement the server endpoints to perform encryption/decryption.

  • Server-side crypto applies to save blobs only; slot metadata and screenshots are stored unencrypted.

  • Key Source (Static/Passphrase) is ignored because crypto happens on the server.

Server-side key creation (important)

For ServerSide crypto, the master key must live on your server, not in the Unity project or build. The ServerSideCryptoProvider asset only stores endpoint URLs and headers, not the key.

Recommended: generate the key on the server

Create a 32-byte key and store it as a server secret (env var, secret manager, etc.). Example (Linux/macOS):

Use the resulting base64 string as your server-side master key.

Alternate (one-time): generate in Unity, then move it

If you want a quick “one-click” start:

  1. Open Crystal Save Settings and set Cloud Crypto Mode to ServerSide.

  2. Click Export One-Time Server Key (Copy to Clipboard).

  3. Paste the copied key into your server’s secret storage.

Warning: If the key ever ships in the build, it defeats the purpose of server-side crypto.

Server endpoint spec (example)

The built-in Server-Side Crypto Provider expects HTTP POST endpoints:

Endpoints are API routes. Do not include the key in the URL path or query. The server should load the key from a secret store.

Encrypt endpoint

  • URL: https://your-server.example.com/crystalsave/encrypt

  • Request JSON:

  • Response (JSON or plain text):

Decrypt endpoint

  • URL: https://your-server.example.com/crystalsave/decrypt

  • Request JSON:

  • Response (JSON or plain text):

Headers (optional):

  • Authorization: Bearer <unity_access_token>

  • X-CrystalSave-UserId: <user_id>

Notes:

  • The server must manage the master key and perform crypto.

  • The encrypted payload can be any format as long as your encrypt/decrypt endpoints are symmetric.

Minimal Node.js sample (AES-256-GCM)

Minimal ASP.NET Core (C#) sample (AES-256-GCM)

Minimal PHP sample (AES-256-GCM)

Technical details

  • Encrypted payloads are tagged with the "CSAV" header.

  • AES-256-GCM is used where available.

  • Master secret must be exactly 32 bytes (base64 encoded in assets).

Best practices

  • Back up the master secret asset and keep it out of public repos.

  • Do not rotate the master secret after release unless you accept save loss.

  • Prefer Unity Authentication when you want cross-device save portability.

  • For editor-to-build testing, disable encryption or ensure a stable user id.

Troubleshooting

  • "Save file was encrypted with a different master secret" -> master secret or user id changed.

  • Encrypted saves fail after reinstall -> PlayerPrefs GUID changed.

  • Performance issues on WebGL/Linux -> consider disabling compression.

Last updated