[Tutorial] Properly Securing Passwords
#1

Introduction

SA-MP servers these days are constantly complaining of accounts being stolen or hacked. In order to protect these passwords, you must do several things to make sure the password the user enters is always, 100% secured. This means no one can read it, no one can hack it. If you were to attempt to hack it, it would theoretically take months on a supercomputer to crack one password. Yes, that's one password, not the whole database.
It is virtually impossible to "crack" a password using this method. Even if you have the salt, you would have to compute the hash 50 times for each guess that is made.

The Method - Registration
When the user chooses a password when registering, a 8 byte salt is created. This protects against bruteforce attacks and rainbow tables. (See references.) The password is then hashed along with the salt. An example of a raw password with a salt before it is hashed would be "817568n5jkaj1k6a64dj74" (the bold being the raw password, and the italic being the salt). After this, the string you just read is hashed with a SECURE hash algorithm such as sha256 or whirlpool. An example of a hashed password created with sha256 would be "f3fc0170501c092313bb5e7c818677b5fac3ca944122438f9b 9d71e5457bed31". Then, we hash the hash 50 times, and store it in our database. Here's the process shortened into point form:
  1. Password entered
  2. Salt created
  3. Password hashed with salt
  4. Hash hashed 50 times
  5. Hash stored in database
The Method - Logging In
I see nowadays that most scripters are confused with how they will log in their users with only 2 things: a hashed password stored in a database, and a raw password entered in by the user. It really is only a simple logic problem. You don't actually need the user's raw password to compare it with the one they entered at login. What you have to do is hash the password the user enters on login with the salt you grab from the database, 50 times. Once that's done you take the user's inputted (and hashed) password and compare it with the hashed password in the database. If its wrong, and the password entered is correct, there is usually a problem in the code. Here's the process shortened into point form:
  1. User enters raw password
  2. Get salt from database
  3. Hash raw password with salt grabbed 50 times
  4. Get hashed password from database
  5. Compare the two hashes
  6. If true, log in success, if false, log in failure


The Code
Below are some example codes in a couple languages. The code below is written by me, feel free to use it.

Pawn
The following code utilizes the Hash/HMAC plugin by Teprey.
pawn Код:
stock HashPassword(password[], passwordSalt[])
{
    new passwordWithSalt[40+16], hashedPassword[64]; //Introduce variables
    passwordWithSalt = password; //Make passwordwithsalt equal password.
    strcat(passwordWithSalt, passwordSalt); // Add salt to raw password.
    hhash(SHA-256, passwordWithSalt, hashedPassword, 64); // Hash once.
    for(new i=0; i<50; i++) // Start a loop to go 50 times.
    {
        hhash(SHA-256, hashedPassword, hashedPassword, 64); // Hash hashed password 50 times.
    }
    return hashedPassword; // Return the result.
}
PHP
PHP код:
function hashPassword($rawPassword$hashSalt)
{
    
$hashRes hash('sha256'$rawPassword $hashSalt); 
    for(
$round 0$round 50$round++) 
    { 
        
$hashRes hash('sha256'$hashRes); 
    }
    return 
$hashRes;

References
http://en.wikipedia.org/wiki/Salt_(cryptography)
http://en.wikipedia.org/wiki/Brute-force_attack
http://en.wikipedia.org/wiki/Rainbow_table
http://en.wikipedia.org/wiki/Cryptog..._hash_function

Credit to ****** for his simplistic thread design.

Thread Change-Log
2013-07-08
  1. Instead of hashing 65536/65535 times, I updated it to now hash 50 times to eliminate the risk of having lag.
  2. Fixed PHP code, loop was empty code and did nothing
  3. Added "The Method - Logging In" and changed "The Method" to "The Method - Registration"
Reply
#2

Thanks for this!
Reply
#3

Quote:
Originally Posted by ******
Посмотреть сообщение
Firstly, I'm glad that people are starting to take this more seriously and I'm very happy to see this tutorial.

Secondly, I'm glad you like the thread design .

Unfortunately, thirdly, two minor issues (sorry):

1) You don't save the salt at all. You generate a random string, append it, hash it, and discard it. This is no use at all, the salt needs to be stored with the hash so that the same hash can be generated when the user tries to log back in.

