Seitenanfang

When slower is better: Secure your passwords

Dieser Post wurde aus meiner alten WordPress-Installation importiert. Sollte es Darstellungsprobleme, falsche Links oder fehlende Bilder geben, bitte einfach hier einen Kommentar hinterlassen. Danke.


It happend to PerlMonks, Sony and many others: A plain text password table was stolen from the database. Read this carefully to not become the next one on that list.

Adding a user table to a database is easy, same are the signup and login forms, but what happens if someone takes that table? Don't ever think that you're exempt from it because no one is. Many (maybe most) PHP scripts are vulnerable to SQL injection, your Kernel might have security leaks you don't know and even your MySQL server might have big open wholes. You even don't need any software bug if you still use a MySQL root account without password or "root" as the password?

Finally, do you know who has access to the server running your shared webspace or virtual server or might just walk to your computer in the datacenter? Many datacenters store copies of all servers root passwords in a database, you know... plain text copies.

Accepting the fact that your user & password table isn't ever secure from being stolen is the first step of solving the problem. So, if the passwords may be public to the outside world, how to keep them secure?

What about storing something that can be verified against a user-entered password but still is secure even if being stolen? Imagine your passwords might be two-digit numbers, they're safe from being stolen if you simply store the sum of all digits instead of the number itself. Calculate it once when the user signs up or requests a lost password and store the sum into the database. Whenever a user tries to login, calculate the sum of the password he or she entered in the login form and compare it to the stored sum. The password is correct if both match.

You don't like that idea? A thief may easily create another number resulting in the same sum, you're right. It's a (very) basic example of a so-called hashing algorithm, real password one-way hash functions aren't that easy to calculate and they can't be reverted. Using CRC32, the password "foo" turns into the hex chars 2165738c but there's no way of getting it back. The checksum is 32 bits wide and my laptop could calculate about 4.865.763 CRC32 sums per second. It would take less that 15 minutes (882 seconds) to calculate 2^32 checksums for random char combinations. Chances are good that someone finds some char combination turning into 2165738c even sooner than 15 minutes.

Getting slow

Every hash function may be brute-forced in theory, but you could make the job very expensive. A SHA512 sum is even longer (512 bits = 128 hex chars = 64 bytes) and much more expensive to calculate. My laptop could do 386.850 SHA512 checksums per second, less than 10 percent of CRC32, and SHA512 has 256^64 possible results. Calculating all possible checksums would take 10^141 years for this computer.

Usage of SHA512 is very easy, use the Digest::SHA module and push the password through it:

use Digest::SHA qw(sha512_hex);

my $password_hash = sha512_hex($user_password);

Simply do the same as the user logs in:

use Digest::SHA qw(sha512_hex);

my $sth = $dbh->prepare_cached('SELECT passwordhash FROM users WHERE username=?');

$sth->execute($username_from_login_form);

my $logged_in = sha512_hex($password_from_login_form) eq $sth->fetchrow_array ? 1 : 0;

If both password hashes match (the one from the database and the one calculated from the login form), everything should be ok. Chances are very small (1 : 256^64) that someone gets a lucky hit and passes another value having the same hash value.

But computing power increases from Christmas to Christmas and SHA512 calculations might be fast enough to brute force the password in reasonable time (like it happened to CRC32 which was considered secure few years ago). One can't ever be safe from brute force attacks, but still get a good security level - by increasing the password calculation cost. Does it matter at all if a login try is verified in 1, 10 or 100ms? Even a quarter of a second would be ok for most users.

Salt without pepper

Another easy trick for securing passwords: Add some salt. Remember our first example using that two-digit password numbers. Let's add another (salt) digit which is randomly created for each password.

My password is 55 and the random salt number is 3, so my checksum is 5 + 5 + 3 = 13. If the salt digit "3" and the result "13" are stored into the password table, things get harder to get a valid password. Having a list of all two digit numbers and their "hash" digit sum isn't enough, you'ld need 10 lists: One for each possible salt value.

Adding a salt string to the SHA512 example isn't much more complex.

# Create a random 128 byte / 256 hex chars long salt string

my $salt = unpack('H*',join('', map { chr(int(rand(256))) } (1..128)));

my $password_hash = $salt.sha512_hex(pack('H*',$salt).$user_password);

Checking the password requires the salt to be removed from the stored value:

# Extract salt from stored valuemy $salt = substr($db_password_hash,0,256);# Calculate hash value including salt stringmy $entered_password_hash = $salt.sha512_hex(pack('H*',$salt),$entered_password);my $logged_in = $entered_password_hash eq $db_password_hash ? 1 : 0;

