Authentication for Games

Jon Watte
gameauth (at) mindcontrol.org


0. Summary

Authentication for games where untrusted clients connect to one or more trusted servers is an interesting special case of general authentication. This article presents some alternatives that can go into designing authentication for such as system, and proposes one particular set of internally cohesive design choices.

Change History

  • 2010-12-21: Updated description about hashed passwords, adding section on rainbow tables and hashing functions.

1. Introduction

Authentication is the process of making sure that someone says who they say they are. For computer games, this comes in two flavors:
  • Game login
    given a user name and password, match the information against a database of allowed players.
  • Game sessions
    given a network packet, make sure that it's sent by the logged in player it says it came from.
Note that Authentication, the ability to determine who sent a specific message, does not have much to do with Encryption, the ability to hide a message from unintended recipients, except one kind of cryptosystem (public/private key systems) have the ability to provide both functions in one go. Unfortunately, these cryptosystems are usually very computationally expensive, so they're not a great match for real-time, online services like computer games.

2. Game Login

To secure game login, you need to worry about a few kinds of problems:
  • Insecure passwords
    Players may have a password that's a common word (like "secret"), the player name itself, or even blank. Your password setting mechanism should detect weak passwords and require a better password. The bare minimum is six characters or more, with at least one letter, and at least one non-letter character.
  • Insecure password storage
    Are your servers secure? Someone might break into them. If they can read the password in clear text, well, that's a problem. Also, are the operators of your system trustworthy? Even after you just let them know that their services will no longer be needed? It's safer to store passwords in a way that means you can't easily read them back.
  • Sniffed passwords
    If you don't use Secure Sockets or some similar encrypted protocol, it's possible that someone can use a packet sniffer to read a password sent in clear text, and then impersonate the user in question.
  • Multiple logins
    The same user shouldn't be allowed to be logged in more than once, or what will happen is that a single player will pay for the game, and share the log-in with all his friends. While this is a small bit of lost revenue, the bigger problem comes when you have to ban the account because some of those "friends" didn't play by the rules. This is a customer service nightmare.
Let's examine security in your client/server design a little closer:
The main trade-off you have to make is whether you want the passwords to be recoverable from the database or not. Depending on this choice, you have the following two options:
  • Recoverable passwords
    If the password is recoverable, then you can use the Challenge Hash Authentication scheme. This is a major benefit. However, the passwords are more vulnerable when they are recoverable in the database.
    Don't store the passwords in cleartext in the database. At least scramble them using some key that you build into the code, to make it harder for the casual inspector to "accidentally" see the password. There's still a danger that the passwords can be compromised, so be vigilant against human factors that can compromise your data.
  • One-way hash passwords
    When setting a password, you calculate a one-way hash of the password (such as a SHA-256 checksum), and store the checksum. When the user tries to log in the next time, he gives you a password, and you calculate a SHA-256 checksum of that, and compare to the stored checksum. If they match, you assume the password was correct.
    The main benefit here is security on the system side; reading an SHA-256 checksum will not immediately let anyone know what the actual password is. Finding another password that generates the same checksum is computationally very hard. However, you must use Secret Exchange Authentication.

  • Also, when using hashed password storage, common passwords like "password" or "123456" will be easily found, for example using a rainbow table. Even less common, but short, passwords can be brute-forced. One defense against this kind of attack is to "salt" the hashed value with some data that changes per entry, so that two entries storing the same password don't have the same hashed value. However, anyone recovering the salt will still be able to recover the password using guessing, unless you have a way of making the salt a lot harder to come by than the password database. This weakness means that you might just decide to store passwords recoverable in the first place, and implement good security practices to avoid leaking the password database in the first place.

2.1. Challenge Hash Authentication

In Challenge Hash Authentication, the server issues some random number, called a "challenge", to the client. The client computes a hash of this random number and the client-side entered password, and sends the hash value back to the server. The server then computes a hash of the remembered challenge value and the stored (plain-text) password, and compares to what the client submitted. If the hashes match, the right password was supplied.
There are three main properties of this system:
  • Passwords are not transmitted
    Thus, someone sniffing the regular login traffic will not be able to determine what the password is.
  • The challenge is specific to each login attempt
    Thus, if you sniff the connection, you cannot remember what the hash is and then just re-supply the same hash value later to log in, because the random challenge generated by the server for a specific login attempt will be different than the last.
  • The server has the clear-text password
    This is a security problem if the server side becomes compromised, but the clear-text password, which is a secret shared by both sides of the communication, can be used to encrypt any data coming to/from that particular client. Care has to be taken to use an encryption algorithm from which the key cannot be recovered, of course -- XOR would not be appropriate!

