Am I using PHP's crypt() function correctly? - php

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;
}

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
UPDATE:
Currently there's an alternative for the PHPASS library. In the next version of PHP there are special functions for hashing and verifying passwords (using bcrypt): http://www.php.net/manual/en/ref.password.php. There is a compatibility library that implements these functions for PHP 5.3.7+: https://github.com/ircmaxell/password_compat

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.

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.

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.

Use SHA-512 (if available) with a salt which includes time() and openssl_random_pseudo_bytes(). Crypt is consolidated / efficient because it returns the salt inserted with the hashed string.

Related

How can password_verify and doveadm pw -t verify a password without salt

im currently trying to understand hashes and salts. As i understand it, it should not be possible to verify a password, if i only have the password and the generated hash(that was generated with a random salt ofc).
So how can the password_verify function in PHP verify my password, if i dont give it the salt? Is there a hidden variable in the background, that stores it for the php hashing functions?
And if that is the case, how can
doveadm pw -t '{SHA512-CRYPT}$6$myhash...' -p "qwertz"
verify it too, even if i run it on a complety different computer? Thats a tool, that comes with Dovecot(a MDA).
Here is my PHP code, that creates a random salt with 64 chars, combines it with a password, creates a hash and verifies the hash via password_verify().
I just started working on the whole hash/salt/pepper thing today, so there could be a huge flaw in my whole train of thought.
<?php
$password = "qwertz";
$salt = createSalt(64);
$hash = crypt($password, "$6$$salt");
if (password_verify($password, $hash)) {
echo 'Password is valid!';
} else {
echo 'Invalid password.';
}
function createSalt($length){
$chars = "IrhsYyLofUKj4caz0FDBCe2W9NRunTgQvp7qOXmS5GM3EJV6i8tAHdkPbxwl1Z";
$salt="";
for($i=0; $i < $length; $i++){
$newchar = substr($chars, rand(0,strlen($chars)-1),1);
$salt .= $newchar;
}
return $salt;
}
?>
The hash contains several pieces of information. This article explains the format used by Unix but I believe PHP password functions use a similar format (if not the same):
The hash field itself is comprised of three different fields. They are
separated by '$' and represent:
Some characters which represents the cryptographic hashing mechanism used to generate the actual hash
A randomly generated salt to safeguard against rainbow table attacks
The hash which results from joining the users password with the stored salt and running it through the hashing mechanism specified in
the first field
It can also include the exact per-algorithm options used to generate the hash, such us the algorithmic cost:
var_dump(password_hash('foo', PASSWORD_BCRYPT, [
'cost' => 8,
]));
string(60) "$2y$08$7Z5bTz7xXnom8QsrbZ7uQetMLxOZ7WjuDkUYRIh73Ffa17GV1Tb7q"
Here $2y$08$ means that Bcrypt with cost 8 was used.
If we use the newer Argon2 available in PHP/7.2 there're even more params:
$argon2i$v=19$m=1024,t=2,p=2$YzJBSzV4TUhkMzc3d3laeg$zqU/1IN0/AogfP4cmSJI1vc8lpXRW9/S0sYY2i2jHT0
Some backgrounds to the answer from #Álvaro González :
PHP manual suggests using "password_hash" instead of "crypt" function through "password_hash" is a "crypt()" wrapper ( Because, it uses a strong hash, generates a strong salt, and applies proper rounds automatically. )
"password_hash()" returns the algorithm, cost, and salt as part of the returned hash. Therefore, all information that's needed to verify the hash is included in it. This allows the "password_verify" function to verify the hash without needing separate storage for the salt or algorithm information. : http://php.net/manual/en/function.password-verify.php
Since, "password_hash" is a wrapper for "crypt", "crypt" also does the same, ie., returns the algorithm, cost, and salt as part of the returned hash. and thus "password_verify" can verify the hash.
Now, please check the answer given by #Álvaro González

Providing a salt for encryption with 'crypt()' isn't affecting the output + questions on crypt

