views:

159

answers:

3

Like most users, I'm simply trying to figure out a secure way to store passwords. What I haven't found here (or maybe it's my lack of understanding) is how to retrieve a salted hash in my database and separate the salt from the hashed password, especially with unique salts to each password while maintaining the salt+password in a single column.

I'm finding all these cool ways to encrypt passwords (SHA-256, but does MySQL only support SHA/1 and MD5?) and other things from the PHP manual, but not sure how store and retrieve the passwords.

So, far this is all I understand:

SHA('$salt'.'$password') // My query sends the password and salt 
                         // (Should the $salt be a hash itself?)

After that I'm lost with salts.

Retrieving the password without a salt is easy, but the salt confuses me. Where do I get the value from $salt again, especially if it's unique and secure? Do I hide them in another database? Constant (seems unsafe)?

EDIT: Is the key variable in HMAC supposed to be salt or is this something else?

+2  A: 

This seems covered in-depth in a previous post: http://stackoverflow.com/questions/401656/secure-hash-and-salt-for-php-passwords

WolfOdrade
I read the best answer for this before I made this question and it did not answer my question. It provides a good way to store a password securely, but it's not exactly what I'm looking for.
Tarik
+4  A: 

First of all, your DBMS (MySQL) does not need to have any support for cryptographic hashes. You can do all of that on the PHP side, and that's also what you should do.

If you want to store salt and hash in the same column you need to concatenate them.

// the plaintext password
$password = (string) $_GET['password'];

// you'll want better RNG in reality
// make sure number is 4 chars long
$salt = str_pad((string) rand(1, 1000), 4, '0', STR_PAD_LEFT);

// you may want to use more measures here too
// concatenate hash with salt
$user_password = sha512($password . $salt) . $salt;

Now, if you want to verify a password you do:

// the plaintext password
$password = (string) $_GET['password'];

// the hash from the db
$user_password = $row['user_password'];

// extract the salt
// just cut off the last 4 chars
$salt = substr($user_password, -4);
$hash = substr($user_password, 0, -4);

// verify
if (sha512($password . $salt) == $hash) {
  echo 'match';
}

You might want to take a look at phpass, which also uses this technique. It is a PHP hashing solution which uses salting amongst some other things.

You should definitely take a look at the answer to the question WolfOdrade linked to.

igorw
Thanks! A lot of new functions, but I can figure out the nitty gritty myself. This is exactly what I was looking for. I never knew about the subtract string feature, that helps. Is it possible to subtract from the front of a string for different salt placement? Thanks again!
Tarik
Sure, substr can handle both. If you have a 4 character salt at the front you would do: `$salt = substr($user_password, 0, 4); $hash = substr($user_password, 4);`
igorw
Thanks. I tried the hash_hmac, but I'm having trouble comparing the login password with the database password. The salt in the hash_hmac is randomly generated, so how can I subtract it? I also noticed the same thing in your example. sha512($password . $salt) is hashing the password and a unique salt. How would I get that unique salt back to hash the entered password with the database pass? Sorry for the question overload, I'm just inches away from this!
Tarik
Well, in my example the salt is stored together with the password. This allows you to split the two before checking the hash. hash_hmac is basically a more sophisticated way to create a salted password. In WolfOdrade's link they suggest using a salt that consists of a per-user nonce (like my 4 letter salt) and a global application-accessible "site key" (so a compromised db does not compromise the salt). Those two concatenated would be the $key argument for the hash_hmac function.
igorw
The per-user nonce is what I'm having trouble with. It's not unique, but duplication would be very rare. Just like your example, I have a randomized number, but with 10 digits and the padding uses random characters, and then that is hashed as a salt and added in the hmac thing. I just can't wrap my head around where I should keep these per-user salts but within the same column. In WoflOrdade's link, he uses colons between his strings, but I can't find any colons in my hash, so I don't see how they could help.
Tarik
Also, I don't know how hash_hmac hashes the strings together. But either way, how do I know I'm subtracting just enough off to get rid of the salt for verification? Cause I see $salt getting the data from a hash, so wouldn't that mean it's getting an already hashed number? So how does combining an already hashed number with the user password result in the hash?
Tarik
Salt does not get any data from the hash and it is not in any way hashed. The user_password db field stores (HASH + SALT). The length of the salt is known, so you just extract that amount of characters (in my example 4) from user_password as the hash.
igorw
I got it now. Thank you for being so patient. Thank you so much!
Tarik
A: 

Personally I recommend letting MySQL do this with its built in functions.

They way I do this is to create a function in my database config file which returns a key string. The config file should be outside your sites root so that the webserver can access the file but not others. so for example:

function enc_key(){
     return "aXfDs0DgssATa023GSEpxV";
}

Then in your script use it with the sql query and AES_ENCRYPT and AES_DECRYPT functions in MySQL like this:

require_once('dbconf.inc.php');

$key = enc_key();

//When creating a new user
$sql = "INSERT INTO users (username, password) VALUES ('bob', AES_ENCRYPT('{$key}', {$password}))";

//When retrieving users password
$sql = "SELECT AES_DECRYPT('{$key}', password) AS password FROM users WHERE username like 'bob'";
Kelly Copley
This is really not a good idea. You should not encrypt passwords, you should hash them (if somebody hacks your server he would have full access to all your users' passwords). You are also implying SQL injection through the absence of escaping.
igorw
First of all, I reaize that the strings there are open to sql injection; however this was not the topic of the discussion. The sql statements are just examples and I assume that they realize the need to sanitize their user input. I'm not here to write the application for them I'm here to answer specific questions.
Kelly Copley
Second of all, 2 way encryption is just as secure as "hashing" as long as you keep the key safe. I realize that if someone hacks your server they have their passwords BUT if someone hacks your server they have access to everything, including every other piece of information in the database so hashing passwords kind of becomes a moot point don't you think?
Kelly Copley
If somebody gets access to your key (for example: sql injection breaks the query, leading to an error message which exposes the failing query) and to the encrypted passwords, it's easy to get all the plaintext passwords. Had hashes been used this would not be possible.
igorw