The One Man MMO Project: Designing a Secure MMO Login System
I needed an account system for my MMO but I couldn't find much info on designing such a beast. Having figured out much of this on my own, cobbled together from stuff I read, I thought I'd put together an article on a few of the things I learned while building my login server, LoginD.
A lot of systems use email addresses as accounts for logging in. There is one big problem with this practice -- it is really easy to guess a user's account name. Any thief is already half way to being logged in. So for LoginD, I'm using account names, distinct from the user's email address. As a practical matter, account names should have minimum and maximum lengths, be stored as unicode for your international customers, and disallow whitespace for easier handling.
You also should not use account names for forums, chat or guild identities in-game. Have the user create another identity to use in-game or in public.
There are a lot of options for passwords. Length is a big concern, as it pertains to guessability. Passwords should have a minimum length, depending on how secure you want your system to be. I think 6 characters is an absolute minimum. Passwords should be case-sensitive, allow upper and lower case, numbers, symbols, punctuation, anything the user can use to make their password unique.
You probably want to disallow passwords that feature all the same character AAAA, or simple sequences like 123456, ABCDEF, QWERTY and really common passwords like GOD or PASSWORD. You might also consider checking passwords against a password list - a Google search for "password cracker word list" gave me a number of good lists to start with.
The issue with restricting passwords is that you can make it too hard for the user to create a valid password. There's a balancing act between easy-to-remember, and easy-to-guess passwords. Some systems use automatic password generators, but the passwords they create are impossible to remember and users hate them.
I'll probably go with a system that tells the user how secure or insecure their password is when the account is created. Google has a password strength checking API you can use. It returns a value between 1 (weak) and 4 (strong.)
Microsoft had an interesting idea for limiting passwords. Rather than checking potential passwords against a dictionary, they check them against a list of existing passwords in the system. Once a password is used N times (where N is small), it can't be used again and the user needs to use another password. This gives a more even distribution of passwords and prevents too many accounts from being compromised by a single easy-to-guess password.
I wasn't able to find a whole lot of information on login algorithms, but there were some basics I was able to gather. The first rule is to never send login information unencrypted over the internet. The second, is never roll your own encryption.
To avoid sending unencrypted login information, my MMO client hashes both the account name, and the password, using a SHA hash. A hash is a mathematical formula that converts a message into a fixed-length string of digits known as "message digest" that uniquely represents the original message. A hash is a one-way function - that is, it is infeasible to reverse the process to determine the original password. Also, a hash function will not produce the same message digest from two different inputs.
Once the account name and password are hashed, the client sends the two hash results to the server to compare with existing accounts. Hey, we're sending data securely!
Whoops. There is a huge problem with this approach -- if someone captures your login information, they can easily resend the same packet later to log in. This is called a replay attack.
The solution to this replay issue is salt. Salt is a unique bit of information sent by the server to the client which the client then incorporates into the hash calculations. The Salt should be random -- cryptographically random, not stdc random. (If an attacker can guess your random numbers, it compromises your system.) Putting the salt into the hash calculation is a simple matter of putting it into block of data that is hashed. Since both the client and the server know the salt value, the server can easily compare hash values to determine if the login is valid.
You probably want to consider protecting the login information stored on the server against possible attacks from inside your network. The most common way to do this is to hash the password in the server database. To do this and still have the added protection of salt, the server hashes the hashed data retrieved from the database a second time with the salt. The client does the same thing: it hashes the plain text password, adds the salt to the hash result, then hashes again.
Since we don't want the account name to be transmitted in plain text over the internet, we need to use a hash of the account name as a database key so we can look up the correct account using the login data from the client. Since we are storing hashes of the account names, when accounts are created, we must check that the new account being created doesn't have a hash collision with an existing account. Hash algorithms are designed to minimize collisions, but it is still a remote possibility that needs to be addressed. The simple thing to do is in case of a collision, require the user to choose a different account name.
Detecting and Handling Brute-Force Login Attempts
The server maintains a blacklist of IP's with failed logins, independent of login ID. Too many failed logins from a single IP, and that IP gets ignored from then on. If the number of failed attempts for a single IP, or the number of IP's gets too large, Ops gets paged. The list of blacklisted IP's can be shared with the frontend load-balancer to redirect attackers to honeypot machines and keep the regular servers relatively unmolested for real users.
Mitigating Denial of Service Attacks
Denial of Service (DOS) attacks are getting more common. The server maintains a list of active login sessions. The problem comes if there are many, many clients attempting to log in since the server could theoretically run out of memory. To help fight this possibility, sessions are tagged with time values so older sessions can be automatically expired.
Locking Out Accounts
One simple way to slow down brute-force attacks is to limit the number of failed attempts and then lock the account from further attempts. The danger here is that if the limit is too low, you will lock out your fumble-fingered customers and get support calls. Doing some research on the web, I found a study that concluded that locking out after ten attempts gives the best balance of protection and customer convenience. I'm not a big fan of any feature that requires a human interaction to address it, so LoginD reactivates a locked account automatically after a short period of time.
This was an interesting discovery. Typically strings are compared with strcmp which exits as soon as it finds a character which doesn't match. Someone who knows this can attempt to gain access to an account by logging in repeatedly, changing the password hash one character at a time, measuring the amount of time it takes for the login to fail. A, B, C, D, aha! It took .15ms longer to fail that time, first character is a D. DA, DB, DC, DD, DE, DF, aha! And so on. Of course it isn't quite that simple, an actual attacker needs to compensate for network variability in packet timing, but with enough attempts, it is doable. Fortunately there is a simple fix, do the hash string compare yourself and don't exit early on a mismatch.
PIN Client Security
A feature I'm considering adding is a PIN system which is activated whenever a new computer attempts to log into a given account. This prevents attackers from compromising an account by using a keylogger on the client machine.
To implement this feature, each time someone attempts to log in, they also include a hash of some information unique to a given client PC such as the hard disk serial number, network card MAC address, CPU serial number, etc. This gives each PC a unique identity we can tie an account to.
If the login server hasn't seen that hardware hash previously, it requires the entry of a PIN (typically numeric) to validate that the login is legitimate. Since users will typically log in from the same machine, they won't usually have to enter the PIN, so it is less likely to be caught by keyloggers.
The trick with the PIN is that you must only use it for logins from new machines, and make that very clear to users that that is the only situation that the PIN is used. That should protect users from bad guys phishing for their PINs.
The Login Algorithm
The login algorithm as it is currently implemented is:
The Login Database minimally contains these fields (plus others of your choosing):
I should note that when I say Database, I don't mean SQL database. You don't want to be hitting a SQL database for every login attempt. LoginD loads the entire account database into RAM from the SQL server at startup. It propogates changes to the database back to the SQL server in periodic batches to minimize SQL database load.
If you want to learn more about cryptography, I highly recommend reading Applied Cryptography by Bruce Schneier. The book is easy to read and full of helpful algorithms.
Once you are familiar with hashes and cryptograpically secure random number generators, there are a number of free crypto libraries you can use, a couple good ones are OpenSSL and Crypto++.
Copyright (C)2009-2013 onemanmmo.com. All Rights Reserved