Generating and storing salt in the database when using crypt() - php

What I used to do:
$salt = md5(time().rand(0,9999999).rand(0,100000));
$hashed_password = sha1($salt.$_REQUEST["newpassword"].sha1($salt));
$query = "update users set password=:hashed_password, salt=:salt where uid=:uid";
And then to check the password
if ($mysql_row["password"]==sha1($mysql_row["salt"].$_REQUEST["loginpassword"].sha1($mysql_row["salt"]))) loginsuccess = true;
Now I find out that this is an unsafe way to store passwords because sha1() is crap, so I decided to use crypt(). I'm not using password_hash() and the compatibility pack because I sometimes have to move a site to another server that has a different (sometimes older) PHP and I want the hashes/passwords generated on later PHP versions to still work on servers with the earlier PHP versions.
So crypt() says I can do something like
$salt = md5(time().rand(0,9999999).rand(0,100000));
$hashed_password = crypt($_REQUEST["newpassword"],$salt);
$query = "update users set password:hashed_password where uid=:uid"; // doesn't save salt
and then to check the password:
if (crypt($_REQUEST["loginpassword"], $mysql_row["password"]) == $mysql_row["password"]) $loginsuccess = true;
So my questions are:
Do I really not have to store the salt anywhere when using crypt()? Is crypt($something,$somethingelse)==$somethingelse always true when $something==$somethingelse ? (now I realize I could've just started with this question)
Am I right that hashes generated with the compatibility pack on newer PHP versions may not be the same as those generated on older PHP versions, so in effect making generated passwords incompatible between different versions of PHP? If so, why is it called a "compatibility" pack?
I realize I could test most of this, but when it comes to security I'd rather have an expert opinion.

No, it can vary from server to server; it depends on the available algorithms. password_hash allows you to specify the algorithm.
See 1. password_hash is compatible with crypt.
There is one more problem with your code:
Your salt generation is not random enough. rand is not very good. mt_rand is slightly better, openssl_random_pseudo_bytes is good, password_hash is recommended (but you know this).

Related

Are there any reasons not to use PHP's default password hashing library?

I'm a fairly new developer and I want to ensure that I'm making the right decisions with regards to security. According to my research, it appears I should use a salted bcrypt hashing algorithm for my passwords. According to PHP 7's documentation, the password_hash functionality does exactly this. I don't even have to figure out a way to make my own salt as salting is included in password_hash. This seems like a simple correct answer to password hashing for PHP 7. However, simple correct answers are often wrong. What are the pitfalls of PHP's default password hashing library?
Most answers about PHP password hashing conclude that I should use password_hash/password_verify. However, my question is closer to 'how should I use them?' or 'what should I be aware of when using them?'
Seems you are looking for confirmation, so yes the password_hash() function is definitely the way to go, you can't do any better with a normal PHP installation. This function is future proof and can change the algorithm should this become necessary, while keeping backwards compatibility with existing hashes.
What you should do is:
Use the function with the parameter PASSWORD_DEFAULT, this allows to switch the algorithm in future. Have a look at the example below.
Store the hash in a database field of type varchar(255), so future algorithms can store their hashes as well.
Make sure you are working with a PHP version 5.3.7 or higher, so the function produces hashes with the identifier $2y$ (and not $2a$).
Example:
// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($password, PASSWORD_DEFAULT);
// 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);

PHP Mysql secure passwords [duplicate]

This question already has answers here:
Secure hash and salt for PHP passwords
(14 answers)
Closed 6 years ago.
I know this is asked a lot, but right now (April 2016), what is the preferred method of storing passwords securely for my users? My data is not super-sensitive (it's just a game with no personal or financial details, but it would still be nice to have secure passwords).
I've read a large amount of tutorials and questions tonight, but it's still unclear. I just want a fairly simple to understand but fairly safe way to take a created password, store it in my database and then retrieve it. I've tried several methods, they all work fine but then later somebody says it's not safe or things have moved on since then. It would be nice to see a couple of clear functions to just encrypt and decrypt the passwords.
Edit - To clarify why the new answers are better than previous answers on stackoverflow. The language has been updated with password_hash() and password_verify() which hugely simplify and improve the situation. Previous answers gave manual solutions, saving salts, using different algorithms etc. None of that is needed now.
I know this is asked a lot, but right now (April 2016), what is the preferred method of storing passwords securely for my users? My data is not super-sensitive (it's just a game with no personal or financial details, but it would still be nice to have secure passwords).
I maintain the blog post that James Patterson linked to in his answer. Despite being a mere two months old, it's already due for a massive update, but that's being held back by the slow movement of several other languages. I can provide you with an immediate updated answer for PHP.
I'm going to answer in this format:
What you should do today (i.e. the standard response) and why.
What you should do later this year.
What you should do in the years to come.
How to Safely Store a Password in 2016
As the blog post mentioned in the current iteration of the PHP password storage section, you want bcrypt, facilitated through PHP's built-in password API. This API is straightforward, but there are a few gotchas you need to keep in mind:
Hashing a password with bcrypt
$hash = password_hash($userPassword, PASSWORD_DEFAULT);
Validating a password with bcrypt
if (password_verify($userPassword, $hash)) {
// Login successful.
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
// Recalculate a new password_hash() and overwrite the one we stored previously
}
}
Why bcrypt?
Bcrypt is the preferred algorithm right now for many reasons:
Great GPU resistance (which matters a lot in resisting password cracking)
Well-studied by experts for many years -- it's the conservative choice
In low memory configurations, it's safer than scrypt.
Why not bcrypt?
There are two known weaknesses with bcrypt that you should be aware of:
Bcrypt truncates after 72 characters.
Bcrypt truncates after \0 bytes.
Typically, people run into the second problem while trying to solve the first problem. A good stop-gap solution for right now is:
Today's Recommendation: Bcrypt-SHA-384 (with base64 encoding)
/**
* Bcrypt-SHA-384 Verification
*
* #ref http://stackoverflow.com/a/36638120/2224584
* #param string $plaintext
* #param string $Hash
* #return bool
*/
function bcrypt_sha384_verify(string $plaintext, string $hash): bool
{
$prehash = \base64_encode(
\hash('sha384', $plaintext, true)
);
return \password_verify($prehash, $hash);
}
/**
* Creates a Bcrypt-SHA-384 hash
*
* #ref http://stackoverflow.com/a/36638120/2224584
* #param string $plaintext
* #param int $cost
* #return string
*/
function bcrypt_sha384_hash(string $plaintext, int $cost = 12): string
{
$prehash = \base64_encode(
\hash('sha384', $plaintext, true)
);
return \password_hash(
$prehash,
PASSWORD_BCRYPT,
['cost' => $cost]
);
}
The specific reasoning was all covered in How to Safely Store Your Users' Passwords in 2016.
How to Safely Store a Password in the Immediate Future
Later this year, you might consider switching to Argon2, the winner of the Password Hashing Competition. (It's not mandatory; bcrypt is still quite good. But in the coming years Argon2 will be the recommended algorithm, barring any new attacks against it.)
You can, in fact, use it right now. You just need to install libsodium 1.0.9 and libsodium-php 1.0.3 (or newer).
Hashing a password with Argon2i in PHP using libsodium
$hash = \Sodium\crypto_pwhash_str(
$password,
\Sodium\CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
\Sodium\CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
Validating a password with Argon2i in PHP using libsodium
if (\Sodium\crypto_pwhash_str_verify($hash, $password)) {
// Login successful
}
If you're using Halite 2.0.0 or newer, the Password API uses Argon2i then encrypts the hashes using authenticated encryption. (This is preferable to "peppering".)
How to Safely Store a Password in the Long Term
I'm going to be proposing an RFC soon to add Argon2i as a possible algorithm to the password hashing API. If it gets accepted in time for PHP 7.1's feature freeze, we might expect to see Argon2i be the default as early as PHP 7.3 (which should release in 2018).
Thus, the long-term solution might simply be:
Hashing a password with Argon2i, in the future
$hash = password_hash($userPassword, PASSWORD_DEFAULT);
Validating a password with Argon2i, in the future
if (password_verify($userPassword, $hash)) {
// Login successful.
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
// Recalculate a new password_hash() and overwrite the one we stored previously
}
}
In Sum
There are things you can do to improve the security of your password hashes right now, but in the long run the password hashing API will come full circle and the lazy answer today will once again be the best practice answer tomorrow.
TL;DR: password_hash() and password_verify() will outlive bcrypt, so just use those unless you have a compelling reason to do more.
You don't store passwords, you store one-way hashes. NEVER store passwords, even if you use encryption!
The reason is that, if the database were ever compromised, you'd have passwords open and available for all to see. Even if they're encrypted, all you need to do is crack the key once and POW, every password unlocked. This is very, very bad.
A hash, on the other hand, is one-way encryption. A password is tested against the hash, and the response is either true or false (authorized or unauthorized). You're never actually storing the password directly, so if your data were to leak, you'd have to crack each password individually -- and with salted hashes and a good hashing algorithm, good luck with that.
Fortunately, recent versions of PHP greatly simplify the process. password_hash() and password_verify() take all the uncertainty out of it.
The triviality of your application should not factor into a decision like this as passwords are private user information and protecting that information is your responsibility. One of the most common things to do with a dump of a user database is to bruteforce the passwords and then use those to log into the email accounts of the users, as most people still use the same password for everything. Now the attacker can potentially access other much more damaging things like banking systems.
As well, there is the case that once people learn that you're not storing their information securely they may not touch your application, or any other application you ever develop, with a ten-foot pole.
As stated in much of the other commentary you want to be securely hashing passwords, which is a one-way process, and not encrypting them, as that implies that it is reversible.
PHP 5.5 implemented the password_hash() and password_verify() functions to make it much easier for developers to properly hash passwords for storage. There is also the password_compat library provided by one of the PHP devs that backports these functions as far back as 5.3.7. [Note: If you're using PHP older than this please drop everything and upgrade immediately]
The broad strokes of using them are simply:
$hash = password_hash($raw_password, \PASSWORD_DEFAULT);
// store the hash wherever, eg: database
and:
$stored_hash = ...; // retrieve from storage
if ( password_verify($raw_password, $stored_hash) ) {
echo 'yay!';
} else {
echo 'boo!';
}
and that's literally it. The library provides the current best-practice hashing algorithm, secure defaults, and is forward-compatible meaning that even if the default hashing algo changes your previous hashes will still work and your new hashes will use the new algo. There's even a password_needs_rehash() function that can tell you if you need to store a new hash.
TL;DR: password_hash() is dead-ass simple. Use it.
Here's the best article I've read on the subject: https://paragonie.com/blog/2016/02/how-safely-store-password-in-2016.
It shows you how to use the PHP password API, which will be the most future proof solution to hashing passwords. Also consider reading the docs for password_hash().

What will happen if they changed PASSWORD_DEFAULT in PHP Password library?

Consider this line of code by using PHP:
$password = password_hash($password, PASSWORD_DEFAULT);
What will happen if they changed the default password hashing algorithm? I mean I will be having hashed password inside the database. Then, from my own understanding, it will be impossible to check the password because the hashing algorithm will be totally changed.
What would happen is that newly-hashed passwords will be using the new algorithm - obviously.
However, you shouldn't be concerned about this, because the whole thing is designed with forward-compatibility in mind - your code won't be broken when the default algorithm changes, as long as you're using the password_*() functions correctly.
By correctly, I mean use password_verify().
password_verify() accepts a plain-text password and a hash, and it can easily determine what the used algorithm is by looking at the hash that you feed it. Therefore, it would also still be able to verify a password that has been hashed using an old algorithm - not just the previous one, but any algorithm that is supported.
In fact, the sole purpose of the PASSWORD_DEFAULT constant is that you can easily migrate older hashes to a new algorithm (once one is added). This happens the following way:
When a user logs in, you verify their password via password_verify() (any hashing algorithm that has a PASSWORD_<name> constant will work).
You call password_needs_rehash(), and if the password you just verified is using an older algorithm (or a lower 'cost' paramater) - it will return boolean TRUE.
If boolean TRUE was indeed returned, you can now replace the old hash with one that uses the new algorithm; you can do that during a login, because the user just gave you the password and you verified that it is correct.
In summary - it's a really, really well-designed API and it solves problems for you that you haven't even thought about. Don't worry about it.
Edit (noted in the comments):
It should be noted, however, that new algorithms will quite probly result in longer hash lengths, so if you're storing the passwords in a database - don't limit the field's length (i.e. use a varchar(255) field).
Just for clarification, I would like to add to the answer that PHP uses the following structure. Thus, the password_needs_rehash() and password_verify() functions will check the algorithm and cost and do their work to keep everything compatible and correct.
Source: http://php.net/manual/en/faq.passwords.php

Case insensitive usernames and password salts

I have a login system
where, for example, Joe can login with the username joe without any problems.
Currently, I am using a password encryption that uses the username as a salt. This is creating issues for logging in. For example,
SELECT * FROM `users` WHERE LOWER(`username`) = LOWER(`:username`)
$stmt->bindValue(':username', $_POST['user']);
This works fine. THe trouble involves the password:
SELECT * FROM `users` WHERE LOWER(`username`) = LOWER(`:username`)
AND `password` = :password
$stmt->bindValue(':username', $_POST['user']);
$stmt->bindValue(':password', encrypt($_POST['password'], $_POST['user'])); //encrypt(password, salt)
As you can see, the password encryption wouldn't check with the database because the user has logged in with joe instead of Joe
Is there a workaround to this, or should I use something else as a salt? If so, what should I use as a salt? This is my encrypt function:
function encrypt($password, $salt) {
return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($salt), $password, MCRYPT_MODE_CBC, md5(md5($salt))));
}
First off, what's the issue with using strtolower($_POST['user']) as the salt? It looks like it would work.
Now in general, salts should be unpredictable. Usernames aren't really that unpredictable, so although this won't be the end of the world your users' security will be better if you replace the username with a randomly generated string of modest length (it need not be crypto-strength random, just unpredictable).
But the biggest issue here is the use of MD5, which has been considered unsafe for some time. Security would improve if you switched to some other hash function; SHA-1 is not a particularly bad choice (it does have an undesirable property: it's fast to calculate), but the best fit for such applications are hash functions with a variable load factor such as bcrypt.
There are a couple issues here:
The case-sensitivity of your salt
Your method of password "encryption"
The first issue is what you've asked about, so let's take a look at that.
So your passwords are salted with the user name, and you expect to be able to use the username the user entered during login as the salt. This will work, if you settle on a normalization scheme for your salt. Instead of using it as-entered, convert it to lowercase or uppercase before using it in your function.
The other option is to perform two queries to the database: one to pull the username as it's stored, and the second to actually check the password. This isn't optimal, but it would really be the least of your troubles.
Is there a workaround to this, or should I use something else as a salt? If so, what should I use as a salt?
Since you asked, let's look at the second issue: your method of password "encryption."
Trying to invent your own password hashing/encryption scheme is never a good idea, for a number of reasons. Your best option is to use an already widely accepted password hashing scheme, such as bcrypt or some derivative of PBKDF2. These are great because they both include both salting and key stretching. They are designed to be slow (relatively, anyway) so brute-forcing these types of hashes is made very computationally expensive.
Using one of the aforementioned password hashing schemes will solve your first problem as well, as these schemes use built-in per-user salts. Depending on how you choose to implement these schemes, you will not (should not) need to provide your own salt. The top libraries will generate salts for you.
Try taking a look at these libraries for password hashing:
Openwall's PasswordHash class - Written for PHP4, but works on the latest versions as well. Supports bcrypt, but will fall back to its own scheme if bcrypt isn't supported (PHP < 5.3, mostly).
Anthony Ferrara's (ircmaxell's) PHP Password Library - Great library, but requires PHP 5.3+.
My own PHPassLib - Similar to Anthony's library, but implemented differently. The 3.x branch attempts to be more like Python's PassLib. Also requires PHP 5.3+.
Skip the SQL lower and use PHP's strtolower(). That way your usernames will be consistent across the board.
The problem you're having is one reason that username's shouldn't be used as the salt. A better way to hash passwords is with crypt(). You just need to generate a random salt to use with it. The string it returns contains the hash and the hashing algorithm so you can change the algorithm or difficulty if you want to without having to rewrite code. As long as your hash is 7-bit safe, the whole string will be, so you don't need to base64_encode() anything.

Alternative to bcrypt when saving passwords in PHP 5.2

I'm using bcrypt locally since xampp has PHP 5.3 but online my hosting account only has PHP 5.2. Is there a good alternative I can use which works for 5.2?
I think i should update and improve this answer, because i learned a lot about password hashing in the last years.
PHP version 5.5 will provide a convenient way to use BCrypt, for PHP version 5.3.7 and above there exist a compatibility pack. Please have a look at this answer.
For PHP versions before 5.3 it is recommended to use the phpass library, they support PHP back to version 3.
I'm using bcrypt ... Is there a good alternative I can use which works for 5.2?
See Openwall's PHP password hashing framework (PHPass). Its portable and hardened against a number of common attacks on user passwords. The guy who wrote the framework (SolarDesigner) is the same guy who wrote John The Ripper and sits as a judge in the Password Hashing Competition. So he knows a thing or two about attacks on passwords.
Check out the Mcrypt PHP extension. It's been around for a long time and has several different algorithms. bcrypt appears to just be a Blowfish wrapper. You could just as easily use PHP's crypt() function, and pass the appropriate salt to force the function to use Blowfish:
// crypt($plaintext, $salt);
// How you define $salt determines the encryption algorithm used
$hash = crypt('PASSWORD', '$2a$12$Some22CharacterSaltXXO');
echo $hash;
// Output would be $2a$12$Some22CharacterSaltXXO6NC3ydPIrirIzk1NdnTz0L/aCaHnlBa
The PHP manual page (linked above) has the explanation of why the password salt looks the way it does in my example above. The $2a$ tells PHP to use Blowfish, the 12$ is a cost modifier; a number between 04 (yes, it has to be 2 digits) and 31 that (I believe) effects the number of iterations the hashing mechanism uses. As you can see, the salt is included in the output from the call to crypt(), so when you need to check something against the hash you need to retrieve the hash first (from the file or database where it's stored) to pull out the salt.
It depends on where and what you store your passwords for.
For a online website (with users etc+) i would done this:
$hash = "jr38028(/#Fjg4i4g438h9)(#Hhhf3,..;uh#F)8";
$hashed = sha1($hash . $PASSWORD . $hash); // where $PASSWORD is the variable thats holding the password.
echo $hashed; // shows the hashed password.
Edited after doing something wrong. Forgot to hash inside function, also changed to sha1 instead of md5. And haters, love you too <3

Categories