50

TL;DR

I am working on a gaming system that uses UnityScript and C# on the client and PHP on the server. A MD5 hash of the data plus a shared secret is used to check that the data has not been modified in transit. Is MD5 good enough for this? What other hash algorithm could I use that works in all three languages?

The Problem In More Detail

I have come across some code on a widely used community website about the popular Game Development Platform Unity, and I am now working on improving the MySQL, PHP and security of that code.

The code uses a "secret key" value that is shared between the client and the server. All messages from the client includes a hash of the data (e.g. name and score) plus the secret key, that the server checks before accepting the data. This is basically an authentication that the data passed has not been tampered with.

However, because it's MD5 I think someone who is listening to the network traffic could easily work out the secret key and then post whatever data they want to the server.

So my questions are:

  • Does this current state of affairs warrent improvement? Or is this the intended current use of MD5 (as of January 2017)?
  • Is there another hashing algorithm that could further improve/authenticate this communication activity? Please note that the algorithm would need to work in PHP, UnityScript and C#.

The Code

  • In UnityScript (client side):

    var hash=Md5.Md5Sum(name + score + secretKey);
    
  • In C# (client side):

    string hash = MD5Test.Md5Sum(name + score + secretKey);
    
  • In PHP (server side):

    $secretKey = "mySecretKey"; // Change this value to match the value 
                                //stored in the client javascript below 
    
    $realHash = md5($_GET['name'] . $_GET['score'] . $secretKey); 
    if($realHash == $hash) {
           //interact with database 
    }
    

Further Criteria

  • Some Unity programmers use UnityScript (a Python-like language with a JavaScript-like syntax on the .NET API) but no library can be assumed to be installed.

  • Game Developers are not PHP / MySQL programmers so complex or 3rd party PHP/C#/Js code would probably not be helpful.

  • BCrypt is apparently unwise to use with C#

  • BCrypt needs a static salt in this instance (perhaps derived from the secret key?) but is intended to work with a random salt.

  • PBKDF2 seems to be prefered over BCrypt but can be very slow particularly on mobile devices without much memory.

  • Dealing with a secured server can not be expected (if only...).

  • I don't know enough about the C# security library to really pick out best options from those listed.

  • While the code outlined is simply to do with highscore updates, this code has been in the past - and will be in the future - taken and used for transporting all sorts of data, public and private to various databases.

  • Dealing with hash algorithm interoperability between PHP, UnityScript, C# is a bigger hurdle than I had anticipated. If it was just PHP I'd use password_hash.

Some Thoughts and background to this question:

I updated the title as the edited title seemed to suggest that I wasn't sure about changing MD5, whereas knowing I should change MD5 was one of the core reasons of asking the question here in the first place.

The original question was that I wanted to update the terrible code suggestions given on (amongst other places) here about how to handle interactions between a game on a client machine and data storage on a remote server. Bare in mind this is code suggestions for beginner programmers in Unity and this site is [now] run by Unity Technologies themselves.
If you look, the original question (linked above) was using PHP Mysql_ functions (as well as a rather crappy invalid form of PDO). I felt this would benefit from a rewrite. I saw that the original code had also used an md5 routine to hash the intended data. When it came to the replacement of MD5, I hadn't realised either the vulnerability of compiled project files or the size/scale of the work needed to make this codeblock be actually more secure (on either the interaction with the server or the client side data). My original quest was to find a suitable drop in replacement for the MD5 which could work in the varous languages required (UnityScript, C#, PHP) , as I was aware of it's shortfalls. I hadn't realised (judging by the comments here) how tediously easy it actually is to break into exe's and grab hardcoded data.

This question is NOT about a game I'm making, it is not about My project and the code quoted that I am intending to replace was not written by me. I read a lot of the comments that somehow people are having a go at the messenger, but this question came from my own wish to improve an existing shortcoming on a teaching wiki website. I do have the greatest respect for the knowledge shared in answering this question but I am aware that from the last 6 months exploring the Unity documentation and learning sites that there is a significant gap in securing both local applications and multiplayer or other remote interactions.

I see a lot of responders in comments stating that the answer given by George is a bad answer - but it answers the specific question I asked, at the time. Thanks.

Final Note:
This Blob post from comments by Luke Briggs underlined how much of an eye openingly easy process it is to manipulate local Unity Game Application data. I did not at all comprehend how vulnerable local files are....