2.2. Secret Exchange Authentication

In Secret Exchange Authentication, the server stores a hash of the password. The client submits a plaintext password, and the server hashes this plaintext password, and compares the hash to the stored hash. If they match, the right password was supplied.
There is one strength and two weaknesses in this system:
  • The server doesn't store the plain-text password
    If someone breaks in and steals the password file, it doesn't matter, because you can't guess what a password is just by knowing its cryptographic strength hash. On old UNIX machines, the strength of the cryptography is not that high, so you should still keep your /etc/shadow file secure, but with a 256 bit SHA-256 hash, you should be pretty safe. If you can't trust your backup operators, or if you get hacked, then this is a major benefit!
  • The password is transmitted on each login attempt
    If someone can sniff the connection, they could recover the password. Thus, you have to secure the login attempt using some kind of encryption -- but it's not clear what you should use as a key to achieve good security. The most secure way involves a Diffie-Hellman key exchange, which is fairly tricky code to implement right, but will provide for a secure, encrypted channel between two endpoints, without prior exchange of keys. If you wanted to protect against a sophisticated attacker inserting themselves in the middle of the network, you would additionally have to introduce a public key based cryptographic authentification system, but computer games by and large do not have to worry about this kind of attack.
  • The server has the clear-text password
    Because the client sends the clear-text password, the server has at least temporary access to the clear-text password, and can use this as a key for future communication encryption, after the initial log-in. Unfortunately, this means that if someone can impersonate your server, or read the memory of your server process, they can still recover plain-text passwords, even if the password storage file itself is secure.

3. Game Sessions

Once the player has logged in, your troubles are not over. You often need to transfer a player from one machine to another, or to allow the player to disconnect from the server (perhaps through crashing) and then re- connect, resuming where he left off. You clearly can't just let the client say whom it is, and have the server blindly trust that, because it would be trivial for another player to suddenly impersonate another player. Instead, you have to use one of three techniques:
Identity by IP Address, Identity by Autentication Token or Identity by Cryptography.

3.1. Identity by IP Address

