Cheating is becoming a bigger and bigger problem for developers that arent authenticating their saved data. You only have to look at the leader boards for the ‘Flappy Bird’ game and you will find hundreds of users on 999999. It’s near impossible to actually achieve that in the game, but users can easily access the files used to save this data and modify it to meet their needs.

It’s not only high scores that are an issue. Developers need a way of keeping track of in-app purchases, and this is often saved on the device in the form of JSON or SQL files. This obviously opens the door up to users activating features or items without paying for them.

There is however, a simple solution to check if the data being saved, then loaded back into the app is genuine or not. This is in the form of a checksum. A simple calculation using the data you want to save, a random key, and the resultant. Simply calculate a checksum at data save and store the result with the saved data. When it comes to load the data back in, perform the same calculation and ensure that the result matches the result that was saved.

Authenticating Data

The following steps are used to save data with a checksum:

  • Save the score.
    You want to save a high score value to the phone. Save the value to a JSON file.
  • Create a random key.
    This random key can be generated however you wish. This should also be saved to the JSON file
  • Perform the checksum calculation.
    The checksum can be any calculation or formula that you like. The more complicated the formula the harder it will be to crack
    This example uses MOD instruction. In lua code that is achieved by

    'a % b'

    So in this instance it would be

    'High score value % Random key'

    Save the resultant to the JSON file.

Now when you are ready to load the data back into the app, use the following steps:

  • Load JSON File.
    Load the JSON file back into the app.
  • Perform the checksum calculation.
    Re-perform the checksum calculation made in the saving procedure, using the values you have loaded in from the JSON file.
  • Check the checksums match.
    Now check if the resultant from the calculation matches the resultant saved in the JSON file. If it matches everything is great. If it is different, you know that one of the values in the JSON file has been modified. At this point you can get the app to reset a score or display a message etc.

Thats all there is to it. You can expand it a bit more to incorporate more saved fields, or even a different calculation, but that is the basic principle of creating a checksum.

Example

In the example below, I will integrate a checksum for a high score. The app will automatically check the checksum when you load the data, if it finds the checksum is wrong it will reset the players score. The following example is written in Lua for Corona SDK; however the same principle can be used in any SDK or platform. Likewise the example below uses a JSON file for saving data, this will however work with any system including mySQL.

To make everything simple, I will create a function for saving data, and a function to load data. That way whenever we want to save or load data we just call the corresponding function.

1. Create a save data function

local function saveGameSettings()

	--Check game settings actually exist
	if (gameGlobalData.gameSettings ~= nil) then
		--Create the unique key value
		--This can be anything. In this example i am using a random number
		gameGlobalData.gameSettings.key = math.random(30);

		--Create the checksum
		gameGlobalData.gameSettings.checksum = gameGlobalData.gameSettings.HighScore % gameGlobalData.gameSettings.key

		--Save the settings using your own function.
		loadsave.saveTable(gameGlobalData.gameSettings, "gamesettings.json");
	end
end

This function does the following:

  1. Checks the gamesettings table exists
  2. Generates a random key. This can be generated however you wish.
  3. Create the checksum by performing the calculation
  4. Saves the gamesettings table back out to a JSON file using an external function. This should be replaced with your own function

2. Create a load data function

To make life simple, create a single function that will load all the data into the application typically on app start up:

local function getGameSettings()
	--Load the data in from a JSON file using you own json reading function
	gameGlobalData.gameSettings = loadsave.loadTable("gamesettings.json")

	--Now Check if the settings were loaded OK
	if( gameGlobalData.gameSettings == nil ) then 
		--There are no settings present. 
		--This is the first time the user has launched the game
		--Create the default settings
		gameGlobalData.gameSettings = {}
		gameGlobalData.gameSettings.HighScore = 0
		gameGlobalData.gameSettings.key = 0
		gameGlobalData.gameSettings.checksum = 0
		
		--Save the settings
		gameGlobalData.gameSettingsSaveFC();
	end

	--Perform the checksum calculation
	local calcChecksum = gameGlobalData.gameSettings.HighScore % gameGlobalData.gameSettings.key

	--Check if any tampering with the score has taken place
	if (calcChecksum ~= gameGlobalData.gameSettings.checksum) then
	--Checksums do not match!!
	native.showAlert( "High Score Error", "It appears there is something not quite right with your saved high score, so we have reset it.", { "OK" } )

	--Reset score
	gameGlobalData.gameSettings.HighScore=0

	--Save the new score
	gameGlobalData.gameSettingsSaveFC();
	end
end

This function does the following:

  1. Loads the JSON saved data file into a global variable. (You will need your own function to load the json into a Lua table)
  2. Checks to make sure data was loaded. If not, default data will be stored, then saved. In this default data we create the high score variable, and also the key and checksum.
  3. No we have data in our global variables, we need to check the integrity of the data.

Conclusion

The above method is a simple and easy way to add a layer of security to your saved data. There are also other ways such as encryption. However, as it has been pointed out, as with anything, the protection is defeat-able if the end user goes to as much trouble as to decompile the source code to reveal your formula, or cipher keys. So, although this method is perfectly fine for saving high scores and IAP tracking, you wouldn’t want to use it (in this form) with highly sensitive data.