Martin
  • 1,057
  • 1
  • 11
  • 18
  • 1
    Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackexchange.com/rooms/51112/discussion-on-question-by-martin-is-md5-a-good-option-for-verifying-game-scores). – Rory Alsop Jan 03 '17 at 11:25
  • Now, please stop leaving comments here - leave them in the chat as required. And moe here will be deleted. – Rory Alsop Jan 03 '17 at 21:24
  • 1
    It's not terribly clear from the question, but are _you_ using Unity? It needs to be noted that Unity **does not support JavaScript**. Rather, it supports UnityScript _(a Python-derived language with a C/JS-like syntax on .NET)_, which is falsely advertised by Unity Technologies as “JavaScript”. This makes a significant difference for how to answer your Q because JS's standard libraries are _not_ available in UnityScript. – Slipp D. Thompson Jan 04 '17 at 18:56
  • writing in Unity I use `C#` and know the "javascript" and "Boo" languages by reference only, in the context of Unity. @SlippD.Thompson – Martin Jan 04 '17 at 19:05
  • 2
    What you're implementing is a (H)MAC, you might want to have a look at some more info regarding that -> https://en.wikipedia.org/wiki/Hash-based_message_authentication_code In 2011 an informational RFC 6151 was approved to update the security considerations in MD5 and HMAC-MD5. For HMAC-MD5 the RFC summarizes that - although the security of the MD5 hash function itself is severely compromised - the currently known " attacks on HMAC-MD5 do not seem to indicate a practical vulnerability when used as a message authentication code." – BlueCacti Jan 06 '17 at 12:08
  • Thanks for the feedback, @GroundZero . I thought after I posted this question that what I was looking for was a HMAC but my issue was finding an algorithm that fits all the various nessecary languages. With the replies to this question I find that the wider issue is that both the data and the hash can be easily manipulated client side, so the scope of the security concern is far wider than I had originally anticipated (wider than any HMAC can solve, I think). – Martin Jan 06 '17 at 12:20
  • @Martin the problem with anything client-side is that the client almost always is able to manipulate things. If you look at CheatEngine etc, they often work with manipulating data in RAM etc, before any of the game's checks/encryptions/hashes/... are done, effectively bypassing the effect of your HMAC. The HMAC will protect you from data manipulation while between the client and your server, but now from data manipulation on the client's side. You'd have to implement server-side 'sanity checks' to confirm that client-data fits within what's to be expected – BlueCacti Jan 06 '17 at 12:42

4 Answers4

203

This approach is fundamentally flawed. Anything on the client side can and will be tampered with by players. It is the same problem which makes DRM untenable - the user owns the machine and all the data on it, including your executables, data in memory, etc. Keeping algorithms secret doesn't work (see Kerckhoffs's principle) because it only takes a small amount of reverse engineering work to work out what your code is doing.

For example, let's say you've got a routine in your game client which posts the level score up to the server, using some cryptography of whatever form to ensure that it isn't tampered with over the network. There are a whole bunch of ways to get around this:

  • Use a memory editing tool such as Cheat Engine to scan for the current score (pause, search the score, unpause, wait for score to change, search again, repeat until you find the memory address which contains the value) and edit it. When the level completes the score value will be happily treated as legitimate by your code, and uploaded to the server.
  • Modify the game executable on disk so that your "level complete" code ignores the real score value and picks a different one.
  • Modify the game executable on disk so that simple things (e.g. killing one monster) increases your score by 1000x more than it should do.
  • Modify the game executable on disk so that you can never die, or have infinite powerups, or one-hit kills, or any other number of helping things, so that you can easily attain a very high score.
  • Perform any of those modifications in-memory after the game loads so that the original executable stays intact (useful for cases where there are annoying integrity checks)
  • Simply expose the "we finished a level, now upload the score" code externally from the process so that it can be called by anyone's program. In native code you can inject a small stub and add an entry to the export table, or just directly copy the code into your own executable. In .NET it's trivial to just modify the class and method's visibility flags and import it into a new program. Now you can submit whatever score values you like without ever even running the game.
  • Reverse engineer the game and get hold of the "secret" key and write your own app to send the score value to the server.

This is just the tip of the iceberg. For more complex games there are all sorts of workarounds to anti-cheat and other problems, but regardless of the defense tricks used there will always be a way to mess with client-side values.

The critical feature of a secure approach is that nothing on the player's computer should be trusted. When your server code receives a packet from a player, assume that your game might not even be running - it could be a totally homebrew piece of code that lies about everything.

