How secure is encryption for passwords with crypt() in PHP? - php

I'm using crypt() encryption in PHP like this:
<?php
$password = sanitizing_func($_POST['password']);
$var = crypt($password, 'ab');
?>
How Secure is this?
Found a better solution here: openwall phpass
Thanks to Edward Thomson

It's less secure than if you just use crypt the way it was designed, with the password as the first argument and the salt as the second.
Now you're encrypting known plaintext using the user's password as the salt. If your system uses an MD5 crypt, then you've just limited the salt space to 12 characters, so you're truncating the space of users passwords to twelve characters. Worse still, my system requires me to use a prefix on the salt in order to specify my crypt, or else I get old school crypt, meaning you have two characters for the salt. So you've limited the possible length of a users password to two characters. Plus there's no point in even running crypt at this point, you might as well just store their two character password, since the salt is prefixed to the ciphertext so that subsequent calls to crypt can pass the same salt.
Also, you're limiting the character space of the password by using it in the salt, since the character space of the salt is limited to A-Z, a-z, 0-9, ".", "/". Even if you switch the arguments around from your code example, you're using the same salt data for every call. This means that every password has the same salt. So if your password table is exposed, it becomes less computationally expensive to crack using a dictionary attack.
In other words, swapping the password and salt arguments is a fatal mistake.
Finally, there's simply no reason to call crypt twice. If you want better encryption, use a better algorithm, don't call it more frequently. For example, if you're using a DES crypt, then it's still an ancient algorithm no matter how many times you call it. (I also seem to remember reading that multiple passes of an algorithm may inadvertently produce weakened ciphertext. But I don't have Schneier in front of me.)
What you want to do is the industry standard: use a strong crypt, pass the password as the first argument and random salt data in as the second argument and make sure that you're passing the maximum allowable number of bytes in for the salt.

Related

Why is PHP's hashing Algo not working right?

I use PHP's PASSWORD_DEFAULT as the hashing Algorithm; now, I noticed that, if I use it with a salt, only the first 8 chars are verified.
Here is a bit of code I wrote to test that:
<?php
$test_pw = "%ImAVery1234Secure!Password$";
$test_pw_to_be_hashed = "%ImAVery";
//
$salt = bin2hex(openssl_random_pseudo_bytes(32));
$password = $salt.$test_pw;
$password_hashed = password_hash($salt.$test_pw_to_be_hashed, PASSWORD_DEFAULT);
echo password_verify($password, $password_hashed);
?>
This returns 1 for me. If I remove one more chars from the test_pw_to_be_hashed value, it returns 0.
Is there any way to hash the whole password? Do I have to use another hashing algorithm?
If I have to use another hashing algorithm, is there any way to check it with PHP's password_verify method, or do I have to "re-hash" it and then just check both values like below?
if(password_hash($salt.$test_pw_to_be_hashed, OTHER_ALGO) == $db_password)
If I have to change the hashing algorithm, is there any way to re-hash the passwords used currently, or do I have to hash them again when I have the plain text password (when a user logs in again)?
The built-in function password_hash already generates a random hash for you. It returns a string containing the algorithm identifier, the salt and the cryptographic hash. This complete string is confusingly called "hash" itself in the PHP docs. In fact it is a composition containing the hash.
The function password_verify can identify the hash algorithm, the salt and the hash from the string generated by password_hash.
$hash_from_db = '$2y$10$1Ow3T9597X1e9W8dtVbKK.VAAo6Op6xIbglp.3amRCSVgLlTevhjS';
$test_pw = '%ImAVery1234Secure!Password$';
// this gives another hash each time due to a random salt
echo password_hash($test_pw, PASSWORD_DEFAULT), PHP_EOL;
// this verifies against a strored hash
echo password_verify($test_pw, $hash_from_db) ? 'correct' : 'incorrect';
A cryptographic oneway hash function is meant to be irreversible by design. Thus there is no way to rehash older hashes directly. You have to wait until the next login. Then you can check whether the hash in the database is compliant to the current security standard. Older algorithms will still work. First check as usual, whether the provided password is correct. Then recreate a new hash from the given plain password.
If you for some reason do want an additional own long salt string, you have to store that along with the hash as well. When verifying, you need to use that same salt with the user provided password in the same way as you have built the hash input before and pass the combined string to the password argument of the password_verify function.
Since some crypto algorithm might limit the length of the password input, it is a good idea to append further salt strings to the end of the password rather than prepending. Otherwise in the worst case the verification would always be true when the input is truncated to a shorter length than the length of a prepended salt.
As stated in the password_hash PHP docs
Caution
Using the PASSWORD_BCRYPT as the algorithm, will result in the password parameter being truncated to a maximum length of 72 bytes.
bcrypt is the current default algorithm used by password_hash. Thus prepending instead of appending a longer salt would counteract the security.
Though building a longer hash from the generated hash by an own implementation would be possible, cryptography is a complex sciency and custom implementations will most likely introduce more security holes (e.g. timing attacks) rather than increasing security. Use the options provided by PHP's implementations instead, e.g. adjust the cost of the calculation as documented in Predefined Constants. A calculation time of about 3-6 seconds is a fair compromise. This is usually done only once per session and the session is secured by a less secure session id. Consider to reask the password when accessing sensitive data like password change, critical personal information aso.
Keep in mind that even a strong password hash algorithm is considered as not secure enough. Consider to implement multi factor authentication, e.g. sending a TAN to a mail address or mobile phone or even better supporting a cryptographic hardware dongle. (This does not replace but extend password security!)

Understanding bcrypt salt as used by PHP password_hash

I have some trouble to understand how bcrypt uses the salt. I know what the salt is good for but I do not understand how the salt value is used exactly.
Problem 1: What is the correct salt length?
All sources I found say, that the salt has a length of 22 and that it is stored together with the algorithm, the costs and the actual hash value in the result string.
However, all implementations I found, use a salt with length 32. For example the FOSUserBundle used by Symfony used the following code to creat the salt:
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36)
Since a sha1 hash is 32 chars long, the generated salt also has a length of 32. Is this just a lazy implementation, skipping the code to trim the string to a length of 22 because this is done by bcrypt it self? Or are 32 chars necessary for some reason?
Problem 2: Is a salt length of 22 really correct?
In the following example it seems, that only the first 21 chars of the salt are saved in the result string. Passing these 21 chars as salt to password_hash will result in an error, but padding a 0 will work:
$s = 'password';
$salt = 'salt5678901234567890123456789012';
$salt_prefix = 'salt567890123456789010'; // first 21 chars of salt + 0
$h1 = password_hash($s, PASSWORD_BCRYPT, array('salt' => $salt));
$h2 = password_hash($s, PASSWORD_BCRYPT, array('salt' => $salt_prefix));
echo $h1 . PHP_EOL;
echo $h2 . PHP_EOL;
//Result
$2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu
$2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu
So, one needs to pass a salt with at least 22 chars to the algorithm but the 22nd chars seems to be useless. Is that correct? What is the sense of the 22nd char if it is not used at all?
Problem 3: Why not specify the salt manually?
In the PHP function password_hash using a manual hash is deprecated. Instead one is encouraged to let password_hash automatically, since would be safer.
I understand that using a "weak" salt or the same salt for all passwords can lead to risks due to rainbow tables. But why is it safer to use the auto-generated salt in general?
Why is it safer to use the auto-generated salt instead of manual salt, that is generated like this:
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36)
Problem 4: Is there any replacement for password_hash that still allows the usage of a custom salt?
Due to the implementation of project I am working on, I need to control the salt, that is used to generate a password hash. This can be changed in the future, but right know it is necessary to set the salt manually. Since this feature is deprecated in password_hash, I need some alternative to generate the hash. How to do this?
EDIT:
Just a short explanation why I need to control the salt: The password is not only used to login into the web app directly, but also to connect to the app via a REST API. The client requests the salt from the server and uses it (algorithm and costs are known) to hash the password, the user entered on the client side.
The hashed password then send back to the server for authentication. The purpose is to not send the password in plain text. To be able to generate the same hash on the client as on the server, the client needs to know which salt the server used.
I know that a hashed password does not add any real security, since the communication is already uses HTTPS only. However this the way the clients currently operate: Authentication is granted if the client send back the correct password hash.
I cannot change the server side without breaking thousands of existing clients. The clients can be updated sometime in the future, but this will be a long process.
Since this is done, I need to follow the old process, which means I need to be able to tell the clients the salt.
However I do not need to generate the salt myself. I am totally fine if PHP knows the most secure way how to do this. But I do need to get/extract the salt someway, to send it to the clients.
If I understood everything correctly, I could just let password_hash do the work and then extract the chars 7-29 from result string. Is this correct?
Problem 1: What is the correct salt length?
All sources I found say, that the salt has a length of 22 and that it is stored together with the algorithm, the costs and the actual hash value in the result string.
If all sources say it, there's shouldn't be a reason for you to question that ...
There's no universal salt size, it depends on the algorithm and for bcrypt, it is 22 ... although there's a catch. The necessary size is actually 16 bytes, but that is actually Base64-encoded (*).
When you Base64-encode 16 bytes of data, that will result in a 24-character length ASCII string, with the last 2 characters being irrelevant - that becomes 22 when you trim those 2 irrelevant ones.
Why are they irrelevant? Your question is broad enough already ... read the Wikipedia page for Base64.
* There are actually a few Base64 "dialects" and the one used by bcrypt is not quite the same as PHP's base64_encode().
However, all implementations I found, use a salt with length 32. For example the FOSUserBundle used by Symfony used the following code to creat the salt:
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36)
Since a sha1 hash is 32 chars long, the generated salt also has a length of 32. Is this just a lazy implementation, skipping the code to trim the string to a length of 22 because this is done by bcrypt it self? Or are 32 chars necessary for some reason?
That line will result in a 31-character string, not 32, but that's not actually relevant. If you provide a longer string, only the necessary part of it will be used - those last characters will be ignored.
You can test this yourself:
php > var_dump(password_hash('foo', PASSWORD_DEFAULT, ['salt' => str_repeat('a', 22).'b']));
string(60) "$2y$10$aaaaaaaaaaaaaaaaaaaaaO8Q0BjhyjLkn5wwHyGGWhEnrex6ji3Qm"
php > var_dump(password_hash('foo', PASSWORD_DEFAULT, ['salt' => str_repeat('a', 22).'c']));
string(60) "$2y$10$aaaaaaaaaaaaaaaaaaaaaO8Q0BjhyjLkn5wwHyGGWhEnrex6ji3Qm"
php > var_dump(password_hash('foo', PASSWORD_DEFAULT, ['salt' => str_repeat('a', 22).'d']));
string(60) "$2y$10$aaaaaaaaaaaaaaaaaaaaaO8Q0BjhyjLkn5wwHyGGWhEnrex6ji3Qm"
(if the extra characters were used, the resulting hashes would differ)
I'm not familiar with that FOSUserBundle, but yes - it does look like it's just doing something lazy, and incorrect.
Problem 2: Is a salt length of 22 really correct?
In the following example it seems, that only the first 21 chars of the salt are saved in the result string. Passing these 21 chars as salt to password_hash will result in an error, but padding a 0 will work:
$s = 'password';
$salt = 'salt5678901234567890123456789012';
$salt_prefix = 'salt567890123456789010'; // first 21 chars of salt + 0
$h1 = password_hash($s, PASSWORD_BCRYPT, array('salt' => $salt));
$h2 = password_hash($s, PASSWORD_BCRYPT, array('salt' => $salt_prefix));
echo $h1 . PHP_EOL;
echo $h2 . PHP_EOL;
//Result
$2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu
$2y$10$salt56789012345678901uTWNlUnhu5K/xBrtKYTo7oDy8zMr/csu
So, one needs to pass a salt with at least 22 chars to the algorithm but the 22nd chars seems to be useless. Is that correct? What is the sense of the 22nd char if it is not used at all?
It's not really irrelevant ... pad it with e.g. an 'A' and you'll see a different result.
I can't explain this properly to be honest, but it is again caused by how Base64 works and because in the resulting hash, you actually see something similar to this (pseudo-code):
base64_encode( base64_decode($salt) . $actualHashInBinary )
That is, the (supposedly) Base64-encoded salt is first de-coded to raw binary, used to create the actual hash (again in raw binary), the two are concatenated and then that whole thing is Base64-encoded.
Since the input salt is actually the 22 relevant out of a 24-size full length, we actually have an incomplete block at the end, which is completed (filled?) by the beginning of the raw hash ...
It is a different thing to concatenate 2 separate Base64-encoded values, and to concatenate the raw values before Base64-encoding them.
Problem 3: Why not specify the salt manually?
In the PHP function password_hash using a manual hash is deprecated. Instead one is encouraged to let password_hash automatically, since would be saver.
I understand that using a "weak" salt or the same salt for all passwords can lead to risks due to rainbow tables. But why is it saver to use the auto-generated salt in general?
Simply put - the salt needs to be cryptographically secure (i.e. unpredictable), and PHP already knows how to do that, while chances are (overwhelmingly) that you don't.
Unless you have an actual hardware CSPRNG (that PHP isn't already configured to use), the best thing you can do is to leave PHP to automatically generate the salt anyway.
Yet, here we are, you obviously wanting to do the opposite (for whatever reason) and making it less secure in the process - a lot of people do that.
This is why the salt option is deprecated - to protect you from yourself. :)
Why is it saver to use the auto-generated salt instead of manual salt, that is generated like this:
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36)
As I said, the salt needs to be unpredictable. In this specific example - none of the functions used are unpredictable, even mt_rand().
Yes, mt_rand() is not actually random, despite what its name implies.
Problem 4: Is there any replacement for password_hash that still allows the usage of a custom salt?
Due to the implementation of project I am working on, I need to control the salt, that is used to generate a password hash. This can be changed in the future, but right know it is necessary to set the salt manually. Since this feature is deprecated in password_hash, I need some alternative to generate the hash. How to do this?
You don't.
There's absolutely zero reason for your project to dictate how the password_hash() salt is generated. I don't know why you think it is necessary, but it 100% isn't - it would make no sense.
Though, ultimately - this is why deprecations are put in place before something is removed. Now you know the salt option will be removed in the future, and you have plenty of time to refactor your application.
Use it wisely, don't try to replicate deprecated functionality. You should be working in the opposite direction - ask how to separate the two without breaking your application.
You can use crypt with blowfish. It still acccepts custom salt in 2023. Not recommended to use the same salt for password, but for identifiers e.g. email addresses it is better than nothing or a checksum algorithm.

