Implementation of a cryptographically secure password hash function with Libsodium

  • Hello folks,


    Today i want to show you how you can implement a secure way of hashing passwords of your users.

    For this, we use an external cryptography library. In this case it's Libsodium.

    Why should you take into consideration to implement this?

    It's a common habit that the average user signing up to a Service uses the same password over and over. In case of a database leak (which happens quite often in this scene), any malicious person will be able to crack these passwords (bruteforce, dictionary attacks, rainbow tables) with little effort, because of the use of unsalted and unsuitable Hashes by default. To be clear: Hash functions like MD5, SHA1 and even SHA2 are NOT suitable for password hashing. They are perfectly fine for other things, but not passwords. For this purpose, there are own algorithms like argon2, bcrypt, PBKDF2, which will also add a random generated salt for you. These Hashes have amongst other things, the property to be really compute-intensive, what makes them not really worth to crack, especially because they use a salt. You as Server-Admin are responsible to not put your users into jeopardy by running insecure Systems. A leak of a database containing poor hashed passwords is more catastrophic than you might think.

    Why Libsodium?

    It's a modern and easy-to-use Crypto Library. Most traditional Crypto Libraries are not that straightforward to implement and use, which can lead to additional vulnerabilities.

    Keep in mind however, this not limited to just Libsodium. Use any library supporting password hashing algorithms, as you like.

    HowTo

    In advance, this guide is not intended to be implemented on a production server! For this guide, you need to rehash every password to get it up and running. A standard leaked game core source uses the PASSWORD() function from MySQL by default, which utilizes SHA1 twice in the background, This is what we are gonna replace now. I'm using here the Public Martysama Files 4.9. This should work similar on other Sources, but i cannot guarantee that. Please backup your files, before modifying them.


    First we grab a fresh copy of the Libsodium source and then compile it.


    Notice that you need to enter the commands below on a 32Bit FreeBSD, because the game core is also a 32Bit binary. Another approach, which i didn't tested would be to pass some additional 32Bit Parameters to the ./configure command, so the output will be a 32Bit binary on a 64Bit FreeBSD (otherwise you get an incompatible 64Bit binary).


    Lets start:

    Code
    1. wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.18-stable.tar.gz
    2. tar -xf libsodium-1.0.18-stable.tar.gz
    3. cd libsodium-stable
    4. ./configure
    5. make && make check
    6. make install


    Next open Server/game/src/Makefile (path may differ) and add the following below the OpenSSL lib block:

    Code
    1. # Libsodium
    2. INCDIR += -I/usr/local/include/sodium
    3. LIBS += /usr/local/lib/libsodium.a

    The above code gives the linker the instruction to link Libsodium statically.


    Now open Server/game/src/utils.cpp and add the following header and function:

    C
    1. #include <sodium.h>
    2. bool hash_secure_verify(const char *hashed_pwd, const char *plain_pwd)
    3. {
    4. if (crypto_pwhash_str_verify(hashed_pwd, plain_pwd, strlen(plain_pwd)) != 0) {
    5. return false;
    6. }
    7. return true;
    8. }


    Open Server/game/src/utils.h and add the corresponding declaration:bool hash_secure_verify(const char *, const char *);

    C
    1. bool hash_secure_verify(const char *, const char *);


    Open Server/game/src/db.cpp and do the following changes:

    increase the size to 128 (max length of the hash):

    C
    1. char szEncrytPassword[45 + 1] = {0, };
    2. char szPassword[45 + 1] = {0, };

    to:

    C
    1. char szPlainPassword[128 + 1] = {0, };
    2. char szPasswordHashed[128 + 1] = {0, };

    To match the variable names to the next changes, I renamed them. Replace the new names to all occurrences in this file.


    In the same File replace:

    C
    1. int nPasswordDiff = strcmp(szEncrytPassword, szPassword);

    with:

    C
    1. bool login_status = hash_secure_verify(szPasswordHashed, szPlainPassword);


    Further down remove:

    C
    1. if (openid_server)
    2. {
    3. nPasswordDiff = 0;
    4. }



    Also replace:

    C
    1. if (nPasswordDiff)
    2. {
    3. LoginFailure(d, "WRONGPWD");
    4. sys_log(0, " WRONGPWD");
    5. M2_DELETE(pinfo);
    6. }

    with:

    C
    1. if (!login_status)
    2. {
    3. LoginFailure(d, "WRONGPWD");
    4. sys_log(0, " WRONGPWD");
    5. M2_DELETE(pinfo);
    6. }


    Open Server/game/src/input_auth.cpp and search for the third occerances of:

    Change:

    C
    1. mysql_hash_password(szPasswd).c_str(), szLogin);

    to:

    C
    1. szPasswd, szLogin);

    We need the Password in Plaintext here, so we can verify it with hash_secure_verify().


    That should be all for the C++ part. Recompile the core now.

    From now the core will verify your passwords with the Argon2id password hash function.


    The next step is the database. The default password field in the account/account table is a VARCHAR(42), which is to small for our use. It needs to be adjusted to 128. To do that, execute the following Query:

    SQL
    1. ALTER TABLE account MODIFY password VARCHAR(128);


    Now to work with this new implementation, we need to generate the new hashes.


    In PHP there are two handy functions for this:

    password_hash() - Bitte melden Sie sich an, um diesen Link zu sehen.

    password_verify() - Bitte melden Sie sich an, um diesen Link zu sehen.


    An example of the hashing logic for a SignUp Page:

    PHP
    1. $passwd = $_POST['password'];
    2. $hash = password_hash($passwd, PASSWORD_ARGON2ID); // important here is to use PASSWORD_ARGON2ID, because the Server verifies with this algorithm.


    If you want to build some Login-System, you can use password_verify().

    An example:

    PHP
    1. $passwd_hashed = "$argon2id$v=19$m=65536,t=4,p=1$UUtncTd4YVVrU2QwS3JHZA$HtqrpVt5xvDpI0wd0c7QFqoZDTlaNaDp44OUA9r4HtA" // a value like this should be returned by a Database Query
    2. $passwd_user = 'was_this_my_pwd?';
    3. if (password_verify($passwd_user, $passwd_hashed)) {
    4. echo 'Password is valid!';
    5. }

    PHP 7.3 was used in the above examples.


    Last words

    I hope this guide was clear enough. It's not a guide to 100% Security (in fact there is no), but it's a step in the right direction.

    Last but not least, if you have any contributions/corrections don't hesitate to post them (I also understand German).


    Sincerely,

    Lain0