Securing multiplayer games (or any kind of game where verifying game state is a requirement) isn't easy. The problem isn't really even a security one, it's a usability and performance one. The simplest way to secure a multiplayer game is to keep the entire game state on the server side, have all the game logic executed and maintained there, and have the client do nothing but send player input over to the server ("user clicked the mouse, user is holding W key") and present the audio and video back to the player. The problem with doing this is that it doesn't make for a very fun game due to network latency, and it's quite hard to scale on the server side. Instead, you have to find a balance between keeping things client-side and server-side. For example, the logic for "does the player have a key to open this door?" must be checked server side, but the logic of when to show the context icon for "open door" stays on the client side. Again, things like enemy health and position must be kept on the server side, and the player's ability to deal damage to that enemy must also be verified (is it near enough?) which means that AI can't be kept client-side, but things like the choice of which idle animation the enemy is displaying will probably be a client-side thing.

We have a few questions on here about game security, so I suggest reading through those:

I also recommend these questions over at GameDev SE:

There's also a great paper on multiplayer security from BlackHat EU 2013, and an article on non-authoritative P2P networking, which may both be of use.

Polynomial
  • 132,208
  • 43
  • 298
  • 379
  • This is a really interesting read, I hadn't quite grasped how easily memory addresses can be abused to return edited values. I find this really interesting as working with PHP there's umpteen topics a day about security and general internet security but the whole security aspect is something from a years work on Unity I've found to be... sparse in game development, in tutorials in teachings, Q&A etc. People on the whole don't seem to consider it, unlike with web stuff tech it seem to be a constant hot topic (and better for it?). Cheers – Martin Jan 02 '17 at 22:48
  • 22
    It's a simple matter of economics. Until recently, there wasn't much money to be made by cheating in games. Or, to look at it from the other side: there wasn't much money to be lost by being cheated, so why bother spending money to prevent cheating? The same is not true of the web, ever since eCommerce, and then eBanking became a thing. – Jörg W Mittag Jan 03 '17 at 00:21
  • 11
    @JörgWMittag It's not even that. People are just generally more acquainted with how to cheat in multiplayer games these days, and the tools available make things so much easier. The financial economics aspect still matters in some areas (particularly MMO bots), but more than anything it's the barrier to entry that was lowered. These days you can make a standalone game trainer that does code patching and memory editing without ever writing a single line of code yourself, or even really understanding the underlying concepts of what you're doing. – Polynomial Jan 03 '17 at 00:40
  • 1
    Heck, games used to include cheat codes, so exploiting wasn't necessary. It wasn't until people were competing over the Internet that anyone cared about preventing cheating. – IllusiveBrian Jan 03 '17 at 15:54
  • 2
    I was about to post a comment in the answer but luckily checked other answers. THIS is the answer that should be marked as the correct one. The only way to ensure the player has not cheated is for the game to send the input sequence to the server and the server to replay it back - it's done this way in DROD, demo consists of player moves, the demo is sent to the server as a score and only when Spider plays it back and confirms the end state is exactly the same it is marked as a valid score. – Maurycy Jan 03 '17 at 17:31
  • 11
    `When your server code receives a packet from a player, assume that your game might not even be running - it could be a totally homebrew piece of code that lies about everything.` So true. When we had dial-up internet we'd call the number by hand and make modem noises (the geek version of beatboxing, I guess). While we didn't know what was going on, we typically added several seconds to the time before the remote side hung up. I always wondered if someone could spend enough time that they'd be able to play a game remotely or something, with just their voice. – Wayne Werner Jan 03 '17 at 19:55
  • Doing it server-side is actually not that impractical, with some modifications. Rather than keeping the state _entirely_ server-side, you keep only some relevant state server-side and do basic sanity checks on updates ("ticks") from the client. For example, keeping the position of players in the server and doing checks to ensure no players have moved faster than is possible or in ways that are impossible, or have moved through boundaries like walls or shields. That doesn't prevent trivial cheating, but it breaks all the "infinite health" cheats. – forest Feb 22 '18 at 09:15
  • After all, if the client is reporting that a player has the same HP even after a projectile intersected their hitbox, it must be lying. No need to do all the complex calculations required to see exactly how much damage is done (taking into account headshots, damage buffs, type of weapon, etc), just basic sanity checks. This is the technique used by a number of online games to limits a cheater's ability so they can only gain a moderate advantage (limited aim bots, better weapons, see through walls) rather than create a god mode (infinite ammo or health, one-hit kill shots, infinite items). – forest Feb 22 '18 at 09:20
  • Is this less of a concern for mobile games? I reckon there are very few iOS users who jailbreaks nowadays. But how about Android? Or is it just as risky as with PC games? – alexx0186 Jan 14 '21 at 20:14
