What’s this about?
If you’re new to Unity development, you might assume that Unity has basic security issues covered, such as ensuring that your game files are hidden away so that users can’t modify them.
Even if Unity itself doesn’t handle these security issues, if you’re targeting a mobile platform then the data and code must somehow be protected by the system, right?
Unfortunately, these are false assumptions, and will lead to pain (and perhaps embarrassment).
The simplest solution
The simplest “solution” for security problems is of course to do nothing. Every extra layer of security you add imposes its own risk, and increases development and testing time. I’ll try to touch on the risks for each attempted solution I present.
If you accept that it’s inevitable that someone will crack your app – and it most likely is if it’s at all popular – then you might consider adding complex countermeasures to be pointless. However, there can still be some merit in at least making it a bit difficult or time consuming to hack your app.
One solution I do not cover in this post is using a server to authenticate anything. I simply don’t have enough experience yet to say anything worthwhile about that.
A simple example – PlayerPrefs
Let’s make a simple toy class to illustrate some of these issues. It represents a “Player” of some sort that has one attribute: a Power Level. We’ll store the Power Level attribute in the Unity PlayerPrefs so that it will persist between runs. To make this really simple and easy to test, we’ll increase the PowerLevel every time we run the game.
Here’s the class, Player.cs:
using UnityEngine;
using System.Collections;
public class Player : MonoBehaviour
{
public int PowerLevel
{ get; set; }
void Start()
{
PowerLevel = PlayerPrefs.GetInt("PowerLevel", 1);
LevelUp();
}
public void LevelUp()
{
++PowerLevel;
PlayerPrefs.SetInt("PowerLevel", PowerLevel);
PlayerPrefs.Save();
}
}
This script increments the stored “PowerLevel” entry in the PlayerPrefs and then saves it.
And here’s the security issue: PlayerPrefs entries are stored in plain text, and are editable by users. Any value you save in PlayerPrefs is public knowledge, and can be changed at will by users. It’s great for a scratch pad, but you may want to think twice before storing anything game-breaking (or sensitive, such as passwords!) in there.
Where the PlayerPrefs are stored is OS-dependent, and described in detail in the PlayerPrefs documentation.
Potential solution: you can find tools in the Unity Asset Store to encrypt or obfuscate your PlayerPrefs in some way. I have not used them, so I can’t vouch for them (not that my word should carry any weight).
Let’s try that again – data files
Alright, so storing important data in PlayerPrefs was a bad idea. What if we store the data in a file that we have complete control of?
Let’s refactor our Player class to make use of Application.persistentDataPath, which Unity helpfully provides so that we don’t have to write OS-dependent code ourselves to find some place to write files to.
We’ll save out the PowerLevel to a file, then load it in on Start().
using UnityEngine;
using System.Collections;
using System.IO;
public class PlayerWithFile : MonoBehaviour
{
private string playerFile;
public int PowerLevel
{ get; set; }
void Awake()
{
playerFile = Application.persistentDataPath + "/Player.txt";
}
void LoadPlayerData()
{
if (File.Exists(playerFile))
{
string fileContents = File.ReadAllText(playerFile);
PowerLevel = int.Parse(fileContents);
}
else
{
PowerLevel = 1;
SavePlayerData();
}
}
void SavePlayerData()
{
File.WriteAllText(playerFile, PowerLevel.ToString());
}
void Start()
{
LoadPlayerData();
LevelUp();
}
public void LevelUp()
{
++PowerLevel;
SavePlayerData();
}
}
The Application.persistentDataPath is dependent on the OS, but once again the important point is that files you create this way are stored in plain text, and are editable by users.
For example, if I run this sample app on my Windows machine, it creates a “Player.txt” file under C:\Users\andrewd\AppData\LocalLow\DefaultCompany\SecurityTest
If I open it up, there’s my PowerLevel value. I can then edit it to whatever I want and the game will load it just fine.
Mobile builds are not any more secure. If you come from the console development world, that can come as a surprise. Users can edit any iOS app file by installing a program such as iExplorer. It’s even easier on Android, since you just need to connect your device through USB, then peruse through the Android/data folders.
For iOS users, you should also be aware that the info.plist file is bundled with your app and is also in plain text. This is not specific to Unity however; all apps have an info.plist file. It’s still something to be aware of if you make any additions that contain sensitive information.
What about a checksum?
If there’s no secure place to stuff our files (is there?), what about some sort of checksum?
Where would you store a checksum though? In the same data file you already know is completely exposed? That would at least force the “hacker” to figure out your checksum algorithm, then update it every time they change a value. Then of course your app has to be able to handle invalid checksums. What should you do in that case? Do you blow the data away and restore it to “factory settings” (whatever those might be)? Perhaps you can assume that if a checksum is invalid, the person has manually modified the file, so who cares if they lose their data?
If we’re writing out a binary file, then we could include the checksum as part of the output. Attackers will be able to figure out where in the file your checksum is by changing values in the game, then noting what bytes change.
If you’re writing XML or text files it gets a little awkward. You could write the checksum field out as a Base64 encoded string.
Another option would be to store the checksum out into PlayerPrefs, separate from the file it is the checksum for. That’s right, the same insecure PlayerPrefs talked about previously. I’ll share an example of this scheme anyway.
In this new class, PlayerWithHashCheck, we’ll use the .NET System.Security.MD5 class to compute a hash on the data file. By the way, when you start digging into some areas of .NET, you should consult this Mono Compatibility page to determine if the classes you want are actually available from within Unity. And also remember that just because something works on PC, it doesn’t mean it will work on a mobile device! (Click here for an example).
string GetHash(Stream stream)
{
// Here we'll use the .NET MD5 class to compute a hash on the file.
using (MD5 md5 = MD5.Create())
{
// It gives us a byte array, so we'll convert it to a Base64
// encoded string.
byte[] hash = md5.ComputeHash(stream);
return Convert.ToBase64String(hash);
}
}
void LoadPlayerData()
{
if (File.Exists(playerFile))
{
using (StreamReader fileStream = new StreamReader(playerFile))
{
string hashString = GetHash(fileStream.BaseStream);
// Does this match our previously stored hash value?
string storedHash = PlayerPrefs.GetString("mystery", hashString);
if (hashString != storedHash)
{
Debug.LogError("Invalid hash!");
}
}
PowerLevel = int.Parse(File.ReadAllText(playerFile));
}
else
{
PowerLevel = 1;
SavePlayerData();
}
}
void SavePlayerData()
{
// Write out the file, then store the new hash in PlayerPrefs.
using (StreamWriter outStream = new StreamWriter(playerFile))
{
outStream.Write(PowerLevel);
outStream.Flush();
}
using (StreamReader inStream = new StreamReader(playerFile))
{
string hashString = GetHash(inStream.BaseStream);
PlayerPrefs.SetString("mystery", hashString);
PlayerPrefs.Save();
}
}
If I now modify the Player.txt file, when I next load the player data it will detect and report the incorrect hash value. Since this is a toy example, I just log it and continue, but in a real app you’d want to actually handle it in some way
It’s worth pointing out that just storing the md5 value like so is a bad idea. If someone takes a peek at the PlayerPrefs, it will be rather obvious that it’s an md5 value. For example, it will look something like this in the windows registry (which is where PlayerPrefs are stored on Windows):
I’ll leave it as an exercise to obfuscate the hash, or perhaps generate a real checksum value from the data.
What about storing the data in a binary file?
Saving data as XML makes it potentially easily human-readable, which is both good and bad. It certainly can make it easier to read as a developer or designer. It can also make figuring out how to modify your game a bit easier.
One thought would be to save all your data as binary. This might also have the side effect of reducing your load times (benchmarking is required to determine that, of course). But what about the security properties?
A competent attacker will only be slightly delayed by writing your data out in “plain binary” format. All that’s needed is a Hex Editor and some perseverance. It will certainly be a slower attack than if everything is given to them in plain text, but it’s still not much of a deterrent.
For instance, your strings will still be saved out in a readable format, so they aren’t hidden. A trained eye will notice how certain things align and be able to figure out what certain key values are. If you don’t include a checksum of some sort, then all it takes is one person to figure out the binary file format. They can then either distribute an edited file that other people can use, or make a tool that lets users edit the file.
What about Encryption?
Saving as plain text or binary can still expose sensitive information, even if you add checksums to prevent tampering.
One strong solution would be to encrypt data files. For this you want to look into the System.Security.Cryptography Namespace
Let’s see what that looks like. For this test, we’ll make use of the TripleDES class in System.Security.Cryptography.
In order to encrypt a file, we’ll need a Key. One possible key is the SystemInfo.deviceUniqueIdentifier provided by Unity. Like the name implies, it’s unique for every device. That means that users can’t share data files, since only the device that the file was encrypted on is able to decrypt it. One potential downside to using that value is that if for some reason its value changes, due to Unity changing the algorithm it uses or some other change (updating the device OS, for instance), the user will no longer be able to open their data files. So beware!
Another option would be to include an encryption key with your app, embedded as a string perhaps. That would probably be worth exploring, but I’ll stick to the custom-keys in this example.
using UnityEngine;
using System.Collections;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public class PlayerWithEncryptedFile : MonoBehaviour
{
private string playerFile;
private static readonly int KeyLength = 24;
private static readonly int IVLength = 8;
private TripleDES algorithm = null;
private TripleDESCryptoServiceProvider serviceProvider = null;
public int PowerLevel
{ get; set; }
void Awake()
{
playerFile = Application.persistentDataPath + "/EncryptedPlayer.txt";
string uniqueID = SystemInfo.deviceUniqueIdentifier;
algorithm = TripleDES.Create();
algorithm.Key = MakeKey(uniqueID);
algorithm.IV = MakeIV(uniqueID);
serviceProvider = new TripleDESCryptoServiceProvider();
}
void LoadPlayerData()
{
if (File.Exists(playerFile))
{
using (FileStream fileStream = new FileStream(playerFile, FileMode.Open, FileAccess.Read))
using (var cryptoStream = new CryptoStream(
fileStream,
serviceProvider.CreateDecryptor(algorithm.Key, algorithm.IV),
CryptoStreamMode.Read))
using (StreamReader reader = new StreamReader(cryptoStream))
{
PowerLevel = int.Parse(reader.ReadLine());
}
}
else
{
PowerLevel = 1;
SavePlayerData();
}
}
void SavePlayerData()
{
using (FileStream fileStream = new FileStream(playerFile, FileMode.Create, FileAccess.Write))
using (var cryptoStream = new CryptoStream(
fileStream,
serviceProvider.CreateEncryptor(algorithm.Key, algorithm.IV),
CryptoStreamMode.Write))
using (StreamWriter writer = new StreamWriter(cryptoStream))
{
writer.Write(PowerLevel);
writer.Flush();
cryptoStream.FlushFinalBlock();
}
}
void Start()
{
LoadPlayerData();
LevelUp();
}
public void LevelUp()
{
++PowerLevel;
SavePlayerData();
}
private byte[] MakeKey(string input)
{
string mod = input.PadRight(KeyLength, '0').Substring(0, KeyLength);
return Encoding.ASCII.GetBytes(mod);
}
private byte[] MakeIV(string input)
{
string mod = input.PadRight(IVLength, '0').Substring(0, IVLength);
return Encoding.ASCII.GetBytes(mod);
}
}
If I open the data file now, I’ll just see what appears to be nonsense. As you can see, this is starting to get a bit complicated, and there are a lot of risks that come along with adding these extra layers.
However, the data files are pretty secure. There’s still more trouble though.
Editing Memory
If your data files are locked up tight, then attackers will just bypass them by getting your app to save bad data. How? By using memory editing tools. These are readily available for every platform, although on Android and iOS they do require a rooted or jailbroken device (correct me if I’m wrong).
They work by monitoring the memory associated with your app, then allowing users to compare memory values.
Consider the the simple “PowerLevel” of the player as an example. Assume that when we start our game, our PowerLevel is 1. A user starts the memory editor and searches for the value 1, and probably has many thousands of results (1 is a popular number). You then play the game for a bit, and progress to level 2. Again a memory search takes place, which now displays all the memory locations that were previously 1, and are now 2. This search continues until we’ve narrowed down the exact memory address of the PowerLevel. We then write some high value into it, and the next time the app saves the data file, it will store it and think nothing is wrong.
So what can you do? One solution would be to obfuscate the stored value of the data item you want to protect. Let’s return to our basic Player example to illustrate what I mean.
You’ll notice that PowerLevel is an automatic property, so we know that no code anywhere is referencing a backing store for it. This simplifies things a bit, since we know that we can implement a backing store for the property without breaking any client code. We do that so that we can implement some obfuscation when actually storing the value.
That is, in the getter for the property, we de-obfuscate the value and return the true value. Perhaps a UI element displays the PowerLevel. Well, we can ensure that the display copy is correct. If users attempt to narrow down the location of the true variable (the property), they’ll most likely end up just changing the display value.
In the setter, we obfuscate the value before storing it in the backing field so that it can’t easily be searched for by a memory editing tool.
One obvious obfuscation technique is to shuffle the bits of the value around. Let’s try a shuffling algorithm slightly modified from Hacker’s Delight and see how that goes.
Here’s the new class:
using UnityEngine;
using System.Collections;
public class PlayerWithObfuscatedData : MonoBehaviour
{
private int powerLevel;
public int PowerLevel
{
get
{
return Unshuffle(powerLevel);
}
set
{
powerLevel = Shuffle(value);
}
}
private int Shuffle(int x)
{
int t = (x ^ (x >> 8)) & 0x0000FF00; x = x ^ t ^ (t << 8);
t = (x ^ (x >> 4)) & 0x00F000F0; x = x ^ t ^ (t << 4);
t = (x ^ (x >> 2)) & 0x0C0C0C0C; x = x ^ t ^ (t << 2);
t = (x ^ (x >> 1)) & 0x22222222; x = x ^ t ^ (t << 1);
x ^= 0x55555555;
return x;
}
private int Unshuffle(int x)
{
x ^= 0x55555555;
int t = (x ^ (x >> 1)) & 0x22222222; x = x ^ t ^ (t << 1);
t = (x ^ (x >> 2)) & 0x0C0C0C0C; x = x ^ t ^ (t << 2);
t = (x ^ (x >> 4)) & 0x00F000F0; x = x ^ t ^ (t << 4);
t = (x ^ (x >> 8)) & 0x0000FF00; x = x ^ t ^ (t << 8);
return x;
}
void Start()
{
PowerLevel = PlayerPrefs.GetInt("PowerLevel", 1);
LevelUp();
/*for (int i = 0; i < 10; ++i)
{
int shuffled = Shuffle(i);
Debug.Log(i + " shuffles to " + shuffled + ", unshuffles back to " + Unshuffle(shuffled));
}*/
}
public void LevelUp()
{
++PowerLevel;
PlayerPrefs.SetInt("PowerLevel", PowerLevel);
PlayerPrefs.Save();
}
}
If we uncomment the for loop in Start(), we can see what kind of job the shuffle method does on some integers:
Notice that by design, as the true value increases by 1, the obfuscated value actually decreases by a different amount. The only search method that would work would be to search for memory values “not equal to previous”, unless the hacker picks up on the decreasing value sequence. Even then, it becomes a challenge to figure out what exactly to set the value to.
Next Level: Decompilers
Here’s the unfortunate truth: nothing we’ve discussed so far will matter if someone can decompile your code. By that I mean the process of taking your compiled .NET assembly and using a tool to convert the byte code back into human-readable source. These tools are very common, and quite effective. dotPeek is one example.
So can anyone come along and decompile your code with one of these tools? Yes; trivially.
Let’s examine the Unity output from making a PC build. Here I’ve made a build of my simple test project, and saved it in a “Builds” directory.
If we take a look inside that “Managed” folder, we’ll find a dll file named “Assembly-CSharp.dll”. What’s in that file? Let’s take a look using dotPeek.

