How to reliably obscure playerprefs saved information ie in app purchase amounts

Hello,

Game uses in app purchases to purchase a set amount of gems, say 1000 for a dollar.
You can also earn gems in game.

My issue is how to prevent people hacking or modifying the playerprefs file so they can get an easy fix - i understand it to be easy on jailbroken phones. Since the game is free, it is likely to just be fiddled around with in the source files than have a dedicated hacker waste his time on it.

Any suggestions?

Well, you could also save a hash of the current amount of gems.

Let’s say, you have 500 gems and a secret keyword, i.e. 0c8a51e5b234fec38b6a1bd681. When you save the amount, you will save the gems in “gemAmount” PlayerPrefs variable and an md5(“500:0c8a51e5b234fec38b6a1bd681”) in “gemAmountHash”. When you read it, you check against it.

edit:
You can also add a user specific string to the hash, i.e. uuid, the unique user id (at least on Android, no idea about iOS, but they should have something similar too), so the hash would be device specific and harder to make a general tool or instruction to cheat
/edit

This will protect it against simple and “blind” editing of the PlayerPrefs. However, someone who really wants to hack it, will just download the APK (in case of Android, dunno about iOS), decompile/unpack it (it’s a simple signed zip file) and decompile your Unity-xxx.dll in ILSpy or some similar tool and look for the method used to create the hash (and most probably will also see your secret key used for it).

Of course the most secure way is to store it on the server, but that would make your game only work while you’re connected to the internet. There are some ways to hide the keys too. Usually you’d obfuscate the dll but this doesn’t seems to be supported by Unity3D which really sucks

edit:
And just as a side note, I wouldn’t bother trying too hard to prevent cheaters and pirates, because people who’d like to cheat and get gems without paying aren’t going to purchase anyways, even if you prevent them from cheating. More likely they are going to uninstall the game. If they can cheat, they still can play the game and i.e. generate revenue by in-game ads or by recommending your game to others. This is unlikely if they uninstall it because they can’t get the gems and are unwilling to pay anyway :slight_smile:

The safest way I can think of the top of my head right now is to store everything on the server as well. If they get to see your source code, then I think any defensive mechanism relying on the client side is pretty much useless.

Anyway, this is my code used for storing integer

using UnityEngine;
using System.Collections;

public class HiddenInt
{
	private int _value;
	private int r;
	
	public HiddenInt(int startValue = 0)
	{
		Value = startValue;
	}
	
	public int Value
	{
		get
		{
			return _value - r;
		}
		set
		{
			r = Random.Range(-1000000,1000000);
			_value = r + value;
		}
	}
	
}

This is to prevent memory hacking. You’ll never store the actual amount of coin in the variable. The actual amount of coin must come from a calculation involving _value and r. R is also random so it should take them a while before they know what’s going on.

Another way is to keep track of the amount of coin player has gain since last sync with server.
and if

current local amount of coin == coin stored on server + coin gained since last sync

then, that means the data is correct.

Depending on the game play, you can also record the amount of time player spend on playing, and the average amount of coin a player can get. If the time he spend on playing is not realistic, comparing to the amount of coin he has right now, then something’s fishy is going on. This would be harder and take more effort to cheat, considering how they have to come up with a realistic number that only you know, unless they play games many time and predict the same number that you have.

This does not protect you from memory hacking at all.

Maybe I used a wrong term. By memory hacking, I mean they can’t look at their current number of coins, and then find the same number in the memory, because the actual number of coins never stored anywhere.
Or if this still doesn’t help, could you please elaborate?

Well, he actually mentioned in-app purchases, so it’s very likely he talks about Android and iOS where… well memory manipulation aren’t an issue

Heh, you’re right. That random number technique is what I got from a Flash game developer regarding submitting an online highscore or flash game cheating in general. But yeah, memory manipulation is probably not an issue in a mobile phone game.

It is www.gamecih.com

What about storing a secondary variable that is a hash of the variables you want to protect.

Then when you first load the game and access your playerprefs, you decompose the hash and compare it to each of the individual keys making up the hash(or their sum). If the sum of the hash values isn’t equal to the sum of the individual keys, someone changed the playerprefs and you can set them all to 0.

Or when you save to playerPrefs you have a secretVar which adds up your individual keys and performs some type of operation to it (factor of, multiply by, etc) then do the same thing to the individual key values when the game first loads and compare it to the same equation used with the secretVar, if it’s the same then nothing was changed between last time application was exited and the next time it loads.

Cool!!! lmbarns, you had to be from Seattle…oh, only good stuff growing there…:smile:

I made 3 classes for protection against runtime value manipulations. Code is below. You can use them as a secure value type. Just add the AntiCheatManager script to the Camera or GameManager object, and you can start using them.
Use the AntiCheatExtensions.Synchronize() to assign the value.
As for the SaveGame Files, the most effective way to secure yourself from cheating is to:

  1. Encrypt the save file with AES.
  2. Encrypt the AES key with RSA using application’s public key.
  3. Hash the result with SHA2.
    For loading:
  4. Check the hash.(throw “!=” as cheating)
  5. Decrypt the AES key with application’s private key.
  6. Decrypt the save file with AES using decrypted key.
public class AntiCheat
{
    public static int lastID;
    public int ID;
    public Single Value;
    public Single HackCheck;
    public Action OnHack = null;

    public AntiCheat ()
    {
        Value = 0;
        HackCheck = 11;
        lastID++;
        ID = lastID;
        AntiCheatManager.MonitoredValues.RemoveAll(item => item == null);
        AntiCheatManager.MonitoredValues.Add(this);
    }

    public AntiCheat (Single value)
    {
        Value = (float)(value);
        HackCheck = 11;
        lastID++;
        ID = lastID;
        AntiCheatManager.MonitoredValues.RemoveAll(item => item == null);
        AntiCheatManager.MonitoredValues.Add(this);
    }

    public AntiCheat (Single value, Action onHack)
    {
        Value = (float)(value);
        HackCheck = 11;
        OnHack = onHack;
        lastID++;
        ID = lastID;
        AntiCheatManager.MonitoredValues.RemoveAll(item => item == null);
        AntiCheatManager.MonitoredValues.Add(this);
    }

    public static bool operator < (AntiCheat a, AntiCheat b)
    {
        if(a.Value < b.Value)
            return true;
        else
            return false;
    }

    public static bool operator < (Single a, AntiCheat b)
    {
        if(a < b.Value)
            return true;
        else
            return false;
    }

    public static bool operator < (AntiCheat a, Single b)
    {
        if(a.Value < b)
            return true;
        else
            return false;
    }

    public static bool operator > (AntiCheat a, AntiCheat b)
    {
        if(a.Value > b.Value)
            return true;
        else
            return false;
    }

    public static bool operator > (Single a, AntiCheat b)
    {
        if(a > b.Value)
            return true;
        else
            return false;
    }

    public static bool operator > (AntiCheat a, Single b)
    {
        if(a.Value > b)
            return true;
        else
            return false;
    }

    public static bool operator <= (AntiCheat a, AntiCheat b)
    {
        if(a.Value <= b.Value)
            return true;
        else
            return false;
    }

    public static bool operator <= (Single a, AntiCheat b)
    {
        if(a <= b.Value)
            return true;
        else
            return false;
    }

    public static bool operator <= (AntiCheat a, Single b)
    {
        if(a.Value <= b)
            return true;
        else
            return false;
    }

    public static bool operator >= (AntiCheat a, AntiCheat b)
    {
        if(a.Value >= b.Value)
            return true;
        else
            return false;
    }

    public static bool operator >= (Single a, AntiCheat b)
    {
        if(a >= b.Value)
            return true;
        else
            return false;
    }

    public static bool operator >= (AntiCheat a, Single b)
    {
        if(a.Value >= b)
            return true;
        else
            return false;
    }

    public static bool operator == (AntiCheat a, AntiCheat b)
    {
        if(Equals(a,b))
            return true;
        else
            return false;
    }

    public static bool operator == (Single a, AntiCheat b)
    {
        if(a == b.Value)
            return true;
        else
            return false;
    }

    public static bool operator == (AntiCheat a, Single b)
    {
        if(a.Value == b)
            return true;
        else
            return false;
    }

    public static bool operator != (AntiCheat a, AntiCheat b)
    {
        if(!Equals(a,b))
            return true;
        else
            return false;
    }

    public static bool operator != (Single a, AntiCheat b)
    {
        if(a != b.Value)
            return true;
        else
            return false;
    }

    public static bool operator != (AntiCheat a, Single b)
    {
        if(a.Value != b)
            return true;
        else
            return false;
    }
}
public class AntiCheatManager : MonoBehaviour
{
    public static List<AntiCheat> MonitoredValues = new List<AntiCheat>();

    void Update()
    {
        foreach(var ob in AntiCheatManager.MonitoredValues)
        {
            if(ob.Value != 11 - ob.HackCheck)
            {
                ob.Value = 11 - ob.HackCheck;
            }
        }
    }
}
public static class AntiCheatExtensions
{
    public static void Synchronize(this AntiCheat ac, float SecureValue)
    {  
        ac.Value = SecureValue;
        ac.HackCheck = 11-SecureValue;
    }
}
1 Like

Well, @hippocoder , these necro posts sure bring blasts from the past, don’t they? Great to see how one has grown in four years.

1 Like

Thanks guys :slight_smile: