Secure User Authentication

From GT-MP
Jump to: navigation, search

Secure User Authentication

Introduction

This page will explain how to safely store passwords and utilize them in authenticating a user.

How do we safely store passwords?

Passwords must not be stored directly, instead they should be hashed with a key derivation function that also salts them.

A key derivation function is a one way function, it can map a plain-text password onto a secret key but not the inverse, moreover, such function should be computationally intensive/slow enough to hinder brute-forcing without hindering the experience of legitimate users. Note that basic hash functions such as MD5 or SHA1 alone aren't recommended for password derivation, as they're designed to be fast to compute and hence fast to brute-force, especially with modern CPUs and GPUs.

Most key derivation functions accept a salt, which is a randomly generated value that mustn't be reused between passwords. A KDF(key derivation function) fed with the same plaintext password but a different salt, will generate different hashes. This way, if one of the hashes is compromised (you find out which password it belongs to), it wouldn't compromise the rest of the hashes. Not salting your passwords makes them vulnerable to dictionary attacks and rainbow table attacks.

How can we do that?

It is generally a bad idea to implement your own crypto, hence why we'll use the official .NET implementation of the PBKDF2 algorithm to hash a password and verify one.

First, you'd want to add the following imports to your file:

using System;
using System.Linq;
using System.Security.Cryptography;

Then, put this class into your project's namespace.

    // All this logic is encapsulated in two static class methods.
    public static class PasswordDerivation
    {
        // Here we define some constants that are used within the generation of the password.
        // You may increase defaultIterations to make the computation slower(and thus harder to brute-force), which would be needed in the future when hardware gets better..
        public const int defaultSaltSize = 16;
        public const int defaultKeySize = 16;
        public const int defaultIterations = 10000;

        // This function takes our plain text password, and hashes it, salting it on the way, 
        // returning the salted hashed password, the salt itself, and some other computation parameters, encoded in a single string.
        public static string Derive(string plainPassword, int saltSize = defaultSaltSize, int iterations = defaultIterations, int keySize = defaultKeySize)
        {
            // The key derivation class automatically generates the salt using the given parameter
            using (var derive = new Rfc2898DeriveBytes(plainPassword, saltSize: saltSize, iterations: iterations))
            {
                // These functions generate raw byte arrays, we encode them as base-64 strings so that they can easily be stored in most databases.
                var b64Pwd = Convert.ToBase64String(derive.GetBytes(keySize));
                var b64Salt = Convert.ToBase64String(derive.Salt);
                // note that we also include the iteration count and key size, so when verifying the password, we'd use them, instead of the class constant values which we may change in the future.
                return string.Join(":", b64Salt, iterations.ToString(), keySize.ToString(), b64Pwd);
            }
        }
        public static bool Verify(string saltedPassword, string plainPassword)
        {

            var passwordParts = saltedPassword.Split(':');
            // we convert our strings back into raw bytes/integers.
            var salt = Convert.FromBase64String(passwordParts[0]);
            var iters = int.Parse(passwordParts[1]);
            var keySize = int.Parse(passwordParts[2]);
            var pwd = Convert.FromBase64String(passwordParts[3]);
            // we generate a salted hashed password with the user input 'plainPassword', using the salt and the computation constants of the original password.
            using (var derive = new Rfc2898DeriveBytes(plainPassword, salt: salt, iterations: iters))
            {
                var hashedInput = derive.GetBytes(keySize);
                // we ensure that the resulting salted hash is equal to our original hash, if so, the two passwords match.
                return hashedInput.SequenceEqual(pwd);
            }
        }
    }

If you look at the code, you'll see that we barely touched any crypto stuff, as it is done by the Rfc2898DeriveBytes class. Most of the work was in bundling the salt, the hash and the computation parameters in a string and unbundling them.

Implementing user authentication

First of all, we need to understand how we'll store our passwords into a database or any file storage system that we'd like to use. Actually into GT-MP development environment, most developers prefer to use SQL to store user datas, but this do not means that you couldn't use others alternatives like noSQL or even a file storage system.

In this guide we'll use MySQL for help you to understand how to store secure password. If you didn't know how to setup a database, check the MySQL Page that will help you to do that. Assuming that you already have your CRUD Class, we can writing our script to store user's password.

This method will retrieve an hash from our PasswordDerivation class and send to your database.

