PrerequisitesIn order to help prevent abuse, several of the Newgrounds API functions allow the use of encryption.
Our encryption process uses a combination of JSON, RC4, MD5 and BaseN encoding. These methods were chosen because they allowed us to use the barest character set, and therefor be widely compatible across different platforms.
Every game project gets a unique encryption key. If one game is compromised, any problems will be contained to that game.
Every secure packet will also contain a randomly generated seed/salt. This can be any random string of alphanumeric text, and should be randomly generated for ever individual secure packet.
Test DataFor the sake of this tutorial we will be simulating an unlockMedal command for a made-up game. The command we want to send will have the following data:
- command_id = "unlockMedal"
- medal_id = 321
- session_id = "R4ndOmSe551onStR1nGzz"
- publisher_id = 1
- seed = "s0m3r4ND0mSe3d"
Step 1: Hash the seedBefore we encrypt our data we need to get a 32-character MD5 hash of our seed so the server can validate it.
The hash of our sample seed should be:
Step 2: JSON EncodingNow we can convert our data to a JSON encoded string. We use JSON because it is widely supported, and is significantly smaller, byte-wise, than XML.
Our encoded object should look like this:
Note: The order in which JSON encodes object properties may vary between development environments. When testing your encryption, it would be wise to copy the above JSON string directly!
Step 3: Encrypt the packetNow that our data is a single string, we can encrypt it with our RC4 key. If your RC4 library encrypts as binary, you will need to convert the result to hex.
Our sample data should now look like this:
Step 4: Concat the MD5 and RC4 stringsIn this step we simply do MD5_hash + RC4_string. The result should be:
Later in this tutorial I will refer to this string as "the encrypted string".
Step 5: Compress the string with a custom BaseN radixThis is where the encryption process gets a little complicated. To support old platforms, we have to keep all these packets as strings, and we have to work with a limited character set. In order to compress our final data, we use a custom radix with a BaseN algorithm.
BaseN is simply a way to use a custom base to encode base 10 numbers. We could use the following 16-character radix to encode numbers to Base16, aka hexadecimal: "0123456789ABCDEF". Most programming languages have some kind of convert-to-base library available.
We will be using the following 79 character radix:
This radix contains 79 characters that have proven to be multi-platform-friendly. They are in a random order to add additional obfuscation to the compressed packets we will be working with.
If we use our radix to convert the number 123456 to a string, the result would be:
Because we are working with a very long encryption string, we will actually be converting it in chunks. Essentially we will be taking 6 hex characters, converting them to an integer value, and converting that to a BaseN string. Because we need to be able to decode these chunks, they will each have to be 4 characters long.
In the previous example, 123456 only produced 3 characters. To convert it to 4 characters we simply add the very first character from our radix (which represents zero), to the beginning of our BaseN string. The four-character version of 123456 would be:
As mentioned above, we are going to crunch our encrypted string 6 characters at a time. The first 6 characters of our string are "c63bd6". We need to convert this to a base10 number first. In this case, the number is 12991446.
When we convert 12991446 to a BaseN string we end up with:
We will do this for every 6 chars until we get to the end of our encrypted string. The final, compressed result will be:
Step 6: Record the length of tail charactersWhen our compressed string gets decoded on our servers, it will convert our 4-character chunks back into 6-character hex values. To deal with these, we will add a single integer from 0-5 at the start of our new, compressed string.
The value of this integer should be calculated something like this:
tail_length = encrypted_string.length % 6
In this example, our overflow length is 4. So, our final encoded string will be:
This value is what you will send to our servers.