2) You are right, this is a proper cryptographic hash in that it is both secure and slow (an odd combination of requirements, but makes brute-forcing basically impossible). The best I've seen before in SA:MP in terms of repeat hashings is 5 (I'm sorry to say that even y_users only does it twice, and only once till barely a month ago). Unfortunately, "slow" is a terrible feature for code in a real-time server like SA:MP as you hold up the whole main data processing loop while doing this one task (and I dread to think how long 65536 hashes take - do you have any numbers). For a while now I have debated a threaded plugin for doing this sort of cryptographicly secure hashing separately to the main loop, with a callback when it is done. Players will not notice that the "logged in" message took 800 ms to appear instead of 300, but they WILL notice the improvement in sync from not having the server hang while doing it.
1) You're right, but I chose not to include the salt saving. I will change it accordingly.

2) This may be how it is for SA-MP, but I do the hash 65536 times in PHP and it works absolutely fine. It does make sense though, as calling a function that belongs to a plugin that many times in a row could cause the server to hang.

I'll be changing this later on.
Reply
#4

little suggestion: this function
pawn Код:
strDest[strLen] = random(2) ? (random(26) + (random(2) ? 'a' : 'A')) : (random(10) + '0');
doesnt distribute the letters equally to numbers. they both got a 50% chance to get picked, but the alphabet contains 16 more letters than numbers, and it got uppercases aswell - they're less represented compared to the numbers. (10 vs 26*2). for 52 letters, there will be only 10 numbers, caused by the ranodm(2) for taking the first choice...
explained differently: the percentage for a number is 4.2* higher than for 1 letter.
Reply
#5

Quote:
Originally Posted by ******
Посмотреть сообщение
I've not actually timed the code, so you could be right, but don't forget that PHP can handle multiple connections at once in separate threads so longer running tasks are not as much of a problem.
I checked how long it takes to do that with Whirlpool and it was about 315ms on average (with i7-2600).

The CPU usage of one core increased to 100% during this, so this indeed is a resource intensive operation. This might result in less smooth gaming experience, particularly with low-end servers.

Just for comparison: "A PC running a single AMD Radeon HD7970 GPU, for instance, can try on average an astounding 8.2 billion password combinations each second, depending on the algorithm used to scramble them.", http://arstechnica.com/security/2012...under-assault/


In case someone cares, here's the script I used:
pawn Код:
main()
{
    new s_time = GetTickCount();

    new buffer[129];
    WP_Hash(buffer, sizeof(buffer), "password123");

    new i = 0;
    while(i++ != 65536)
        WP_Hash(buffer, sizeof(buffer), buffer);

    printf("%d", GetTickCount() - s_time);
    return 1;
}
Reply
#6

Very good to see someone took the initiative to finally make a serious thread about this, good work!
Reply
#7

If you hash the hash twice, I don't really see the point. Hashing it twice only makes the hacker have to crack the code twice for each attempt at cracking the raw password.

Sure, that makes the process take twice as long, but that is not comparable to sixty five thousand times as long, which makes hacking it virtually impossible.

What is the highest number of times I can hash without making the server hang or lag?
Reply
#8

This is a mod for an 8 year old game, not the freaking CIA! Salting and hashing the pass once is more than enough, in my opinion. Even IF your database ever gets compromised, you'll have more than enough time to inform your players that they should change their password.
Reply
#9

Quote:
Originally Posted by Vince
Посмотреть сообщение
This is a mod for an 8 year old game, not the freaking CIA! Salting and hashing the pass once is more than enough, in my opinion. Even IF your database ever gets compromised, you'll have more than enough time to inform your players that they should change their password.
Please.

Do you have any knowledge about passwords?

People usually always use the same password for everything, including SA-MP. If you get a hold of that password, everything the user is registered to could be compromised, even maybe their bank or paypal.

The password is the most valuable piece of information that gets exchanged between client and server, and therefore it must be protected to the full extent.

To be honest, I actually have no idea why some malicious asshole didn't already create a server just to get passwords.
Reply
#10

So what? It is the user's fault for not properly caring for their own security. But even so, it would probably take weeks if not months to crack a single password. Furthermore, you would have to find out the algorithm that was used to generate the output password in the first place.