Precision loss in PHP's Crypt()? Same salt, different passwords = same encrypted result

I am using Crypt() in PHP to encrypt passwords.
Let's say salt is "bg",
Password is: "gg456456gg"
Encrypted result gives: "bgvQk9C2Pv27o"
But if I use password: "gg456456" - without two last characters, it gives same result.
Because of this, users are able to login without typing 100% exact password.
What's happening? I mean gg456456 and gg456456gg are two different passwords, why is encrypted result same?
Php.net on function crypt()
The standard DES-based crypt() returns the salt as the first two
characters of the output. It also only uses the first eight characters
of str, so longer strings that start with the same eight characters
will generate the same result (when the same salt is used).
So use a different encryption method.
Such as blowfish or sha-512. These will accept much longer strings
E.g. SHA-512:
$encpassword = crypt($password,"$6$".$salt);
Used the method above (and same salt):
gg456456 -> $6$631080661$L2o7HNKfYrqB4H19vYe7fRWWLenQj2EcWqriNG9rX6ki1QKO2YytkylrYmZ8mhIr6XE19Ms4RW2of5Z/dsYRA/
gg456456gg -> $6$631080661$maGxQ2d7ZIPIdXDFN1sJJsIjTFEwD9dL/uljSXdKXeJU4E5miCzh1ZCao57sGDm9PrDhdPYPLGUvoy0HzTfqI.
Use a good random-number generator for your salt and voila you have a well encrypted password
The original crypt function on Unix systems only uses the first 8 characters of the password. Eventually we decided that was insecure and have switched to more secure password hashes.
The PHP crypt function selects the algorithm to use based on the salt you supply, and a two character alphanumeric salt like you used triggers that original crypt algorithm.
See http://php.net/manual/en/function.crypt.php for the list of algorithms and respective salts.