40

There are three problems here:

  • What you're trying to do is fundamentally misguided in several ways, as described in Polynomial's answer.

  • If you insist on doing it anyway, then you're not even using the right primitive! Authenticating messages requires a MAC, not a hash. It's possible to build a MAC out of a hash -- this is what HMAC does -- but it's not as trivial as it looks. What the code you pasted is doing is trying to naively build a MAC out of a hash, and these kinds of naive constructions are usually broken.

  • Also yeah MD5 is a bad hash function. But this is really the least of your worries... MD5 is not the weak point of this design. You're asking for advice on how to replace the bars over your windows with 2-inch thick steel plating -- but your front door is wide open. Justifying this as being "better security" makes no sense.

27

Edit: To clarify, I can't speak to the security of your approach, only the hash portion. There are some strong comments here discouraging your approach. (i.e. a good hash can be part of a secure approach or an insecure approach, just like a good lock can be used on either a good door/frame, or a flimsy door/frame)

In response to your TL;DR I can say quite assuredly that SHA-2 is the recommended drop-in replacement hash function.

If length is an issue, it is better to use a truncated SHA-2 than MD5.

SHA-2 comes in 4 sizes: 224, 256, 384, or 512; with 256 and 512 being the most common.

Keep in mind, this is a General Purpose (Fast) hash, and is not suitable for password storage, however it is suitable to ensure data integrity as your TL;DR suggests.

BCrypt is a Slow Password Hash and is used if you need a sort of one way encryption. (i.e. the password is never stored, only its hash, and is difficult to brute force)

forest
  • 64,616
  • 20
  • 206
  • 257
