Monday, December 20, 2010

SmartFoxServer Decrypt Login Message

Here we are, nearly to where we will officially let our user into the system as a registered user and give them access to the game. We now need to decrypt the incoming data, verify the user against the database, and kick them off or let them into the full game. To start we will need to create a handler so right click [domain.domainname.gamename].handlers and add a new Java class. This will be called DBLoginHandler.

We will start it off with:


package [domain.domainname.gamename].handlers;


import [domain.domainname.gamename].[GameName]Extension;
import [domain.domainname.gamename].entities.SfGuardUser;
import [domain.domainname.gamename].core.AegisBornAccount;
import [domain.domainname.gamename].core.World;
import [domain.domainname.gamename].util.RoomHelper;
import com.smartfoxserver.v2.entities.User;
import com.smartfoxserver.v2.entities.data.ISFSObject;
import com.smartfoxserver.v2.entities.data.SFSObject;
import com.smartfoxserver.v2.extensions.BaseClientRequestHandler;
import java.math.BigInteger;
import java.security.MessageDigest;
import javax.crypto.Cipher;
import javax.persistence.EntityManager;
import javax.persistence.Query;


/**
 *
 * @author Christian
 */
public class DBLoginHandler extends BaseClientRequestHandler {


    @Override
    public void handleClientRequest(User u, ISFSObject data) {
        try {

        } catch (Exception e) {
            trace(e);
        }
    }
}

Just your typical handler. Nothing particularly interesting. Now lets add the rest of the code chunk by chunk and I'll explain along the way. All of this code will go in handleClientRequest in the try block in the correct order.


            // Get the world and add the player.
            World world = RoomHelper.getWorld(this);
            AegisBornAccount player = world.getPlayer(u);
            Cipher cipher = player.GetCipher();
            ISFSObject keyData = data.getSFSObject("login");
            String user = new String(cipher.doFinal(keyData.getByteArray("user")));
            String password = new String(cipher.doFinal(keyData.getByteArray("password")));

This first chunk goes in and gets our world. It uses the world to load our player. Then we request our cipher from the player. This will return our decrypt cypher which uses are server's private key to decrypt the data the client requested. We get our login object from our incoming data. We then get the key user and password and pass it to our cipher to the doFinal function which decrypts the data and returns our string.

            EntityManager entityManager = (([GameName]Extension)this.getParentExtension()).getEntityManagerFactory().createEntityManager();
            entityManager.getTransaction().begin();
            Query q = entityManager.createNamedQuery("SfGuardUser.findByUsername", SfGuardUser.class);
            q.setParameter("username", user);
            SfGuardUser guard = (SfGuardUser)q.getSingleResult();

This next chunk loads up the entity manager from the extension and starts a transaction. It then creates a named query which is created for us by OpenJPA. We then return a single result from it, which should be our 1 user. We will put in the function getEntityManagerFactory when we finish with this class.

            // Execute query
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.reset();
            digest.update(guard.getSalt().concat(password).getBytes());
            byte[] sha1Password = digest.digest();
            BigInteger bi = new BigInteger(1, sha1Password);
            String result = bi.toString(16);
            if (result.length() % 2 != 0) {
                result = "0" + result;
            }
            trace("Result: " + result);
            if (guard.getPassword().equalsIgnoreCase(result)) {
                player.setGuardUser(guard);
                 send("loginsuccess", new SFSObject(), u);
            } else {
                this.getApi().kickUser(u, null, "Username or Password is incorrect.", 4);
            }
This last chunk creates a Digest and loads the SHA-1 algorithm. This is the default algorithm used by Symfony's SfDoctrineGuardPlugin. We then update the digest with the salt column of the database concatinated with our user's typed password. We then get a byte array after the digest converts it. In order to check the password, we need to load it into a new BigInteger and use it's toString() function to convert it into a hex string. The if block is a catch because every number in hex is 2 string values from 0-9 and a-f. If our value isn't even in length, it means the leading 0 was dropped and we must add it back in. The last if checks to see if the password in the database matches the password we just created combining the salt and typed in password. If it does, we return a loginsuccess message. We don't need to create a real object for it because we are just telling our client it is now truly logged in. If the password doesn't match, we kick the user off the server. We do this because we allowed the user to log in without a password because SmartFox during login encrypts the password in its own fashion and there isn't a way to get the clear text version which is what we need to recreate our digest.

Now that we have our handler created we need to add the last little bit to get it ready for use. Open up [GameName]Extension and below private World world; add:
EntityManagerFactory entityManagerFactory;

below the init function add:


    public EntityManagerFactory getEntityManagerFactory() {
        return entityManagerFactory;
    }

and lastly below the request handler for "publickey" add:

        addRequestHandler("login", DBLoginHandler.class);

and at the after all the event handlers add:
        entityManagerFactory = Persistence.createEntityManagerFactory("[GameName]PU");

Please be sure you double check the PU name. You can do this by opening [GameName]Entities and in the META-INF folder open the persistence.xml and look for the persistance unit name and use that name in the quotes.

Thats it for setting up the persistence and retrieving a user from the database and checking to ensure they are a user. You can go further in the if block and check if they are banned or anything else you need to check before you kick the user off the server and provide an error message.

Next time we will cover getting the login message and pushing the user to another scene where they are now "in game"

1 comment:

Chee said...

"and lastly below the request handler for "publickey" ".

Cant find this line in init() method of my extension. have you posted all the code needed for this method?