The salt value is known to the attacker - it's part of the stored value - but it won't help much. Creating one table of hash values and valid passwords to get them currently takes 10^141 years, but each table would be valid for exactly one salt string. Having a full table would require more than 10^450 years.

Even slower

Still not enough? No problem, adding more security is much easier for the developer than for the hacker to break it.Try out

sub crypt_pw {   my ($password, $salt) = @_;   for (1..1000) {      $password = sha512($salt.$password);   }   return $password;}

Having to recalculate the hash value 1000 times slows down the process by 1000. My computer was at 386.850 SHA512 sums per second but using that loop would slow it down to 386 final results per second. A user logging in would have to wait 1/386 fraction of a second until his password is ready for being compared to the database value. He wouldn't even notice that delay.

Growing by time

As computers get better and better the hashed passwords get weaker and weaker. It doesn't really care if calculation of a comparison table takes 10^450 or 10^50 years but it's still possible to switch to a more secure algorithm lateron - without knowing the cleartext password. There are two strategies for getting more secure even if computer power is increasing.

First one is easy: Just add more calculations. Increase the number of loop runs for each password or add another (newer, even more secure) hashing function after the 1000 SHA512 iterations.

sub crypt_pw {   my ($password, $salt) = @_;   for (1..1000) {      $password = sha512($salt.$password);   }   for (1..1000) {      $password = new_slower_better_hash_function($salt.$password);   }   return $password;}

A simple conversion job could update all your stored password hashes. Just read the stored hash value from the table, pass it to the newly added part (the 1000 new_slower_better_hash_function($salt.$password) runs in the sample) and store the new hash back into the table. All of your passwords will receive the new security bonus at once.

What if you want to get rid of sha512 and replace it by something completely new? A function returning a longer hash than 512 bits won't add more security when using a 512 bit SHA hash value as input.

You still get the un-hashed cleartext passwords at one point: Whenever a user logs into your system. Grab that password, verify it using the old method. Now pass it though your newer, better hashing function to get the new hash value and store it back to the database. You might want to add some kind of encrypting-detection when using this way. Maybe an additional column keeping the "version" number of the hash function used to create that stored hash value or a prefix char. The salt was stored as hex chars because some databases don't do well with binary strings, simply add a "g" as the first letter of the password hash to detect that the password is already converted.

You may also check the stored hash by using the newest hashing first and go to the older one if the first check fails. You may also use the stored hash length if the new hash and the old hash differ in size.

Final hint: Don't simply copy that samples above. Change them a little bit for every new project where you use them. Try out sha512_base64 if you like or other strong hashing functions like bcrypt from Crypt::Blowfish. You may also add hashed variations of the salt string, maybe CRC32 or MD5 it and append it as a suffix, but never ever use a weaker hashing function on the password itself as you might introduce shortcuts to your security loops.

 

7 Kommentare. Schreib was dazu

  1. Watch out for the for "for 1..1000 foo()" method of slowing things down. A sufficiently smart optimizing compiler may realize that running this once will give the same result as a thousand times, since the SHA algorithm is deterministic, and so optimize out the extra 999 runs. So that's a potential vulnerability to this solution.

  2. Sorry, I take that back. While in the general case my comment is true, in your case you did feed the result of each iteration to the next, so that wouldn't be optimized out.

  3. Jakub Narębski

    Or use bcrypt which is designed for hashing passwords, as opposed to SHA-X family which are generic cryptographics hash functions meant to be fast.


    There is Crypt::Eksblowfish::Bcrypt on CPAN for that.

  4. CRC32 is just a checksum, it is never was intended to be used as a cryptographic hash function and never was considered secure (at least by the people who know how it works). There's no actually need to bruteforce it to find dataset with the given CRC32 checksum, it is trivially to compute.

  5. Mariano Spadaccini

    Why compute the algorithm 1000 times is better then once?
    it is not obvious

  6. Sebastian

    Because these routines are relatively slow (in terms of modern computers), using them a thousand times takes a quarter or half a second or so. Doesn't matter for a single password calculation but delays the building of a rainbow- or brute-force table which has to compute every possible password.

  7. That's a pretty standard method called password stretching. More on this here by Bruce Schneier:
    http://www.schneier.com/paper-low-entropy.pdf [KEYSTRETCH Section 4.1]

Schreib was dazu

Die folgenden HTML-Tags sind erlaubt:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>