8.9 Tutorials: Secure Login Example

The source FLA of this example is found under the Examples/AS2/pro_secureLogin folder.

» Introduction

Security is one of the most important aspects in any client-server applications. Very often sensitive data, like user password, are transferred over the network in a readable form and they could be captured with relatively simple software tools.

This technique is also known as "sniffing" and it is usually done analyzing the data that is being sent and received from your network card. Capturing the packets going in and out of your computer is usually not trivial, because the "hacker" would need to have a direct access to your network or computer, however the amount of effort put into these type of "attacks" depends also on what the "reward" is.

While most online games may not need an encrypted login system, multiplayer prize-based games would definately attract more malicious users trying to crack the highscore system and obtain the prizes illegally.

SmartFoxServer has already a good number of bulit-in tools to avoid server hacking attempts and malicious requests.
In this article we will concentrate on the authentication system and we will analyze a simple and effective technique to create a secure login system.

Finally, in the conclusions of this article, we will list some important guidelines that will help you secure your multiuser applications.

» The CHAP Technique

The technique we are going to use in this example, is called CHAP (acronym for Challenge Handshake Authentication Protocol) and it can be implemented very easily with a few lines of server-side actionscript code. For detailed info about CHAP please check this wikipedia page.

In a nutshell, this is how a CHAP-based login works:

CHAP diagram

As you can see we only have two basic steps to create this type of login procedure.
In the first step the client requests a random key to the server. A key is just a simple string made of random characters. We will use this key to hide the user password, applying the MD5 encryption algorithm to it (more about MD5 in a moment)

The second step sends the client password and the key as a single string encoded in MD5 to the server. In other words we concatenate the client's password and the key we received previously, in a single string and we encrypt it. The resulting string is called a "hash" and to the eyes of a possible "hacker" spying your communicatons, it wouldn't just make any sense.

( For more in-depth informations about hashes and MD5 check this article and this article )

On the server side we get the client password by looking for it in our user database, then we proceed in the same exact way we did on the client side and generate the hash of the password + key.

If the hash coming from the client is equal to the server-side generated hash, we can definately allow this user inside the application. On the other hand if the two hashes don't match we can be sure that the client used the wrong password, and we won't let him in.

The interesting aspects of this technique are essentially the following:

1) The user password is never sent through the network in a readable form
2) The random key that we use is unique for each user session. This means that the key you get from the server is only valid for your session.
If someone captures your random key by sniffing your network traffic, he won't be able to use it.

» The example code

Our example is based on the Login Extension tutorial presented in chapter 8.5 of this documentation. We strongly reccomend to read that article before proceeding, if you haven't already done it.

For this application we have created a new zone called "slogin" to which we have attached an extension called secureLogin.as that you can find in the "sfsExtensions/" folder.

In the source folder you will also notice a file called "md5.as". This file contains the MD5 encryption algorithm created in javascript by Paul Johnston, which is released under the BSD licence. You can also check his website, which has some interesting articles on this topic.

You can now open the source .FLA file and inspect the actionscript code located in the label called "connect".
We will now analyze the new parts that were added to the original example:

function handleConnection(success)
{
        if (success)
        {
                // Let's get a random secret key from the server
                if (_global.key == undefined)
                	smartfox.getRandomKey()
                else
                	showLoginFields()
        }
        else
        {
                status_txt.text = "Can't connect!"
        }
}

In the handleConnection() method we check if we already received our unique random key. This is important because the key will be sent by the server one time only in a user session. In other words you will not be able to obtain a new key for the same user session.

The unique key is going to be stored in the _global object so that we can use it from everywhere inside the Flash application.
If the key doesn't exist we request it by calling the getRandomKey() method of the SmartFoxClient class, which will in turn fire an onRandomKey event.

//----------------------------------------------------------
// Handle the server random key
//----------------------------------------------------------
smartfox.onRandomKey = function(key:String)
{
        // Save the key in the global scope, we'll use it later
        _global.key = key
        showLoginFields()
}

//----------------------------------------------------------
// Show the login Textfields
//----------------------------------------------------------
function showLoginFields()
{
        status_txt.text = "Connected, please login:"
        showLogin(true)
        butt_login.onRelease = validateLogin
}

The onRandomKey() handler saves the key for later use and calls the showLoginFields() function which in turn activates the text input fields in the user interface.

Finally when the "Send" button is pressed the sendLogin() method is invoked:

