Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP Mysql secure passwords [duplicate]

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.

like image 955
Farflame Avatar asked Jan 31 '26 21:01

Farflame


2 Answers

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:

  1. What you should do today (i.e. the standard response) and why.
  2. What you should do later this year.
  3. 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:

  1. Bcrypt truncates after 72 characters.
  2. 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.

like image 64
Scott Arciszewski Avatar answered Feb 02 '26 09:02

Scott Arciszewski


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.

like image 27
David Wyly Avatar answered Feb 02 '26 11:02

David Wyly



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!