How do you stop an attacker from just changing a hash?
In the previous episode, I explained how hashes give you the best error detection even when under a direct attack. But the best hash function by itself is not enough. This episode will explain step by step how an attacker can defeat simple attempts to protect data and what you can do about it.
Let’s start with this scenario: You’re designing a game that allows a player to login and then issue instructions to a central server that tell the server where to move the player’s character. If you were playing this game, would you want your opponents to be able to issue commands as if the commands came from you? Not only would the game be unfair, nobody would want to play anymore once the vulnerability became known. So you need to design the game server so it won’t accept any false or malicious commands from other players.
If you’re only worried about detecting accidental errors made during transmission of the instructions as the messages travel to the server computer, then you could use a hash. Just create a hash of each instruction and send that along with each instruction. The server can then perform the same hash, compare them, and ask for the instructions to be retransmitted if the computed hash doesn’t match the hash that was sent.
The problem with sending a simple hash is that an attacker can send commands intended to disrupt another player’s character. All that attacker has to do is send a command that causes the other character to jump off a cliff and the attacker wins the game. The server accepts the command because it has a valid hash and therefore could not possibly have been accidentally changed during transit.
How do we fix this?
One of the most important things to realize about security is that there are boundaries that define what’s trusted vs. everything else outside of the boundary that’s not trusted. At some point, something has to be trusted or you might as well go back to designing a single user game that can only be played on a single computer. Then if that user wants to cheat, no problem. The game will quickly become boring though.
Maybe your first thought is to add a MAC or a message authentication code instead of a simple hash. A MAC is really nothing more than a hash of some secret key along with the message. The hash function doesn’t have to change and produces the same type of output. Just adding a key to the message results in a different hash value.
So where does this key come from? It has to come from the server because that’s inside the trusted zone. In other words, without realizing it, when players agree to play your game, what they’re actually saying is that they agree to trust your server to make sure that the game is fair. They’re not going to trust some other player’s computer.
When a player signs into your server to begin playing, they each get their own secret key. Now when sending commands, the game first starts out with the secret key, then adds a command. The combined key plus command is then hashed and the resulting hash value is sent along with the message.
The server needs to remember each secret key for each player because when it gets a command along with a hash, it does the same thing by hashing the key plus the command and then comparing the calculated hash with the hash that was sent. If the two values match, then it proves two things now. Not only does it prove that the command wasn’t changed, but it also proves that whoever sent the command must also know the secret key. Notice that the secret key is never actually sent with each command. It only needs to be sent when the player logs in.
What’s happening now with your game is called authentication. And a MAC can be used to not only detect errors but to authenticate messages too. You might think the solution is done. That there’s no way for an attacker to replace a hash when the data contains something secret. But there’s a problem with most hash functions. Make sure to listen to the full episode where I describe how another hash creates something called an HMAC and how it can be used to prevent length extension attacks.