function sendLogin()
{
        if (!_global.isBusy)
        {
                // Get the name from the textfield
                var pass:String = pwd_txt.text
                
                // Create an MD5 hash of the "secret" key + userName
                var md5:String = hex_md5(_global.key + pass)
                
                // Send the encrypted password
                smartfox.login(zone, login_txt.text, md5)
        }
}


In these few lines of code you can see how we secure the password before sending it through the network: the text of the input field is concatenated to the previously received key and finally encoded by calling the hex_md5() function, that we've imported with the #include directive at the beginning of the source code.

» The server side extension code

You will notice that the code on the server side was changed a little from the example that originated this tutorial:

var userList

function init()
{
        // Simple list of users
        // The key is the username, the value is the password
        userList = new Object()
        
        userList["tom"] 	= "tom"
        userList["jerry"] 	= "jerry"
        userList["smart"] 	= "fox"
}

function destroy()
{
        trace("Bye bye!")
}

function handleRequest(cmd, params, user, fromRoom)
{
        
        // no requests to handle here...
}

function handleInternalEvent(evt)
{
        if (evt.name == "loginRequest")
        {
                // This will hold an error message
                var error = ""
                
                
                var nick = evt["nick"]
                var pass = evt["pass"]
                var chan = evt["chan"]
                
                // Get the secret key of this channel
                var key = _server.getSecretKey(chan)
                
                // Generate the MD5 of the key + password
                var md5 = _server.md5(key + userList[nick])
                
                
                if (md5 != pass)
                {
                        error = "Authentication failed"
                }
                else
                {
                        
                        var obj = _server.loginUser(nick, pass, chan)
                        
                        if (obj.success == false)
                        error = obj.error
                        
                }
                
                // Send response to client
                var response = new Object()
                
                if (error == "")
                {
                        response._cmd = "logOK"
                }
                else
                {
                        response._cmd = "logKO"
                        response.err = error
                }
                
                
                _server.sendResponse(response, -1, null, chan)              
        }
}


Just like in the previous article our "user database" is kept in a local array called userList.
The little change we did in the server side code is located in the first lines of the handleInternalEvent() method:

1) we retrieve the key associated with the client channel. Please remember that in this phase of the authentication procedure the client is not yet a "User" for the SmartFoxServer. It will become a "User" only when the login is successfull.
In order to retrieve the unique key attached to the client channel we call the _server.getSecretKey(), passing the channel object as an argument.

2) we concatenate the password received with the unique key and create the MD5 hash string, just like we did previously on the client side.
In order to create the hash we just need to call the _server.md5() method.

3) Finally we can check if the client and server hashes match, and take the appropriate action.

» Conclusions

In closing this article we'd like to reccomend some important security guidelines that should be always kept in mind when developing multiplayer online games and applications.

1) Keep important data on the server side: it's crucial to understand that the client application is always the weaker side of your project.
This is particularly true for Flash SWF files as we all know how easily they can be reverse-engineered.
Also memory editors are commonly used by "crackers" to find and alter important game/application data at runtime.
Beacause of these reasons, you should always keep the game score and the other relevant application properties on the server side and communicate changes to the clients when appropriate.
Hacking the way a score is calculated on the server side is definately way harder and it will discourage most of the malicious users.

2) Always validate client requests: in other words, never trust the client requests.
If a certain method on the server side accepts values between certain ranges, always be sure to check that those values are within the expected ranges. When your're writing your server side code, always keep in mind what could happen if a bad value is sent by the client to your extension methods, and do your best to avoid that these values affect the game.

3) Secure the client application: there are different ways to enforce security on the client side.
To avoid modifications it is always good to obfuscate the SWF file bytecode.
Another simple technique is checking the filesize of the client and see if it matches the expected value, or if it was altered.
More advanced techniques involve splitting the SWF file in different modules that are loaded dynamically.
Use the secure authentications method that we have illustrated in this article to avoid user password detection.

4) Use the SmartFoxServer security tools: the server comes with many high-level and low-level options that can be used to fine tune your security settings. You can use the auto-disconnect tool, the ip filter, the flood-filter, the badwords filter to avoid resource stealing.
Moreover you can limit the size of incoming requests, the number of variables that can be created, the size of the server queues and more...
All these settings are documented in section 2 of this documentation and they will help you to have the maximum control on the server configuration.

Finally applying all these techniques in your multiuser applications will definately help you improve their solidity and reliability, avoiding annoying problmes once the project is live.

 


doc index