As you can see, Unity bundles up all our classes into that assembly. If I double click on one of those entries, I get the reconstructed source. Let’s see what it did with PlayerWithObfuscatedData:
// Type: PlayerWithObfuscatedData
// Assembly: Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 906E32B4-5A76-4A2C-9DD6-C771C5D43CFA
// Assembly location: C:\Users\Andrew\Documents\New Unity Project 9\Builds\test_Data\Managed\Assembly-CSharp.dll
using UnityEngine;
public class PlayerWithObfuscatedData : MonoBehaviour
{
private int powerLevel;
public int PowerLevel
{
get
{
return this.Unshuffle(this.powerLevel);
}
set
{
this.powerLevel = this.Shuffle(value);
}
}
private int Shuffle(int x)
{
int num1 = (x ^ x >> 8) & 65280;
x = x ^ num1 ^ num1 << 8;
int num2 = (x ^ x >> 4) & 15728880;
x = x ^ num2 ^ num2 << 4;
int num3 = (x ^ x >> 2) & 202116108;
x = x ^ num3 ^ num3 << 2;
int num4 = (x ^ x >> 1) & 572662306;
x = x ^ num4 ^ num4 << 1;
x ^= 1431655765;
return x;
}
private int Unshuffle(int x)
{
x ^= 1431655765;
int num1 = (x ^ x >> 1) & 572662306;
x = x ^ num1 ^ num1 << 1;
int num2 = (x ^ x >> 2) & 202116108;
x = x ^ num2 ^ num2 << 2;
int num3 = (x ^ x >> 4) & 15728880;
x = x ^ num3 ^ num3 << 4;
int num4 = (x ^ x >> 8) & 65280;
x = x ^ num4 ^ num4 << 8;
return x;
}
private void Start()
{
this.PowerLevel = PlayerPrefs.GetInt("PowerLevel", 1);
this.LevelUp();
}
public void LevelUp()
{
++this.PowerLevel;
PlayerPrefs.SetInt("PowerLevel", this.PowerLevel);
PlayerPrefs.Save();
}
}
The formatting is a little different, but there’s our super-secret value obfuscation algorithm, exposed to the world. What happens to PlayerWithEncryptedFile is even worse, because our method of key generation is completely exposed.
The situation does not improve on Android. An apk can be decompiled, and once that happens they have access to the same Assembly-CSharp.dll file. They can then sign your app using their own key (or just leave it unsigned) and distribute it as their own (this is in fact extremely common).
One potential solution to this problem is to use .NET code obfuscation tools to mangle your source files before they are compiled, so that decompiling them will yield hard to read code.
Unfortunately, most of these tools won’t work with Unity. Consider your classes that derive from MonoBehaviour. They have special methods such as Update and Start that Unity expects to exist (by those names). If your obfuscator mangles those names, your MonoBehaviour class won’t work. Another related issue is SendMessage calls, since they rely on correct names (preventing any obfuscation).
Luckily, there are obfuscation tools available on the Unity Asset Store. I have not tried them yet, so unfortunately I can’t talk about them, but they might be worth looking at.
Conclusion
As you can see, securing applications is not an easy task. While this article may not give much insight into how to actually do so, I hope it at least raised your awareness of some very basic attacks and defensive measures. Good luck!



Really amazing post! It’s great to see all of your research on this topic. I’ve been interested in how I can obfuscate my games better in the future since I’ve noticed tools like ILSpy make it trivial to view a Unity game’s source code.
Your ideas on shuffling bits were particularly interesting and I might give that a go next time I write anything serious.
Thank you, excellent! You may also find the below presentation useful in terms of obfuscation of the code. http://www.slideshare.net/SeungminShin1/gstar-2013-unitysecurity/
Thanks Ivan! It looks like that presentation contains quite a few helpful resources, with actual solutions to some of the problems I mentioned in my post.
You could find my Anti-Cheat Toolkit plugin a good addition to what was said in this article.
Read more here: http://blog.codestage.ru/unity-plugins/act/