If I give you this hashed password: 3705a00b66b3ca91038660863b3790604f0159de
and this salt: a1795bcdf0

You would still be unable to find out that it was generated using
PHP код:
sha1CONCATsha1'test123' ) , md5'a1795bcdf0' ) , 123 ) ) 
Edit: I might add that banks require a token to be able to login. At least here in Belgium.
Reply
#11

Your argument is invalid.

Why are we arguing in the first place? Having maximum security has no downsides.
Reply
#12

No downsides? What about time? Computing power?
I stand by my point that rehashing a password 60,000 times is overkill.
Reply
#13

I have to agree that hashing 60 000 times is pretty much overkill, particularly when considering the CPU time it takes.

An 8-character long password combined with a salt of 16 characters has about 1.5*10^42 possible combinations, and with 32-character-long salt there are more than 4*10^69 possible combinations.

It is possible, however, that one gains access to both password and salt. This somewhat eliminates the protection provided by the salt against brute force attacks.

Anyways, in my opinion already salting the password and using a slower hash, like Whirlpool, should be safe enough.

And if you really want to hash many times, 50 times should be enough.


Please correct me if I'm wrong.
Reply
#14

I think I have at least some clue what I'm talking about.

It's great to have a slow hash when it comes to security, you're absolutely right. But, the impact on the server performance is far from acceptable levels in my opinion.

I ran a test where I had a function called every 50 milliseconds, printing the value of GetTickCount function. I added another timer, repeated every second, hashing the password 65536 times like instructed in this tutorial. Then I drew a figure illustrating the time taken between calls to the 50 ms timer called func().



It is clear that the func() has to wait 300ms extra every time a password is hashed.


Here's the code used for the test:
pawn Code:
#include <a_samp>

native WP_Hash(buffer[], len, const str[]);

forward func();
forward hash();

main()
{
    SetTimer("func", 50, true);
    SetTimer("hash", 1000, true);
    return 1;
}

public func()
    printf("%d", GetTickCount());
   
public hash()
{
    new buffer[129], password[] = "password123";
    WP_Hash(buffer, 129, password);
   
    for(new i = 0; i != 65535; i++)
    {
        WP_Hash(buffer, 129, buffer);
    }
}
Reply
#15

I just reminded of that fact as you seemed so convinced that being THE right solution and everyone else being all wrong.


But I have to agree with the reply above. The concept is good.

Multi threading might be one solution. One thread being busy hashing passwords shouldnt be a problem if the other thread(s) keep the server going. I have no experience with plugins (nor multi-threading), but I've heard that multi threading is possible. What do you think?
Reply
#16

Good tutorial. I had a few thoughts though.

As for the server hanging with a 60k hash loop, I have to agree that despite it being secure, it currently is not viable in samp (as it would cause major lagg, especially with a lot of players and or on a shared server), but that doens't mean we should discard it. We could always enhance the algorithm that salts the pass to begin with. Right now we add the salt to the end of the password, why actually? Why don't we add it in, say, the middle of the password? This way the attacker would first have to guess, even with the salt where it is placed in the password. Of course then we could also hash it a few thousand times to be on the safe side. There is almost no way for the attacker to know how much times the password is calculated, IMHO, getting some random value like 1358 would be safer as he would have to do it exactly 1358.

I also had another thought. Why don't we use some random variable like the players name for the salt. And make a function to calculate the salt using that (maybe a hash of some sort), we don't store the salt anywhere and we do not know the salt, this way if the attacker cracks the database he doesn't have access to the salt. Now I think of that a bit more, the latter might be a bit unsafe.

I'm not going to claim to know much on this subject, I never had classes in it and I'm merely expressing a few thoughts I had, if anyone who is more knowing on this subject could dismiss it (if it proves to be unsafe) I'd be glad to know.
Reply
#17

Bumping, thread updated.
Reply
#18

nic tutorial.....really helped
+rep
Reply
#19

Nice, but it's possible to unhash password ? or it's totaly safe??
Reply
#20

Quote:
Originally Posted by Mindcode
View Post
Nice, but it's possible to unhash password ? or it's totaly safe??
Unhashing a hash is possible. However unhashing a password could take anywhere from days to the end of the universe (No I'm not kidding) depending on how good the users password is. With a hash salt it's near impossible to crack.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)