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:
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!" } }
//---------------------------------------------------------- // 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 }
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 |