I have this code in a PHP page which I am running multiple times. Every time I refresh the page, the salt changes (as it should), but the hash output stays the same.
$iv = mcrypt_create_iv(22);
$ro = rand(6, 9);
$salt = '$2y$'.$ro.'$'.$iv.'$';
echo $salt;
echo '<br />';
$crypt = crypt('test', $salt);
echo $crypt;
Shouldn't the random salt affect the output and make it so that every time I refresh the page the crypt result changes too?
I also have a few general questions on crypt().
Is there any way for you to use a specific hashing algorithm with this function? I would like to use the blowfish algorithm.
Is it the salt length/format that affects which algorithm it chooses?
Lastly, should the salt length for the blowfish algorithm always be 22 characters, or is that just the maximum?
By the way, if anyone is wondering (and if it matters for answering these questions and wasn't obvious), I'm planning to use something similar to store hashed passwords.
Thank you for looking!
The crypt() function on your system does not support the "2y" algorithm. At least Linux GLIBC 2.7 based systems only know DES, $2a$ (blowfish), $5$ (SHA-256) and $6$ (SHA-512). Therefore, the crypt() function assumed DES and only took the first two characters "$2" as salt. That of course produced always the same output.
Try SHA-512 for secure password hashes:
$salt_chars = array_merge(range('A','Z'), range('a','z'), range(0,9));
$salt = '$6$';
for($i=0; $i < 8; $i++) { $salt .= $salt_chars[array_rand($salt_chars)]; }
echo "salt=$salt\n";
$crypt = crypt('test', $salt);
echo "crypt=$crypt\n";
Regarding your second question, you can chose the algorithm by starting the salt with e.g. "$2a$" (instead the $6$ above) for blowfish. Read "man 2 crypt" for details. For some algorithms you can also encode more parameters like "rounds=4000" into the salt.
According the the crypt() manpage, the salt may be up to 16 characters following the $id$.
Longer salts will silently be truncated and produce the same output as for only the first 16 characters.
BTW, even in /etc/shadow, passwords only use 8 characters of salt with the SHA-512 algorithm. As the salt is only to make rainbow table attacks harder, this seems sufficient.

Does a salt and pepper password count as 96-bit encryption?

I have created this code for a salt and pepper password encryption. I assume this is a good way to encrypt passwords, but what level of encryption is this?
I first thought the 'bit' part was the length of characters used but I know that is incorrect. How many bits of encryption would this be?
PHP
function _h() {
$result = "";
$charPool = '0123456789abcdefghijklmnopqrstuvwxyz';
for($p = 0; $p<32; $p++)
$result .= $charPool[mt_rand(0,strlen($charPool)-1)];
return md5(sha1(md5(sha1($result))));
}
$salt = _h();
$pepper = _h();
$pass = $salt.md5($salt.md5($_POST['password']).$pepper).$pepper;
UPDATE
I understand this is not encryption, so I would not be able to say this is encrypted, but instead hashed. Is a hashed password with a salt and pepper secure enough to save a password?
There are quite a lot of problems with your scheme to hash a password.
PHP 5.5 will have it's own functions password_hash() and password_verify() ready, to simplify generating BCrypt password hashes. I strongly recommend to use this excellent api, or it's compatibility pack for earlier PHP versions. The usage is very straightforward:
// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($password, PASSWORD_BCRYPT);
// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($password, $existingHashFromDb);
These functions will solve following problems:
They use a slow adaptable hash function (BCrypt), MD5 is ways too fast for hashing passwords.
They generate a safe salt from the random source of the operating system.
A pepper is not just a second salt, in contrast to a salt, the pepper is secret and will not be stored together with the hash. The same pepper will normally be used for all passwords.
There is no use in complicating the computation of salt and $peppr too much. After all the task is not the retrieval of what was originally used to produce these spices but trather the value of $_POST('a'] and you give away the $saltand $pepper as prefix/suffix (otherwise you couldn't use the stored valuelater to verify an input password with this hash). That being said, do as #zneak commented and use an expert library function.

Update old stored md5 passwords in PHP to increase security

At the moment I have a database with md5 passwords stored, a few years back this was considered a little more secure than it is now and it's got to the point where the passwords need to be more secure.
I've read a lot of posts on here about crypt, md5, hash, bcrypt, etc and have come to consider using something along the lines of the following to 'secure' the passwords better than they are now.
I will use a combination of hash("sha512" and two salts, the first salt will be a site wide salt stored in a file such as .htaccess and the second salt will be created for each user.
Here's an example along the lines of what I'm testing at the moment:
.htaccess
SetEnv SITEWIDE_SALT NeZa5Edabex?26Y#j5pr7VASpu$8UheVaREj$yA*59t*A$EdRUqer_prazepreTr
example.php
$currentpassword = //get password
$pepper = getenv('SITEWIDE_SALT');
$salt = microtime().ip2long($_SERVER['REMOTE_ADDR']);
$saltpepper = $salt.$pepper;
$password = hash("sha512", md5($currentpassword).$saltpepper);
The salt would obviously need to be stored in a separate table to allow checking of future inserted login passwords but it would never be possible for a user to see. Do you think this is a sufficient way to go about this?
Ok, let's go over a few points here
What you have in $salt is not a salt. It's deterministic (meaning that there is no randomness in there at all). If you want a salt, use either mcrypt_create_iv($size, MCRYPT_DEV_URANDOM) or some other source of actual random entropy. The point is that it should be both unique and random. Note that it doesn't need to be cryptographically secure random... At absolute worst, I'd do something like this:
function getRandomBytes($length) {
$bytes = '';
for ($i = 0; $i < $length; $i++) {
$bytes .= chr(mt_rand(0, 255));
}
return $bytes;
}
As #Anony-Mousse indicated, never feed the output of one hash function into another without re-appending the original data back to it. Instead, use a proper iterative algorithm such as PBKDF2, PHPASS or CRYPT_BLOWFISH ($2a$).
My suggestion would be to use crypt with blowfish, as it's the best available for PHP at this time:
function createBlowfishHash($password) {
$salt = to64(getRandomBytes(16));
$salt = '$2a$10$' . $salt;
$result = crypt($password, $salt);
}
And then verify using a method like this:
function verifyBlowfishHash($password, $hash) {
return $hash == crypt($password, $hash);
}
(note that to64 is a good method defined here). You could also use str_replace('+', '.', base64_encode($salt));...
I'd also suggest you read the following two:
Fundamental difference between hashing and encrypting
Many hash iterations, append salt every time?
Edit: To Answer the Migration Question
Ok, so I realize that my answer did not address the migration aspect of the original question. So here's how I would solve it.
First, build a temporary function to create a new blowfish hash from the original md5 hash, with a random salt and a prefix so that we can detect this later:
function migrateMD5Password($md5Hash) {
$salt = to64(getRandomBytes(16));
$salt = '$2a$10$' . $salt;
$hash = crypt($md5Hash, $salt);
return '$md5' . $hash;
}
Now, run all the existing md5 hashes through this function and save the result in the database. We put our own prefix in so that we can detect the original password and add the additional md5 step. So now we're all migrated.
Next, create another function to verify passwords, and if necessary update the database with a new hash:
function checkAndMigrateHash($password, $hash) {
if (substr($hash, 0, 4) == '$md5') {
// Migrate!
$hash = substr($hash, 4);
if (!verifyBlowfishHash(md5($password), $hash) {
return false;
}
// valid hash, so let's generate a new one
$newHash = createBlowfishHash($password);
saveUpdatedPasswordHash($newHash);
return true;
} else {
return verifyBlowfishHash($password, $hash);
}
}
This is what I would suggest for a few reasons:
It gets the md5() hashes out of your database immediately.
It eventually (next login for each user) updates the hash to a better alternative (one that's well understood).
It's pretty easy to follow in code.
To answer the comments:
A salt doesn't need to be random - I direct you to RFC 2898 - Password Based Cryptography. Namely, Section 4.1. And I quote:
If there is no concern about interactions between multiple uses
of the same key (or a prefix of that key) with the password-
based encryption and authentication techniques supported for a
given password, then the salt may be generated at random and
need not be checked for a particular format by the party
receiving the salt. It should be at least eight octets (64
bits) long.
Additionally,
Note. If a random number generator or pseudorandom generator is not
available, a deterministic alternative for generating the salt (or
the random part of it) is to apply a password-based key derivation
function to the password and the message M to be processed.
A PseudoRandom Generator is available, so why not use it?
Is your solution the same as bcrypt? I can't find much documentation on what bcrypt actually is? - I'll assume that you already read the bcrypt Wikipedia Article, and try to explain it better.
BCrypt is based off the Blowfish block cipher. It takes the key schedule setup algorithm from the cipher, and uses that to hash the passwords. The reason that it is good, is that the setup algorithm for Blowfish is designed to be very expensive (which is part of what makes blowfish so strong of a cypher). The basic process is as follows:
A 18 element array (called P boxes, 32 bits in size) and 4 2-dimensional arrays (called S boxes, each with 256 entries of 8 bits each) are used to setup the schedule by initializing the arrays with predetermined static values. Additionally, a 64 bit state is initialized to all 0's.
The key passed in is XOred with all 18 P boxes in order (rotating the key if it's too short).
The P boxes are then used to encrypt the state that was previously initialized.
The ciphertext produced by step 3 is used to replace P1 and P2 (the first 2 elements of the P array).
Step 3 is repeated, and the result is put in P3 and P4. This continues until P17 and P18 are populated.
That's the key derivation from the Blowfish Cipher. BCrypt modifies that to this:
The 64 bit state is initialized to an encrypted version of the salt.
Same
The P boxes are then used to encrypt the (state xor part of the salt) that was previously initialized.
Same
Same
The resulting setup is then used to encrypt the password 64 times. That's what's returned by BCrypt.
The point is simple: It's a very expensive algorithm that takes a lot of CPU time. That's the real reason that it should be used.
I hope that clears things up.
Implementation of your new, more secure, password storage should use bcrypt or PBKDF2, as that's really the best solution out there right now.
Don't nest things, as you don't get any real security out of this due to collisions as #Anony-Mousse describes.
What you may want to do it implement a "transition routine" where your app transitions users over from the old MD5-based system to the new more secure system as they log in. When a login request comes in, see if the user is in the new, more secure, system. If so, bcrypt/PBKDF2 the password, compare, and you're good to go. If they are not (no one will be at first), check them using the older MD5-based system. If it matches (password is correct), perform the bcrypt/PBKDF2 transformation of the password (since you now have it), store it in the new system, and delete the old MD5 record. Next time they log in, they have an entry in the new system so you're good to go. Once all of the users have logged in once you implement this, you can remove this transition functionality and just authenticate against the new system.
Do not nest md5 inside your sha512 hash. An md5 collision then implies a hash collision in the outer hash, too (because you are hashing the same values!)
The common way of storing passwords is to use a scheme such as
<method><separator><salt><separator><hash>
When validating the password, you read <method> and <salt> from this field, reapply them to the password, and then check that it produces the same <hash>.
Check the crypt functions you have available. On a modern Linux system, crypt should be able to use sha512 password hashing in a sane way: PHP crypt manual. Do not reinvent the wheel, you probably just screw up more badly than md5, unless you are an expert on cryptographic hashing. It will even take care of above scheme: the Linux standard is to use $ as separator, and $6$ is the method ID for sha512, while $2a$ indicates you want to use blowfish. So you can even have multiple hashes in use in your database. md5 hashes are prefixed with $1$<salt>$ (unless you reinvented md5 hashing, then your hashes may be incompatible).
Seriously, reuse the existing crypt function. It is well checked by experts, extensible, and compatible across many applications.
I looked into this subject a while back and found the following link of great use:
Secure hash and salt for PHP passwords
I also use the following to create a random salt:
public static function getRandomString($length = 20) {
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$string = '';
for ($i = 0; $i < $length; $i++) {
$string .= substr($characters, (mt_rand() % strlen($characters)), 1);
}
return $string;
}

Creating salt with Portable PHP password hashing framework

I'd like to use Portable PHP password hashing framework to hash passwords. But I find that its demos don't use salt for hashing a password. But it use a dummy salt for checking password which I find it strange and I don't understand this idea at all,
$dummy_salt = '$2a$08$1234567890123456789012';
if (isset($dummy_salt) && strlen($hash) < 20)
$hash = $dummy_salt;
I wonder, if I want to use convention method which I can generate the unique salt and store it for each user in my database, how can I use Portable PHP password hashing framework to generate salts?
This is the function I use to hash passwords but I have been told that sha512 has the same issue as sha1, wise to trust the expert like Portable PHP password hashing framework,
function hash_sha512($phrase,&$salt = null)
{
//$pepper = '!##$%^&*()_+=-{}][;";/?<>.,';
if ($salt == '')
{
$salt = substr(hash('sha512',uniqid(rand(), true).PEPPER_KEY.microtime()), 0, SALT_LENGTH);
}
else
{
$salt = substr($salt, 0, SALT_LENGTH);
}
return hash('sha512',$salt.PEPPER_KEY.$phrase);
}
Let me know if you have any idea. Thanks.
From the phpass article linked to from that page:
Besides the actual hashing, phpass
transparently generates random salts
when a new password or passphrase is
hashed, and it encodes the hash type,
the salt, and the password stretching
iteration count into the "hash
encoding string" that it returns. When
phpass authenticates a password or
passphrase against a stored hash, it
similarly transparently extracts and
uses the hash type identifier, the
salt, and the iteration count out of
the "hash encoding string". Thus, you
do not need to bother with salting and
stretching on your own - phpass takes
care of these for you.
...so it's not surprising that the examples don't use salting! You may be over-seasoning your code.
The code sample with the dummy salt is an example of how to prevent a timing attack by making sure that whether a user exists or not, the validation of the user takes the same amount of time, effectively by doing a dummy authentication for non-existent users. It needs a dummy salt because if the user doesn't exist, it won't have a stored salt to use.

Categories