Thursday, March 15, 2012

Save and Read an Encrypted Data File

Your mobile game or app most probably has some data you want to store locally on the phone. Also there might be good reason why you don't want this saved data to be easily accessible and changeable from outside of your app. For the iPhone for example you can simply use iExplorer to read and change any files of an app - no jailbreak needed. If you develop a game you might want to avoid that a user can easily cheat points and achievements. If your app has in-app purchasable items you might even get financial loss if the user can easily cheat.
Luckily the .Net Framework comes with a lot of classes that make file encryption easy to implement. If you want to secure your data from manipulation you generally have two ways of achieving it:
  • Leave the data readable but add an authentication hash key. 
  • Encrypt the complete data file.
The first method is for example used by RESTful web services. In my previous post I talked about Amazon SimpleDB which uses a hash key for authentication of the database requests. Take a look inside the library source code if you are interested in a working cross-platform implementation example.
The second method encrypts the complete data file. People sneaking into your data files will not be even able to read it and might not be tempted to try to alter the values.
In this post I will give an example for the second method. I will give an implementation that works well with Windows Phone, MonoTouch and Mono for Android and can be easily added to any app.

Application Flow
In this implementation the data that should be stored is located inside a struct. Using XML serialization the struct is converted to XML which is then encrypted and finally stored as an isolated storage file.
struct -> XmlSerializer -> XmlWriter -> CryptoStream -> IsolatedStorageFileStream
When reading the encrypted file the flow is the other way round:
IsolatedStorageFileStream -> CryptoStream -> XmlSerializer -> struct

Implementation
Start by adding the following using statements to your source file:
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Security;
using System.Security.Cryptography;
using System.IO.IsolatedStorage;
In my example I use the following data structure, which is some simple game data.
public struct SaveGameStruct
{
    public string Name;
    public int HiScore;
    public int NumOfCoins;
    public DateTime Date;
    public string Guid;
}
Next store a password and a salt as member variable string:
string m_password = "7Sj)fjfdHf734Jjd";
string m_salt = "hHeh=j84";
The password is not the actual password that is used to encode your data file. Instead it is used to create a key that then is used for encryption. The key generator uses pseudo random numbers, hence the salt. Don't forget to change the password and salt values in your source code.
The function to save an encrypted file now looks like this:
public void DoSaveGame()
{
    //Generate a Key based on a Password and HMACSHA1 pseudo-random number generator
    //Salt must be at least 8 bytes long
    //Use an iteration count of at least 1000
    Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(m_password, Encoding.UTF8.GetBytes(m_salt), 1000);

    //Create AES algorithm
    AesManaged aes = new AesManaged();
    //Key derived from byte array with 32 pseudo-random key bytes
    aes.Key = rfc2898.GetBytes(32);
    //IV derived from byte array with 16 pseudo-random key bytes
    aes.IV = rfc2898.GetBytes(16);
            
    string SAVEFILENAME = "savegame.xml";

    IsolatedStorageFileStream storageStream = m_savegameStorage.OpenFile(SAVEFILENAME, FileMode.Create);
    XmlWriterSettings writerSettings =
    new XmlWriterSettings
    {
        Indent = true,
        IndentChars = "\t"
    };

    XmlSerializer serializer = new XmlSerializer(typeof(SaveGameStruct));

    CryptoStream cryptoStream = new CryptoStream(storageStream, aes.CreateEncryptor(), CryptoStreamMode.Write);

    using (XmlWriter xmlWriter = XmlWriter.Create(cryptoStream, writerSettings))
    {
        // m_saveGame is a member variable of the type SaveGameStruct which holds the data to be saved
        serializer.Serialize(xmlWriter, m_saveGame);
    }
    cryptoStream.FlushFinalBlock();
    cryptoStream.Close();
    storageStream.Close();
}
As you can see I use AesManaged, because this is an encryption class that I found to work with WP7, MonoTouch and Mono for Windows. That is all that is necessary to store a structure inside an encrypted file. The function to read the encrypted file is similar simple:
void LoadSaveGame()
{
    //Generate a Key based on a Password and HMACSHA1 pseudo-random number generator
    //Salt must be at least 8 bytes long
    //Use an iteration count of at least 1000
    Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(m_password, Encoding.UTF8.GetBytes(m_salt), 1000);

    //Create AES algorithm
    AesManaged aes = new AesManaged();
    //Key derived from byte array with 32 pseudo-random key bytes
    aes.Key = rfc2898.GetBytes(32);
    //IV derived from byte array with 16 pseudo-random key bytes
    aes.IV = rfc2898.GetBytes(16);

    string SAVEFILENAME = "savegame.xml";
    if (m_savegameStorage.FileExists(SAVEFILENAME))
    {
        IsolatedStorageFileStream fs = null;
        try
        {
            fs = m_savegameStorage.OpenFile(SAVEFILENAME, System.IO.FileMode.Open);
        }
        catch (IsolatedStorageException)
        {
            // The file couldn't be opened, even though it's there.
            // You can use this knowledge to display an error message
            // for the user (beyond the scope of this example).
        }

        if (fs != null)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(SaveGameStruct));
            CryptoStream cryptoStream = new CryptoStream(fs, aes.CreateDecryptor(), CryptoStreamMode.Read);
            m_saveGame = (SaveGameStruct)serializer.Deserialize(cryptoStream);
        }
    }
    else
    {
        // if file is not found it is probably the first time the app launched - handle it properly
    }
}
As you can see it is pretty much similar to the file save function, just the streams are the other way round.
And this already completes the code for saving and reading an encrypted data file. Please note, that I didn't handle exceptions at the stream reading and writing parts. So you still have to add a proper exception handling here.
My solution was heavily inspired (and partly copy and pasted) from this awesome blog post.
The encryption solution described here is not perfectly save against being hacked, as the password is saved in the source code and could be disassembled. If you have to encrypt critical data, like passwords, bank data or confidential company data, you should use a more save encryption method. But probably cross platform implementation will be not as easily be achieved then. You can read more about this topic and a solution for Windows Phone here.

No comments:

Post a Comment