5
\$\begingroup\$

GameJolt is a gaming platform/website that offers API access with features like achievements tracking for users, etc. I can use the API no problem on my tests but what I'm concerned about is the private key that is issued to my game. How do I ensure that it's protected from people that want to decompile my game? Once they get the API private key, they will be able to do anything with it.

\$\endgroup\$
  • \$\begingroup\$ Does this API key need to be present in game clients or does it suffice to use it from server infrastructure you control? \$\endgroup\$ – Lars Viklund Apr 2 '17 at 18:26
  • \$\begingroup\$ @LarsViklund This can still be received by a hacker. \$\endgroup\$ – Krythic Apr 2 '17 at 19:17
  • \$\begingroup\$ @Krythic I'm not advising to send the secret to the client. I was asking if the API use needed to be on the client at all. \$\endgroup\$ – Lars Viklund Apr 2 '17 at 20:02
  • \$\begingroup\$ @LarsViklund I can probably proxy the API access from a backend that I control but I wanted to not to have to host anything myself if it can be helped \$\endgroup\$ – user400424 Apr 4 '17 at 2:04
3
\$\begingroup\$

This question has actually been asked several times, and the answer is always the same thing; you're not going to like it. It's impossible to keep things like this a secret. You can however, take measures to make it more difficult for your passwords to be discovered. Essentially, you could convert it to a byte array and then perform some kind of hashing algorithm on the bytes. This can also be reversed by anyone with enough time and patience, but so can anything else. What you're basically trying to do is increase the amount of time required to discover the internal passwords. If the time required is greater than what the "hacker" is willing to spend, they will fail, and your game will remain secure. An example of fairly simple byte encryption is as follows:

using System;
using System.IO;
using System.Text;

namespace Your.Engine.Framework
{

    public class Obfuscator
    {
        private int _inext;
        private int _inextp;
        private int[] _seedArray = new int[56];
        private int _seed;

        public int Seed
        {
            get
            {
                return _seed;
            }
            set
            {
                _seed = value;
                int subtraction = (_seed == Int32.MinValue) ? Int32.MaxValue : Math.Abs(_seed);
                int mj = 0x9a4ec86 - subtraction;
                _seedArray[0x37] = mj;
                int mk = 1;
                for (int i = 1; i < 0x37; i++)
                {
                    int ii = (0x15 * i) % 0x37;
                    _seedArray[ii] = mk;
                    mk = mj - mk;
                    if (mk < 0x0)
                    {
                        mk += Int32.MaxValue;
                    }
                    mj = _seedArray[ii];
                }
                for (int k = 1; k < 0x5; k++)
                {
                    for (int i = 1; i < 0x38; i++)
                    {
                        _seedArray[i] -= _seedArray[1 + (i + 0x1e) % 0x37];
                        if (_seedArray[i] < 0)
                        {
                            _seedArray[i] += Int32.MaxValue;
                        }
                    }
                }
                _inext = 0;
                _inextp = 21;
            }
        }
        public Obfuscator()
        {

        }

        public Obfuscator(string seed)
        {
            SetSeed(seed);
        }

        public Obfuscator(int seed)
        {
            SetSeed(seed);
        }

        public void Reset()
        {
            this.Seed = this.Seed;
        }

        public void SetSeed(string seed)
        {
            this.Seed = GenerateHashCode(seed);
        }

        public void SetSeed(int seed)
        {
            this.Seed = seed;
        }

        private static unsafe int GenerateHashCode(string data)
        {
            fixed (char* str = data)
            {
                int num = 0x15051505;
                int num2 = num;
                int* numPtr = (int*)str;
                for (int i = data.Length; i > 0; i -= 4)
                {
                    num = (((num << 0x5) + num) + (num >> 0x1f)) ^ numPtr[0];
                    if (i <= 2)
                    {
                        break;
                    }
                    num2 = (((num2 << 0x5) + num2) + (num2 >> 0x1f)) ^ numPtr[1];
                    numPtr += 2;
                }
                return (num + (num2 * 0x5d588b65));
            }
        }

        private int NextOffset()
        {
            int locINext = _inext;
            int locINextp = _inextp;
            if (++locINext >= 56)
            {
                locINext = 1;
            }
            if (++locINextp >= 56)
            {
                locINextp = 1;
            }
            int retVal = _seedArray[locINext] - _seedArray[locINextp];
            if (retVal == Int32.MaxValue)
            {
                retVal--;
            }
            if (retVal < 0)
            {
                retVal += Int32.MaxValue;
            }
            _seedArray[locINext] = retVal;
            _inext = locINext;
            _inextp = locINextp;
            return retVal;
        }

        public byte[] TransformString(string data)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(data);
            TransformBytes(bytes);
            return bytes;
        }

        public void TransformFile(string inputPath, string outputPath)
        {
            byte[] inputBytes = File.ReadAllBytes(inputPath);
            TransformBytes(inputBytes);
            File.WriteAllBytes(outputPath, inputBytes);
        }

        public void TransformBytes(byte[] bytes)
        {
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = (byte)(bytes[i] ^ NextOffset());
            }
        }
    }
}

The above C# code was written for my game engine. It uses an XOR cipher and an inlined Random class. You take the password's GetHashCode(), run an xor cipher on using the hashcode on each byte, and then store the resulting byte array somewhere in your executable. In order for someone to decompile your password, they will need to first decompile this class successfully, figure out how it works, and just generally do a lot of work to convert everything. Using actual Obfuscation will greatly increase the time further, because the binaries will be simplified down to single letter variables, functions, and class names. It will lose all coherency, which will make it extremely difficult for someone to figure out what's going on.

\$\endgroup\$
  • 3
    \$\begingroup\$ +1, good post. Still, the attacker doesn't need to figure out how the code works, they just need to know what it does (deliver the key) and then run it, so it delivers the key. \$\endgroup\$ – Peter Apr 2 '17 at 21:14
1
\$\begingroup\$

A simple key based software solution is insufficient to solve this problem.

Even if you obfuscate the key, people can easily wait until you generated the key, stop the execution, and grab it from memory. Obfuscation of keys is only relevant in scenarios where attackers fully automate the extraction, e.g. to switch the key for in app payments before they re-release your game under their name.

If creating a user involves some kind of cost, you can try to detect and resolve compromised instructions on the server side, and ban offending user ids, but that's it.

Also, don't use the GameJolt global data storage, unless you want random players to wipe information provided by other players. Stick to the per-user features.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.