700 Software
  • 13,807
  • 3
  • 52
  • 82
  • Thank you for your answer. I'll look into SHA-2, I assume you would *recommend* it as a replacement for Md5? – Martin Jan 02 '17 at 22:28
  • 12
    While this is true, it's worth noting the caveat that none of these are sensible authentication options by comparison to proper [SRP](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) schemes, and won't help with the actual problem OP is facing (users faking values). – Polynomial Jan 02 '17 at 22:28
  • 23
    @Martin SHA2 family would be the ideal replacement for MD5 in almost any case you come across MD5. That doesn't change the fact that your approach is broken, and changing the hash does nothing to improve security, but MD5 is indeed deprecated and SHA2 family hashes are indeed the recommended replacement for most use-cases not including hashing of passwords. – Polynomial Jan 02 '17 at 22:29
  • While Polynomial has given some very valuable and wide ranging advice / criticism, George provided the answer I was looking for. Thanks to you both. – Martin Jan 02 '17 at 22:51
  • 37
    @Martin As a Unity developer who sells on the Asset Store I sincerely hope you won't use this answer. Listen to Polynomial. Many Unity developers *do* understand how cheating works; your scheme is **completely flawed** - teaching it/ selling it to other people will spread misinformation. – Luke Briggs Jan 03 '17 at 04:47
  • 6
    @Martin for your reference I've just pulled together two simple images. See [this](http://i.imgur.com/fHKuMOE.png) and also [this](http://i.imgur.com/mL48jgO.png). Games are different from the web because it's the client who is trying to exploit your system (in order to cheat). Unlike on the web, where it's typically a 3rd party trying to exploit the *client* (in order to steal from them). – Luke Briggs Jan 03 '17 at 06:23
  • 1
    While I agree that Polynomial answer below would be better it is probably better to use HMAC (for details see [security section of Wikipedia page](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code#Security)). – Maciej Piechotka Jan 03 '17 at 19:46
  • 6
    I feel that I have to downvote this on the basis that it is completely off topic. The hashing is done on the client side, so it's utterly trivial for anyone with technical skill to modify the values that are hashed. The "secret key" cannot possibly be kept secret because it's given to the client. The whole hashing stuff in the post is really quite nonsensical. @Polynomial's answer much better illustrates how the OP is approaching the problem in a very flawed way and that hashing here just doesn't make sense. Because it doesn't make sense, there's no reason for elaboration. – Kat Jan 04 '17 at 01:30
  • Should I delete my answer? lol – 700 Software Jan 04 '17 at 13:40
  • I would strongly recommend that you flag for a moderator to delete it. (You can't delete it yourself, since it's accepted.) – user2357112 Jan 04 '17 at 17:22
  • @Kat the answer answers my specific requirement for something that is drop in replacement for MD5, for hashing on the client side. From all the comments and excellent answers on this question I do now realise that the whole approach would ideally need a complete rethink, and I stated this in comments further up, but as said, While Polynomial does indeed give a fuller and broader answer, this answer by George covered what I was *originally* looking for. – Martin Jan 04 '17 at 19:09
  • 2
    @Martin I've pulled together [a blog post](https://futrworld.blogspot.co.uk/2017/01/breaking-verified-game-scores-in-unity.html) which shows just how easy this kind of technique is to break, regardless of the hashing function used. There's project files too so it can be followed along. – Luke Briggs Jan 04 '17 at 21:34
  • 1
    @LukeBriggs that's really interesting for me, thanks for going through that. I had never realised quite how easy/simple it is to open up [game] data like this. I have added a link to this blogg to the question footer. – Martin Jan 04 '17 at 22:35
  • "If length is an issue, it is better to use a truncated SHA-2 than MD5." I could not recommend more against this approach. This increases the chances of collusion since multiple hashes could share the contents of the truncated hash. For example, if you were using this method for passwords you go from one possible match to thousands of passwords that could access that account. Same applies to assets. – Bacon Brad Jan 05 '17 at 21:39
  • 1
    @baconface, Collision is [not a practical issue at 128 bits](https://www.google.com/search?q=how+many+millennia+in+2^128+nanoseconds%3F), though it's still better not to truncate. – 700 Software Jan 05 '17 at 21:48
  • 1
    "If length is an issue, it is better to use a truncated SHA-2 than MD5." While truncating any type of hash does in fact increase the risk of collision, I believe that what this statement was trying to get at is the fact that if, for some reason, you can only transmit 128 bits, a SHA-256 hash truncated to 128 bits is still many times more secure than an MD5 hash. – Hitechcomputergeek Jan 06 '17 at 01:42
  • 1
    However, in this situation, MD5 is perfectly fine. Everything Polynomial said below is true; anything on the client can be attacked. However, in this situation, security by obscurity is really your only choice - it at least makes it a bit harder for script kiddies to tamper with your highscores. While MD5 is no spring chicken, it is still far from reversible - you would still need a brute-force attack. In this case, your security by obscurity implementation is going to be broken long before the crypto itself (MD5) is. – Hitechcomputergeek Jan 06 '17 at 02:06
  • While truncating SHA-1 to 128 bits can increase the obscurity of the hash in sniffed traffic, I'd even go as far as to say that you'd be better off rolling your own not-so-clever XOR-based crypto algorithm in this situation, in the hopes that it makes your code harder to reverse-engineer. (Don't waste time on this - your algo /will/ be cracked eventually.) Also, I'd argue that your link about the C# bcrypt implementation being unverified doesn't matter here for the same reasons - it doesn't matter how strong and tested your lock is when there's a giant hole in the wall right next to the door. – Hitechcomputergeek Jan 06 '17 at 02:47
  • @GeorgeBailey I tihnk you should at least add the fact the hasing must be done server-side, a BCrypt client-side is less secure than a Caesar server-side :) – Walfrat Jan 06 '17 at 09:19
11

A friend of mine once made a flash game (yes it's that long ago) and used the same scheme: md5(authenticated data + secret key), then validate it on the server.

I reverse engineered it with some tool and found the secret value he was using in about 30 minutes. Game over.

But yeah, it's the second-best possible option. Regardless of using sha2 instead of md5 and regardless of using hmac instead of a hashing algorithm, user input is never to be trusted. If users run your game on their machine (i.e. it's under their control) and if they can submit a high score, they will always be able to use tools to enhance the scores.

The best thing you can do is to upload a replay (e.g. recorded keypresses) and re-run it on the server, then validate the resulting time. This is a lot of work to code up, takes more server resources, and you will need to have deterministic physics if you use floats anywhere... but that's the only thing better than what you're doing, and people can still script the game if they want to cheat.

Luc
  • 31,973
  • 8
  • 71
  • 135
  • 2
    For turn-based games which allow repeated plays with the same seed, and which are complicated enough that developing an algorithm to find good solutions would itself be an accomplishment, I think one could render the concept of "cheat" moot. For action games, however, there would be no practical way to determine whether someone actually played the game in real time. – supercat Jan 04 '17 at 00:22