Search Unity

PlayerPrefs Encryption

Discussion in 'iOS and tvOS' started by mudloop, Jul 13, 2009.

  1. mudloop

    mudloop

    Joined:
    May 3, 2009
    Posts:
    1,082
    Hi,

    I don't know how secure PlayerPrefs is, but I figured adding an extra layer of complexity wouldn't hurt, especially if you're storing stuff like score etc.

    I wanted something easy to implement in a project where I already used PlayerPrefs to store my data. I decided to store the values the same way as PlayerPrefs does, but I also store an md5-encrypted value to check if the value is legit.

    Useage example :
    Code (csharp):
    1.  
    2.     void Start () {
    3.         // this array should be filled before you can use EncryptedPlayerPrefs :
    4.         EncryptedPlayerPrefs.keys=new string[5];
    5.         EncryptedPlayerPrefs.keys[0]="23Wrudre";
    6.         EncryptedPlayerPrefs.keys[1]="SP9DupHa";
    7.         EncryptedPlayerPrefs.keys[2]="frA5rAS3";
    8.         EncryptedPlayerPrefs.keys[3]="tHat2epr";
    9.         EncryptedPlayerPrefs.keys[4]="jaw3eDAs";
    10.        
    11.         EncryptedPlayerPrefs.SetString("test_string", "Hello World");
    12.         Debug.Log(EncryptedPlayerPrefs.GetString("test_string", "default"));
    13.        
    14.         EncryptedPlayerPrefs.SetInt("test_int", 500);
    15.         Debug.Log(EncryptedPlayerPrefs.GetInt("test_int", -1));
    16.        
    17.         EncryptedPlayerPrefs.SetFloat("test_float", 500.456);
    18.         Debug.Log(EncryptedPlayerPrefs.GetFloat("test_float", -1f));
    19.     }
    20.  
    The second parameter in the GetString, GetInt and GetFloat functions is an optional default-value that will be returned if the md5-check is incorrect.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Security.Cryptography;
    5. using System.Text;
    6.  
    7. public class EncryptedPlayerPrefs  {
    8.  
    9.     // Encrypted PlayerPrefs
    10.     // Written by Sven Magnus
    11.     // MD5 code by Matthew Wegner (from [url]http://www.unifycommunity.com/wiki/index.php?title=MD5[/url])
    12.    
    13.    
    14.     // Modify this key in this file :
    15.     private static string privateKey="9ETrEsWaFRach3gexaDr";
    16.    
    17.     // Add some values to this array before using EncryptedPlayerPrefs
    18.     public static string[] keys;
    19.    
    20.    
    21.     public static string Md5(string strToEncrypt) {
    22.         UTF8Encoding ue = new UTF8Encoding();
    23.         byte[] bytes = ue.GetBytes(strToEncrypt);
    24.  
    25.         MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
    26.         byte[] hashBytes = md5.ComputeHash(bytes);
    27.  
    28.         string hashString = "";
    29.  
    30.         for (int i = 0; i < hashBytes.Length; i++) {
    31.             hashString += System.Convert.ToString(hashBytes[i], 16).PadLeft(2, '0');
    32.         }
    33.  
    34.         return hashString.PadLeft(32, '0');
    35.     }
    36.    
    37.     public static void SaveEncryption(string key, string type, string value) {
    38.         int keyIndex = (int)Mathf.Floor(Random.value * keys.Length);
    39.         string secretKey = keys[keyIndex];
    40.         string check = Md5(type + "_" + privateKey + "_" + secretKey + "_" + value);
    41.         PlayerPrefs.SetString(key + "_encryption_check", check);
    42.         PlayerPrefs.SetInt(key + "_used_key", keyIndex);
    43.     }
    44.    
    45.     public static bool CheckEncryption(string key, string type, string value) {
    46.         int keyIndex = PlayerPrefs.GetInt(key + "_used_key");
    47.         string secretKey = keys[keyIndex];
    48.         string check = Md5(type + "_" + privateKey + "_" + secretKey + "_" + value);
    49.         if(!PlayerPrefs.HasKey(key + "_encryption_check")) return false;
    50.         string storedCheck = PlayerPrefs.GetString(key + "_encryption_check");
    51.         return storedCheck == check;
    52.     }
    53.    
    54.     public static void SetInt(string key, int value) {
    55.         PlayerPrefs.SetInt(key, value);
    56.         SaveEncryption(key, "int", value.ToString());
    57.     }
    58.    
    59.     public static void SetFloat(string key, float value) {
    60.         PlayerPrefs.SetFloat(key, value);
    61.         SaveEncryption(key, "float", Mathf.Floor(value*1000).ToString());
    62.     }
    63.    
    64.     public static void SetString(string key, string value) {
    65.         PlayerPrefs.SetString(key, value);
    66.         SaveEncryption(key, "string", value);
    67.     }
    68.    
    69.     public static int GetInt(string key) {
    70.         return GetInt(key, 0);
    71.     }
    72.    
    73.     public static float GetFloat(string key) {
    74.         return GetFloat(key, 0f);
    75.     }
    76.    
    77.     public static string GetString(string key) {
    78.         return GetString(key, "");
    79.     }
    80.    
    81.     public static int GetInt(string key,int defaultValue) {
    82.         int value = PlayerPrefs.GetInt(key);
    83.         if(!CheckEncryption(key, "int", value.ToString())) return defaultValue;
    84.         return value;
    85.     }
    86.    
    87.     public static float GetFloat(string key, float defaultValue) {
    88.         float value = PlayerPrefs.GetFloat(key);
    89.         if(!CheckEncryption(key, "float", Mathf.Floor(value*1000).ToString())) return defaultValue;
    90.         return value;
    91.     }
    92.    
    93.     public static string GetString(string key, string defaultValue) {
    94.         string value = PlayerPrefs.GetString(key);
    95.         if(!CheckEncryption(key, "string", value)) return defaultValue;
    96.         return value;
    97.     }
    98.    
    99.     public static bool HasKey(string key) {
    100.         return PlayerPrefs.HasKey(key);
    101.     }
    102.    
    103.     public static void DeleteKey(string key) {
    104.         PlayerPrefs.DeleteKey(key);
    105.         PlayerPrefs.DeleteKey(key + "_encryption_check");
    106.         PlayerPrefs.DeleteKey(key + "_used_key");
    107.     }
    108.    
    109. }
    110.  

    Enjoy!
     
  2. mayekol

    mayekol

    Joined:
    Oct 23, 2007
    Posts:
    115
    Cool, will definitely check it out... thanks!
     
  3. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    very nice :)
    Thanks for sharing.


    One thing to keep in mind: md5 is not really encryption. Its just a one way projection, basically kind of a weak checksum as it is not even generate unique outputs for unique inputs.
    An encryption would always be two way, allowing you to encrypt and to decrypt (otherwise the encryption would be useless)
     
  4. mudloop

    mudloop

    Joined:
    May 3, 2009
    Posts:
    1,082
    I know it's not actual encryption, but if the app won't use the data without the checksum, it's just as secure. I believe there still is no way to find the original string (especially since two strings could lead to the same md5 string).

    Note, I have a modified version of this where HasKey also returns false when the key is present but the md5 checksum isn't there or is invalid, but it's a bit dirty so I'll see if I can clean it up first.
     
  5. minevr

    minevr

    Joined:
    Mar 4, 2008
    Posts:
    1,015
    Take a look,saved data?
     
  6. Madmaax

    Madmaax

    Joined:
    Aug 9, 2010
    Posts:
    4
    There is a exploit in the code above very easy to solve. Change lines 39 and 47 to:

    Code (csharp):
    1.         string check = Md5(key + "_" + type + "_" + privateKey + "_" + secretKey + "_" + value);
    Otherwise you can switch the value from one field to another if they are the same type and share same secret key.

    Cheers!
     
  7. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    408
  8. stechmann

    stechmann

    Joined:
    Feb 24, 2012
    Posts:
    24
    Extremely nice, lean solution. Thanks so much for sharing this mudloop!!
     
  9. greatkingbear

    greatkingbear

    Joined:
    Jan 20, 2014
    Posts:
    1
    It really helped a lot!
    Thanks!!
     
  10. ina

    ina

    Joined:
    Nov 15, 2010
    Posts:
    729
    Can you explain what diff this makes

     
  11. Maisey

    Maisey

    Joined:
    Feb 17, 2014
    Posts:
    258
    I know this is a very old thread. But for anyone who might just copy-paste and use this:
    Code (CSharp):
    1. int keyIndex = (int)Mathf.Floor(Random.value * keys.Length);
    This line can cause
    IndexOutOfRangeException since Random.value returns a value between 0.0(inclusive) and 1.0(inclusive). In other words the index can be for example 7 when max index is in fact 6.
     
  12. Johannski

    Johannski

    Joined:
    Jan 25, 2014
    Posts:
    553
    Oh very interesting, I always thought the maxvalue would be exclusive. An easy fix would be to use Random.Range(int, int):
    Code (CSharp):
    1. int keyIndex = Random.Range(0, keys.Length);
     
    Last edited: Sep 12, 2017
  13. B-R-J

    B-R-J

    Joined:
    Aug 24, 2015
    Posts:
    1
    Thanx for sharing the scripts @mudloop . I followed the steps given, but I get this error when the game is on run mode. Any reason why?

    NullReferenceException: Object reference not set to an instance of an object
    EncryptedPlayerPrefs.CheckEncryption (System.String key, System.String type, System.String value) (at Assets/Scripts/UI/EncryptedPlayerPrefs.cs:46)

    I added the keys as given in the start method, but still it gives the null reference exception. Anyone got such error?
     
    Last edited: Mar 16, 2018
  14. qmzunity

    qmzunity

    Joined:
    Jul 20, 2017
    Posts:
    1
    Same happened to me. I am not sure if this is the right solution, but declaring a new string[] inside of the EncryptedPlayerPrefs class and specifying the keys directly there works for me.

    Code (CSharp):
    1. public static string[] keys = new string[] { "23Wrudre", "SP9DupHa", "frA5rAS3", "tHat2epr", "jaw3eDAs" };