The Importance of Salting Stored Passwords And How To Do So Correctly

Many will have watched the recent releases of user passwords from Sony (and others) with interest. A lot of people, won't however, realise why Sony's practises were so poor. For many, storing passwords means just that, purely because they aren't aware of the methods available to make it a lot harder for an attacker to gain access to users passwords.

Whilst network security obviously plays a very important part, even when that fails it should be almost impossible for an attacker to tell you what your password was based on nothing but a database dump. In this short post we'll examine exactly how passwords should be handled and stored in a database.

Sony made the (unfortunately) all too common mistake of storing passwords in plaintext, that is to say they did absolutely nothing to obfuscate the user's passwords when they were stored in the database. This, frankly, is lazy and unforgivable.

They could have applied a simple site wide salt, or for very little additional processing overhead could have applied a per-user salt (which is what we're going to do here).

It's neither complicated or difficult to create a salted hash of a password;

So the user's password is passed to our PHP function as the string $password;

<?php

function salt_my_pass($password)
{
// Generate two salts (both are numerical)
$salt1 = mt_rand(1000,9999999999);
$salt2 = mt_rand(100,999999999);

// Append our salts to the password
$salted_pass = "$salt1" . "$password" . "$salt2";

// Generate a salted hash
$pwdhash = sha1($salted_pass);

// Place into an array
$hash['Salt1'] = $salt1;
$hash['Salt2'] = $salt2;
$hash['Hash'] = $pwdhash;

// Return the hash and salts to whatever called our function
return "$hash";

}

?>

Explanation:

We begin by creating two salts (I prefer to use more than 1 to reduce the probability of users sharing salts). We do this by using the built in mt_rand function and passing it two values. These are the absolute lowest and highest numbers we expect to see returned. We use 100 instead of 1000 in the second salt to reduce the (admittedly very low) probability of generating identical salts.

We then concatenate a combination of the salts and the password, so with if salt1 = "123", Password = "Password" and salt2 ="345" the value of $salted_pass would become "123Password345"

We then generate a hash of the salted password. We need to do this to obfuscate the value, as simply storing the salted_pass would still allow an attacker to just look at the value and see what the password was. So instead of "123Password345" we'd be storing "38933985dcc6ce6c86b931bf7f639f984cc2f0a6".

Finally we store all the values in the array 'hash' and return the value to the calling function.

 

The calling function would then take the returned array and store it's values in the database (you need to store the salts so that you can generate a hash to verify user submitted passwords at login time)

A basic table layout would be

  • Username
  • PassHash
  • Salt1
  • Salt2

When a user attempts to login, you simply retrieve the salts and hash from the password, use the salts to hash the password they supplied and compare it to the stored hash;

<?php

// Assuming password was submitted in a form field called Pass using POST
function process_login()
{

// Get the submitted login values from POST Data
$userpass = $_POST['Pass'];
$username = $_POST['User'];

// Retrieve the correct values from the database
// example included below
$realvals = db_retrieve_user_details($username);


// Salt the supplied pass
$salted_pass = $realvals['Salt1'] . "$userpass" . $realvals['Salt2'];

// Hash the Salt
$userpasshash = sha1($salted_pass);

// Compare the hashes
if ($realvals['PassHash'] == $userpasshash)
{

// Password is correct,

log_user_in();

}else{

// Password is incorrect
echo "Error: Invalid Username/Password";

}
}


function db_retrieve_user_details($username)
{
// Retrieve salt and hash from the database for username

// Assuming you've already opened the database

// You need to prevent SQL injection
$username = mysql_real_escape_string($username);

// Create the Query
$query = "SELECT Salt1, Salt2, PassHash from AuthTable WHERE Username='" . $username . "'";

// Run the query
$result = mysql_query($query);

// You should actually include a check here to ensure that a record was actually returned!

// Run the result through array assoc
$retvals = mysql_fetch_assoc($result);

// Return the array to the calling function
return $retvals;

}
?>

 

See how easy that was? This is why so many people are upset with Sony and their counterparts, not salting passwords is nothing short of lazy. The code above is actually somewhat inefficient in that we could create a single salting function and use that when setting a password as well as verifying it. So rather than setting the salts in our first function, we'd simply pass them to the function when we call it.

Although we've used PHP for our examples, the basic procedure remains the same whatever language you are writing in. All that really changes is the syntax!

 

 

Bonus Points

For bonus points security wise, you can make sure that a users salts change every time they change their password. This means that if an attacker does compromise your database and generate rainbow tables for a specific users hash (and it's likely to be an admin account), they can't simply reuse the generated tables to crack the new hash.

 

Things to Remember

It doesn't overly matter what the content of your salts are, so long as they are different for every user. This is why I prefer to use two salts, as although the probability of a non unique salt being generated is very low, the more users you have the more that probability rises. You can use alphanumeric salts if you wish, though I've never been too worried about this as any attacker with a copy of your database will know exactly what the salts are. Using alphanumeric salts does, however, help reduce the likelihood of salt clashes when using a set length.

 

 

Developer Responsibility

Although we are all blaming Sony for the leaks, ultimately the person at fault is the developer who chose to take the lazy route and store passwords in plaintext. Sony maintained and ran the sites, and should be held accountable for their grievous lack of security, but until the development community stops storing passwords in plaintext this kind of event will just continue to happen. Don't be one of those responsible for massive credential leaks, start salting passwords now!