Community Pick: Many members of our community have endorsed this article.

Salting Password Hashes

Published:
Updated:
passwordbox.pngSeveral web applications today still use a weak password encryption. Passwords are secret information that should never be seen. That’s one of the reasons why dots or asterisks appear when entering passwords.  

Many websites take the precaution of using secure connections so password never travels across the wire in plain text.  Also, most web applications today take security measures to ensure passwords cannot be seen in plain text once it is stored in the server. Most applications encrypt passwords using one-way encryption. This is also the reason why when we forget our password, we only have an option to reset it. One-way encryption works like this:
 
User registers to the website, supplies password.
Site receives the password, encrypts the password using a one-way encryption algorithm.
Site stores only the encrypted password, also known as the “hash.”
User signs-in to the website, supplies password.
Site receives the password, encrypts the password using a one-way encryption algorithm.
Site compares the password in the database to the password supplied by the user.
The site does not need to know the exact password in order to know if the correct password was supplied. It only needs to compare password hashes. As safe as that sounds, it still presents a possible security exploit, and that's what we'll be discussing in this article.
 

The Exploit

One of the most popular cryptographic hash function is md5. It converts a string into a 32-digit hexadecimal number. Most web applications make use of this function. In PHP, you can use the following to generate the md5 hash of a string:

 
<?php
                      $sha256 = hash('sha256','password');
                      echo $sha256; // outputs 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
                      ?>

Open in new window


Given the same input, the hashing function hash(), always returns the same output.  For example, the output for "password" (as seen above) will always be 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8. Web applications can compare password hashes without ever storing the cleartext password in the database.  That is good.  However, clever (devious) people are now maintaining databases of the hashed values of various commonly-used hashing functions.

These hash values are stored in lookup tables called “rainbow tables” along with the original cleartext password that was used to generate them   And the data is available for easy lookup (you can try the above hashed value in a Google search, and you will learn the cleartext password that was used to generate the hash).

What does this mean? Storing passwords as password hashes can be useless if the password can be obtained from rainbow tables.
 
Ed. Note:
More specifically:  If your database gets compromised and somebody is able to obtain a list of password hash values from it, they can often determine some (or even many) cleartext passwords by comparing the hash values to a list of hash values for commonly-used passwords.

So, if your database security is breached (possibly by an insider), and if you have used a common/standard hashing function, then a "rainbow table" lookup could make many of your accounts vulnerable.
How do we solve this problem?

The basic idea is simple:  Make sure that the hash you store in your database will not be found in anybody's rainbow table.  One way to do that is to use a salt -- add some characters that are not actually part of the password to the input text when generating the hashed value to be stored in the database.  Then do the same when verifying the input password.

Almost any salt value will work to avoid the exploit.   I like to use a random sequence of characters.  The following are the functions I use when dealing with user passwords:

 
function makePass($password) {
                       $salt = generateChars(8);
                       return $salt.hash('sha256',$salt.$password);
                      }
                      function generateChars($length) {
                       $chars = 'qwertyuioplkjhgfdsazxcvbnm098764321QWERTYUIOPASDFGHJKLZXCVBNM';
                       for ($x=0;$x<$length;$x++) $ret.=$chars[rand(0,strlen($chars)-1)];
                       return $ret;
                      }
                      function checkPass($input,$in_file) {
                       $salt = substr($in_file,0,8);
                       if ($salt.hash('sha256',$salt.$input)==$in_file) return true;
                       else return false;
                       }

Open in new window

What this does is it generates an 8-character salt that is pre-pended to the password before and after the encryption. This would ensure the uniqueness of the password hash. The chances of salted password hashes appearing in a rainbow table is very low. But if you’re paranoid, you can increase the length of the salt. Below is a list of the steps in using salted password hashing utilizing the above functions.
 
User registers to the website, supplies password.

Site receives the password, encrypts the password using a one-way encryption algorithm.
      $hash = makePass($_POST[]'password']);

Site stores only the encrypted password, also known as the “hash.”

User signs-in to the website, supplies password.

Site receives the password.

Site checks the database for the password. Site encrypts the password using the randomized salt. Site compares password hashes.
      $is_logged = checkPass($_POST[]'password'],$database_result[]'password']);
2
5,023 Views

Comments (3)

ThievingSixDeveloper
CERTIFIED EXPERT

Commented:
Thumbs up. Some people are still not even hashing them!
Can you show me an example where I would use the functions??  I would love to use this.

Author

Commented:
Hi palmtreeinfotech,

I'm glad you like to use this. The following is an example using the above functions.

Registration page:
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$email = $_POST['email'];

if ($username == '') $err[] = 'Username is required';
else if (strlen($username) > 20) $err[] = 'Username too long';
// also check here if the username exists but for this example, let's skip that

if ($password == '') $err[] = 'Password is required';
else if (strlen($password) < 6) $err[] = 'Password must be at least 6 characters';

if (!filter_var($email,FILTER_VALIDATE_EMAIL)) $err[] = 'Email is invalid';

if (!is_array($err)) {
	$hash = makePass($_POST['password']);
	// Sql::exec("INSERT INTO users (userid,username,password,email) VALUES (NULL,?,?,?)",$username,$hash,$email);
	echo 'User registered!';
} else {
	echo 'Errors!<br />' . implode('<br />',(array)$err);
}
?>

Open in new window


Login page:
<?php
$username = $_POST['username'];
$password = $_POST['password'];
if ($username != '' && $password != '') {
	// $res = Sql::exec('SELECT * FROM users WHERE username=?',$username);
	// $row = @mysql_fetch_array($res);
	$is_logged = checkPass($password,$row['password']);
	if ($is_logged) {
		// proceed with authentication, setcookie, store session to db (or use sessions)
	} else {
		// login failed. invalid password.
	}
}

Open in new window

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.