views:

111

answers:

4

I've been using PHP's crypt() as a way to store and verify passwords in my database. I use hashing for other things, but crypt() for passwords. The documentation isn't that good and there seems to be a lot of debate. I'm using blowfish and two salts to crypt a password and store it in the database. Before I would store the salt and the encrypted password, (like a salted hash) but realized its redundant because the salt is part of the encrypted password string.

I'm a little confused on how rainbow table attacks would work on crypt(), anyway does this look correct from a security standpoint. I use a second salt to append to the password to increase the entropy of short passwords, probably overkill but why not?

function crypt_password($password) {
if ($password) {
    //find the longest valid salt allowed by server
    $max_salt = CRYPT_SALT_LENGTH;

    //blowfish hashing with a salt as follows: "$2a$", a two digit cost parameter, "$", and 22 base 64
    $blowfish = '$2a$10$';

    //get the longest salt, could set to 22 crypt ignores extra data
    $salt = get_salt ( $max_salt );

    //get a second salt to strengthen password
    $salt2 = get_salt ( 30 ); //set to whatever


    //append salt2 data to the password, and crypt using salt, results in a 60 char output
    $crypt_pass = crypt ( $password . $salt2, $blowfish . $salt );

    //insert crypt pass along with salt2 into database.
    $sql = "insert into database....";

    return true;
    }
}  


function get_salt($length) {
$options = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./';

$salt = '';

for($i = 0; $i <= $length; $i ++) {
    $options = str_shuffle ( $options );
    $salt .= $options [rand ( 0, 63 )];
}
return $salt;
}

function verify_password($input_password)
{
if($input_password)
{
    //get stored crypt pass,and salt2 from the database
    $stored_password = 'somethingfromdatabase';
    $stored_salt2 = 'somethingelsefromdatabase';

    //compare the crypt of input+stored_salt2 to the stored crypt password
    if (crypt($input_password . $stored_salt2, $stored_password) == $stored_password) {
        //authenticated
        return true;
    }
    else return false;
}
else return false;
}
A: 

The idea of a rainbow table is that an attacker can make a table with all possible passwords and their hashes at home.

E.g.

PASSWORD HASH
iloveSO  gjroewjgo
password knbnogjwm
secret   gjroehghe
jbieber  rewgroewj

etc.

With this table, the attacker can quickly convert any hash to a password. Rainbow table uses some tricks so that not all hashes have to be stored, but it still computes all hashes beforehand.

By using a salt, even when storing it with the password, you make this much harder. Instead of hashing every word in a dictionary, the attacker would now have to hash every word with every salt. With a long enough salt, this gives enough combinations to make it unfeasible to compute all these hashes.

So a salt is not meant to be an extra password, known only to the application, it is meant to change the hash function so that it is non-standard.

Sjoerd
He is using a salt, but it is of unknown length. With a significantly large salt value such a rainbow table would be infeasible. For instance 256^256, or a base256 salt of 256 bytes is so large that we don't even have a word for that number of bytes required to crack a password of only a single character.
Rook
+2  A: 

You really should have a look at PHPASS: http://www.openwall.com/phpass/ It's a password hashing framework using crypt() which is used in projects like Wordpress and phpBB.

There is also an excellent article on this website about password hashing, salting and stretching using crypt(): http://www.openwall.com/articles/PHP-Users-Passwords

Robert Ros
I can see how that framework can be helpful for an out of the box approach, but I don't see much different between my code and the code phpass code.
Brian Perin
A: 

This is a misuse of crypt() because you are using a deprecated primitive. Blowfish is very old, twofish is the replacement and even that is old because threefish is almost finalized. You should be using a member of the sha2 family, sha256 or sha512 are both good choices. crypt() can be used with sha256 or sha512, you should use the CRYPT_SHA256 CRYPT_SHA512 parameters respectively.

Also your salts have a very small entropy/size ratio, you are only using an alphanumeric set which is a joke because alphanumeric rainbow tables are the most common. You should be using a full byte which base256, and I recommend a salt that is 256 bytes long. Keep in mind all hash functions are binary safe by definition thus you shouldn't have to worry about null bytes and the like.

Rook
Blowfish can only take alphanumeric chars, so maybe I should be using CRYPT_SHA512.
Brian Perin
@Brian Perin then thats something strange with crypt() because block ciphers are used to encrypt files and traffic all the time.
Rook
The Blowfish used by `crypt()` is not exactly the same as the Blowfish block cipher of old - it's a variant algorithm specifically created for password hashing / key derivation.
caf
@caf I am aware that any block cipher can be used as a message digest by use of feedback. I still think it can accept non-alphanumeric characters, it wouldn't be very useful if it didn't. Perhaps @Brian Perin should check.
Rook
@Rook: It can, but the `crypt()` interface uses a different symbol set in order to produce output that can be included in UNIX-style `passwd` format files.
caf
@caf wow what a terrible function. I wouldn't use that garbage.
Rook
+1  A: 

Your use of crypt() is fine. crypt($input, $stored) == $stored is the way it is designed to be used.

Your get_salt() function is not great, since it is using the often-poor rand() function. You should consider using a stronger random function, like openssl_random_pseudo_bytes(), instead.

caf