In this method, the server looks at the source IP address and port number of the arriving packet, and internally have a table that tells you which player sends on which address/port pair. This is secure, as long as you know that the player will keep sending from the same port, and as long as you trust that the internet will not accept spoofed addresses in packets -- or, if a packet is spoofed, that some round-trip confirmation with the real client can take place.
Such round-trip confirmation can come in the form of explicit acknowledge of particularly suspect commands ("surrender game" for example), or implicitly by using a rotating sequence number starting from a negotiated, random initial starting point.
Sadly, if you use TCP for your connections, or if you need to hand connections off between servers, then the port part of the client's address will not necessarily stay the same. TCP allocates a new port for each connection for each machine it connects to, and even UDP can suffer port renumbering when you switch destination machines, if it's behind a non-friendly NAT gateway (although most home NAT routers don't impose this limitation).

3.2. Idenfity by Authentication Token

When the player logs in, the server determines a duration for which the connection is good; for example, one hour. The server then calculates a hash of a few pieces of data: the client ID, the expiration time of the login session, and a secret number that only the servers know. The server then sends a token to the client, which contains the client ID, the expiration time, and the hash of the three pieces of data.
When the client sends data, it precedes the data with this token. The server picks apart the identity, time, and hash parts, and re-computes the hash with its internal secret number. If the hash matches the hash in the supplied token, the server knows that the packet comes from the player who initially authenticated with the server (barring any sniffing or man-in-the-middle attacks throughout the session).
If the client crashes and then re-connects, it could read the cookie from disk and re-supply it, and as long as the session is still valid, it would be fine. If game sessions last more than an hour, the server that the player is currently talking to would extend the cookie by half an hour each time the cookie is at least half an hour old by re-generating a new token based on client ID, new expiration time, and a hash of those entities and the server secret number. That way, a client can crash and then keep playing as long as it re-connects within half an hour, without having to log in again.

3.3. Identity by Cryptography

If you use a shared secret between the server and the client, such as a plain-text password, then you can use that secret as key. Each packet sent by the client contains the client ID in plaintext, followed by the packet data, encrypted by the shared secret, followed by a checksum of the (unencrypted) data.
When the server receives a packet, it looks up the client password in an internal table, decrypts the message, and verifies the checksum. If the checksum doesn't match, then the data was not encrypted with the right password, and thus the packet did not come from the right client.
Best practice says that part of the encrypted data should be a sequence number, so that successive identical packets will still encrypt differently, and so that capturing and re-playing a packet will have a low likelyhood of being accepted for real.

4. Conclusion

If you are reading this, it's a good sign -- you care about security, and want to do it right!
A good encryption algorithm to use when both sides know the key (such as when using
Secret Exchange authentication and Identity by Cryptography identification) is the Tiny Encryption Algorithm, which is easy to implement, yet cryptographically strong. However, if you can find an already-implemented library implementing the current standard encryption algorithm (AES), it's usually better to use that. For C++, a good encryption library is the crypto++ library, available from SourceForge.
SHA-256 is a commonly used hashing (digest) function, and is currently considered secure. However, to deny a brute force attack, you may want to use a library like bcrypt, with a high load factor. Just make sure to scale your sign-on servers to keep up with the added load.
A sufficient implementation of authentification and identity for a cluster of collaborating trusted servers (such as for an MMORPG) would look something like this:
  • As setup, all servers in the cluster share a large random number, known as the cluster secret(*).
  • Client connects to login server.
  • Using Diffie-Hellman key exchange, client and server negotiates keys for login.
  • The server issues a challenge to the client, consisting of a 128 bit random number.
  • Client calculates a hash of this number concatenated with the password the user enters, and supplies the hash to the server.
  • Server verifies that the hash of the challenge and stored password matches what the client supplied, and issues an authentication ticket consisting of user id, ticket expiration time, and hash of a cluster secret combined with these two items, and supplies to the client. This ticket may also contain the IP address of the client (part of the has as well, in that case).
  • Login server also generates a random key for use by this client during this sessions, and supplies to the client. It also records the key for the user, and the expiry time of the ticket.
  • Client connects to arbitrary server that is part of the game server cluster.
  • Client starts connection by sending authentication ticket.
  • The new server verifies that the ticket has not expired, and that the hash is correct. Using the user id in the ticket, the new server retrieves the encryption key for the user from the login server.
  • The new server and the client also negotiate sequence numbers for future communications at this point.
  • Once authenticated, the new server and client exchange data encrypted with the session key, where the encrypted data includes a hash of the data proper, and a sequence number. Each of these packets only need to have the client ID and ticket identifier (a small integer) as header, not the full authentication cookie.
  • Periodically, the server that the client is currently connected to checks whether the session authentication ticket is about to expire; if this is the case, re-contact login server to get a new ticket and forward it to the client.
If you are using TCP for your transport layer, you might want to use a SSL/TLS library instead, which solves the Diffie-Hellman and session key encryption problem, but you still have to implement a ticketing solution of some sort.
This scheme will protect against the dangers of someone sniffing your passwords on the open internet, and against the dangers of someone trying to use a technical flaw to impersonate another player. However, it will still not protect against the danger of a sophisticated man-in-the-middle attack, for which you would have to add public-key certificates. This technique also does not protect against a user looking at all the data sent to his client -- he controls the machine running the client, so he could always inspect the data in memory, so your game design has to be cheat-proof, or provide incentives for users not to cheat to get around that problem.
Recent reports from independent studies show that the newer voting machines replacing old punch-card machines do not actually follow even these simple practices; if you follow them, your game has a chance of being more secure than the process that selects the next leader of the free world. Whether this is a good thing or not is up to you to decide.

(*)Cluster shared secrets are also known as cluster keys. Cluster keys can periodically change while the cluster stays up, for extra security against compromise. The idea is to keep a "current key" and a "next key", with a time at which the next key becomes the current key. Tickets issued with an expiry time at or after the roll-over time use the "next key"; tickets issued with an expiry time before that use the "current key". When validating tickets, the correct key for the expiry time supplied in the ticket should be used. If the key rotation time is less than or equal to the maximum ticket expiry time, then you also need to keep one or more "old" cluster keys to be able to validate tickets that have not yet expired. However, rolling over cluster keys every two hours, in a system with a one-hour session ticket expiry time, should be quite secure enough for most current games.