public static Boolean createAccount(string username, string password)
{
	/*  Now we'll generate our salted password and we will just send it to the database
	
		Example: PasswordDerivation.Derive("bestPasswordEver");
		Output: prHRZBO/xCXJrRpgas1cUA==:10000:16:LqTubT/4KhJWR+qwogrZqw==
		This is our salted password, this is how we will see the users password into the database
	*/
	String saltedPassword = PasswordDerivation.Derive(password); 
	
	/* 
		Remember that these instances has to be from your Database Connection class
		Setup your MySQL database: https://wiki.gt-mp.net/index.php?title=MySql
		
		This will open a connection with your MySQL Database.
		After that we create a MySQLCommand to send our queries for your database.
		
		This simplifies a lot you code and avoid boilerplate.
	*/
	try
	{
		using(connection = new MySqlConnection(myConnectionString))
		using(command = connection.CreateCommand())
		
		// We use command parameters to avoid SQL Injections
		// Learn more about: https://dev.mysql.com/doc/connector-net/en/connector-net-programming-prepared-preparing.html
		command.CommandText = "INSERT INTO AccountsTable (username, password) VALUES (@username, @password)";
		command.Prepare();
		
		command.Parameters.AddWithValue("@username", username);
		command.Parameters.AddWithValue("@password", saltedPassword);
		
		// Open the connection
		connection.Open();
		
		/* 
		 * This will execute our query and will return a int value, that means the number of rows that has been affected
		*/
		return command.ExecuteNonQuery() > 0;
	} 
	catch (Exception err) { Console.WriteLine(err); }
	finally { connection.Close(); }
}

In this point everything is okay and we already did the method that created the user account with a secure password. Now we will make a method to login the user. For that, we push the salted password from the database and compare with the input password (your form or whatever you want).

/**
 * This method will connect into the database and return if user password matches, if yes, we can login
 * 
 * @param player is the client that we'll create an account
 * @param username input from our login form
 * @param password plain input password from our login form and isn't salted 
 *
 * @return Boolean return if password match or do not.
 */
public static Boolean loginAccount(string username, string password)
{
	/*
		Setup your MySQL database: https://wiki.gt-mp.net/index.php?title=MySql
		
		This will open a connection with your MySQL Database.
		After that we create a MySQLCommand to send our queries for your database.
		
		This simplifies a lot you code and avoid boilerplate.
	*/
	try 
	{
		using(connection = new MySqlConnection(myConnectionString))
		using(command = connection.CreateCommand())
		
		// This query is for SELECT the username and password from the database to check the password later
		// We use command parameters to avoid SQL Injections
		// Learn more about: https://dev.mysql.com/doc/connector-net/en/connector-net-programming-prepared-preparing.html
		command.CommandText = "SELECT password FROM AccountsTable WHERE [email protected]";
		command.Prepare();
		
		command.Parameters.AddWithValue("@username", username);
		
		// Open the connection
		connection.Open();
		
		// Execute our query and receive the result as a string
		// This will give us the salted password from the database
		// Output example: prHRZBO/xCXJrRpgas1cUA==:10000:16:LqTubT/4KhJWR+qwogrZqw==		
		string saltedPassword = (string) command.ExecuteScalar();
		
		// return if password (input from user) equals to saltedPassword (stored in database)
		return PasswordDerivation.Verify(saltedPassword, password);
	}
	catch (Exception err) { Console.WriteLine(err); }
	finally { connection.Close(); }
}

Now you can securely login and create account for your users using these methods above.

Usage Example

The usage is a bit simple and you can do into your server and client-side, it depends on how you will make your authentication system. In the example below, we will use TriggerServerEvent for communicate with the server-side and try to create a account and OnClientEventTrigger to receive the event from the client-side.

Client-Side:

// Create account with data: username{ 'Testing' }, password { 'NicePassword' }
API.triggerServerEvent("create_account", "testing", "NicePassword");

// Make a login attempt with data: username { 'Testing' }, password { 'NicePassword' }
API.triggerServerEvent("login_account", "testing", "NicePassword");

Server-Side:

public Main()
{
	API.onClientEventTrigger += OnClientEvent;
}

public void OnClientEvent(Client player, string eventName, params object[] arguments) //arguments param can contain multiple params
{
    if (eventName == "create_account") 
    {
        string username = arguments[0];
        string password = arguments[1];
		  
	// make a create account request!
        var result = YourClass.createAccount(username, password);
        if (result)
        {
	    // account created, do anything!
        }
	else
        {
	    // acccount has not created!
	}
    }

    else if (eventName == "login_account") 
    {
        string username = arguments[0];
	string password = arguments[1];
        var result = YourClass.loginAccount(username, password);
        if (result) 
        { 
            // login succeeded, do anything!
        }
	else 
        {   
            // password is wrong, do anything!
        }
    }
}

Now you can do whatever you want, like return a trigger to the client to spawn the player, send him a message, et cetera.