Is it secure for the string and salt to be the same with crypt() in a password?

As in, say someone gets access to the hash "PafgokWMoHSZE". Because this has been obtained through crypt(Password1,Password1) would it be any easier/harder to reverse engineer than any other salt?
It's less secure, don't do it. The problem is that the salt is prefixed onto the hash - that Pa at the start is the first two characters from Password1. You've reduced an 9 character password to the strength of a 7 character password.
PHP's crypt() will generate a random salt for you automatically. Unless you need a specific algorithm it's best to leave it be.
Edit: It just occurred to me that this also gives the user control of the hash algorithm, which is almost certainly not what you want. An invalid salt (i.e. invalid characters in the password) will also cause crypt() to fail.
I would say it is not very safe simply because anyone can try the password itself as a salt.
A cracker can easily make it a rule to always try the password itself as a salt. Or even not systematically but only on a test batch. If your salt-encrypted password are leaked he can try that with a first run and then discover that the password is the salt very easily, then apply the same recipe for the rest of the list.
More explanation
If your password is "flower", any dictionary attack will try the word "flower". The cracker will try and lookup the hashes of "flower" in md5, SHA1, etc. If it doesn't work then he will assume the hash has been salted. That makes things much harder. Unless he tries to use the password itself as a salt.
Read this and be scared: http://arstechnica.com/security/2013/10/how-the-bible-and-youtube-are-fueling-the-next-frontier-of-password-cracking/

Switching from md5() to crypt()

So far I have been using md5 to hash passwords on my site, no salt.
Now I am building an application that will have to be more secure and I'm reading md5 can be easily brute-force attacked.
So I want to use crypt() to hash the passwords.
What I have not fully understood is:
Do I have to provide a salt or is the built-in generated one ok?
How many times (if more than one) should I iterate the crypt function to be safe?
With md5, no matter the length of the input string, the hash was 32-digit. Does crypt return a standard length of hashes too?
You need to provide a salt, if you want to specify encryption other than DES. Otherwise, you're good with the default salt.
You don't iterate the crypt function yourself, this is done internally with algorithms where it makes sense. Number of iterations is specified via the salt.
Yes, the hash length of a given hash algorithm is standard; different hash algorithms have different hash lengths, however.
crypt can use different hash algorytms. With md5 it returns 128 bit integer (with 32 chars hex representation). Using crypt with a salt once is safe enought. It's recommended the salt to be provided by the application
An optional salt string to base the hashing on. If not provided, the
behaviour is defined by the algorithm implementation and can lead to
unexpected results.

Categories