2

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.

Farflame
  • 421
  • 1
  • 5
  • 17
  • 1
    If you encrypt your passwords you're doing it wrong. You should be using: http://php.net/password – Mike Apr 15 '16 at 00:44
  • Yes, the words hash and encrypt are getting a bit intermingled. I've read so many different tutorials, I'm not sure which is the right way to go right now. – Farflame Apr 15 '16 at 00:46
  • 1
    Any tutorial that recommends encrypting passwords can immediately be discarded. As David's answer says, you store a hash of the password. – Mike Apr 15 '16 at 00:57
  • 2
    Not sure it's an actual duplicate because he's specifically asking for a clear and concise best practice in modern versions of PHP (that which was once complicated and inconsistent is now merely just `password_hash()` and `password_verify()`) – David Wyly Apr 15 '16 at 01:14
  • Yeah that's really the problem that I was having. There are so many out of date answers, it's quite hard to know what's good for today. Thankfully, it's been simplified and solved with password_hash() and password_verify(). – Farflame Apr 15 '16 at 01:31

4 Answers4

7

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.

Scott Arciszewski
  • 33,610
  • 16
  • 89
  • 206
5

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.

David Wyly
  • 1,671
  • 1
  • 11
  • 19
  • Cool, I understand that it's best to store the hash, just there seem to be so many methods. Am I safe to just research into password_hash() and password_verify() and that's basically all I need right now? – Farflame Apr 15 '16 at 01:02
  • 2
    Yep, there used to be a lot of different ways, and then newer versions of PHP addressed this with those two native functions. Nowadays that's all you really need to know! – David Wyly Apr 15 '16 at 01:03
  • 1
    Thanks, I've been reading tutorials back as far as 2009, so I guess I've over-complicated the issue in my head. So now it's as easy as, user registers new PW, hash it with password_hash(), save the result. User logs in with same PW, test that against the stored hash with password_verify()? That simple? And that's nice and secure? – Farflame Apr 15 '16 at 01:18
  • 1
    Yep, you got it! Very simple and secure. The only concern, going forward, is that password hashes become easier to crack as computing power increases over time. That's where the `password_​needs_​rehash()` comes in handy. So as your application ages, you may eventually need to rehash the password with something stronger if the situation arises. – David Wyly Apr 15 '16 at 01:24
  • Wow, the eventual answer is just a couple of simple commands, I've been messing around with all sorts of functions and examples. Oh well, guess I learned a bit along the way :) I believe this is future proof too? – Farflame Apr 15 '16 at 01:27
  • 1
    Glad to have helped! And as long as you rehash really old and weak hashes using `password_​needs_​rehash()`, you should be fairly safe going forward into the indefinite future. I say "fairly safe" because security is always an arms race ;) – David Wyly Apr 15 '16 at 01:34
  • Thanks, you've been really helpful. Sorry I tagged the other guy's answer as the accepted answer, but that feels a bit more complete should another person come along looking for this solution. Though you've been more helpful overall :) He suggests that it's future compatible, meaning that it should update into the future, keeping my hashes up to date etc? That'll be really cool if this is the last I need to think about security :) – Farflame Apr 15 '16 at 01:39
3

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.

IvanRF
  • 7,115
  • 5
  • 47
  • 71
Sammitch
  • 30,782
  • 7
  • 50
  • 77
0

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().

James Paterson
  • 2,652
  • 3
  • 27
  • 40