public
Last active

The new Secure Password Hashing API in PHP 5.5

  • Download Gist
password_hashing_api.md
Markdown

The new Secure Password Hashing API in PHP 5.5

The RFC for a new simple to use password hashing API has just been accepted for PHP 5.5. As the RFC itself is rather technical and most of the sample codes are something you should not use, I want to give a very quick overview of the new API:

Why do we need a new API?

Everybody knows that you should be hashing their passwords using bcrypt, but still a surprising number of developers uses insecure md5 or sha1 hashes (just look at the recent password leaks). One of the reasons for this is that the crypt() API is ridiculously hard to use and very prone to programming mistakes.

By adding a new, very simple to use API we hope to move more developers towards bcrypt.

How to hash passwords

Creating password hashes can't be any simpler than this:

$hash = password_hash($password, PASSWORD_DEFAULT);

This will create a password hash using the default algorithm (currently bcrypt), the default load factor (currently 10) and an automatically generated salt. The used algorithm and salt will also be part of the resulting hash, so you don't need to worry about them at all ;)

If you don't want to stick with the defaults (which might change in the future), you can also provide algorithm and load factor yourself:

$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);

Verifying passwords

Verifying passwords is just as easy:

<?php
// $password from user, $hash from database
if (password_verify($password, $hash)) {
    // password valid!
} else {
    // wrong password :(
}

Remember: The salt and algorithm are part of the hash, so you don't need to provide them separately.

Rehashing passwords

As time goes by you might want to change the password hashing algorithm or load factor, or PHP may change the defaults to be more secure. In this case new accounts should be created using the new options and existing passwords rehashed on login (you can do this only on login because you need the original password to do a rehash).

Doing this is also very simple:

<?php
function password_verify_with_rehash($password, $hash) {
    if (!password_verify($password, $hash)) {
        return false;
    }

    if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
        $hash = password_hash($password, PASSWORD_DEFAULT);

        // update hash in database
    }

    return true;
}

The above snippet will keep your hashes up to date with the PHP default. But once again you can also specify custom options, e.g. password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' => 12']).

Compatibility layer for older PHP versions

The new API will only be introduced in PHP 5.5, but you can already use a PHP implementation of the same API now! The compatibility implementation will automatically disable itself once you upgrade to 5.5.

This isn't any more secure...

Any more secure than what?

Can you specify a salt if you want to? I'm not sure how I feel about purely-invisible salts.

It is more secure than MD5 or SHA + salting since you are slowing down how fast hashes can be calculated/cracked. http://www.troyhunt.com/2012/06/our-password-hashing-has-no-clothes.html

You should just use: http://php.net/manual/en/function.hash-pbkdf2.php as that is the same thing and native and well, better because you can pick your algo.

ie: this new PHP api is doing it right :)

This is great! Glad it's finally in PHP with a nicer interface than crypt(). Question though, why isn't it an object? I know it's a few methods, but making it an object would make things a lot nicer (specifically, with testing, dependency injection).

@benwerd You can specify it in the options. https://wiki.php.net/rfc/password_hash (first code example).

@sparticvs: hash_pbkdf2 is also a new PHP 5.5 function (and just as native as the above API, so I don't know what you mean by that). Afaik we will explicitly discourage it's use for "normal" password hashing, as bcrypt simply is the better alternative. PBKDF2 was added in case you need NIST compliance or need it for some specific protocol that uses PBKDF2.

@nikic I've seen in mentioned in the docs, that bcrypt is better suited for password storage. Could you elaborate that? The API is of course far less nice but that's not a complete deal breaker.

This is great news. This is the interface that languages need to introduce to make sure that the secure method of password handling is easier than the insecure ways that people use now. If you're reading this and not understanding why this is better than the solution you designed, this is especially for you. Delete your code, use this.

Awesome. Using bcrypt will be easier now.

@leftnode: Here's a rather extensive explanation why a procedural API was chosen over an object oriented one: http://www.reddit.com/r/PHP/comments/zrprk/the_new_secure_password_hashing_api_in_php_55/c677yu2 :)

@realityking: See the table in this answer to get a rough estimate about how much better bcrypt is: http://stackoverflow.com/a/5531194/385378 I won't pretend to know the exact reason behind this, but my initial guess would be that bcrypt has higher memory requirements than PBKDF2. scrypt is even better in that respect.

A modern server can calculate the MD5 hash of about 330MB every second. If your users have passwords which are lowercase, alphanumeric, and 6 characters long, you can try every single possible password of that size in around 40 seconds (or a damn sight faster with a GPU cracker).
BCrypt has a built in salt that's auto generated and stored alongside the hash. SCrypt by comparison is quite similar in that its CPU intensive, the big difference with SCrypt is that its also memory intensive, reducing the speed at which one can crack a hash by quite a large factor.
There was a crap blog post, which thanks to the gods of seo, somehow ends up in every damn reply I see about BCrypt etal (www.unlimitednovelty.com/2012/03/dont-use-bcrypt.html) which makes wonderful claims about the NIST approved PBKDF2 and tries to make out that BCrypt and SCrypt are poorly researched; in reality the opposite is true.

At the end of the day its all about balance; SCrypt makes mince meat out of a basic apache server setup; as used by most php devs. BCrypt is 'enough' password security as is PBKDF2; but the password data is only one element that someone would go for if they dumped your database. Don't forget to ensure your servers patched and up-to-date and that your other sensitive data (user details / addresses etal) are properly encrypted.

@nikic BCrypt + PBKDF2 operate in memory, using a higher worker (in bcrypt) or number of rounds increases CPU usage; SCrypt increases both CPU and Memory usage reducing the risk from GPU based attacks. The downside of course is that setting these two high has an adverse affect on the performance of your site. It all comes down to balance; in Ruby theres a nice BCrypt.Benchmark method to return a "suitable" work load which is quite nice.

Watch out though: PHP's string comparison functions are very susceptible to timing attacks! Check out https://github.org/php/php-src/blob/master/Zend/zend_operators.c#L1977 (line 1977)

    retval = memcmp(s1, s2, MIN(len1, len2));

I suggest you use a string comparison function that is constant-time.

Ain't a bcrypt work factor of 10 too CPU-intensive for most "cheap" servers, which is what PHP generally runs on ?

@squeed: The password_verify implementation performs a constant-time comparison to avoid timing attacks ;) So no need to worry about that :)

@nikic: So it does :-)

@dserodio: I don't think so. It hashes quickly on my VPS (a two-node VM from VPS.net) and even if it takes 0.6-1 seconds for a a cheap shared host to do it, it's not like a hash needs to be computed terribly often. A slow loading time when they first log in is perfectly acceptably, and the host isn't going to kill the script unless it runs really long (like 30-60 seconds, then it usually times out on most shared plans). It's not like you're hitting bcrypt on every page load...

The responses to this so far are pretty terrifying. Here PHP is doing the absolutely right thing out of the box, and already users are discussing ways to circumvent it according to their own intuition.

@g1o5b: This isn't any more secureā€¦

Yes, it is. While one is certainly capable of using the existing APIs correctly, this uses cryptographic best practices by default. The old way makes it trivial to shoot yourself in the foot. This way makes it trivial to do things correctly.

@benwerd: Can you specify a salt if you want to? I'm not sure how I feel about purely-invisible salts.

The salt is prepended to the digest. There is no issue whatoever with doing this, and in all likelihood the average person will get salt generation wrong.

@sparticvs: You should just use: http://php.net/manual/en/function.hash-pbkdf2.php as that is the same thing and native and well, better because you can pick your algo.

No. PBKDF2 is a workaround to make fast message digest algorithms slower. BCrypt has this built-in as a core concept, and is intended specifically for this purpose. Picking your own algorithm is exactly why so many real-world password hashing schemes are broken: people are still writing new software that digests passwords with MD5. This does the correct thing out of the box.

@squeed: Watch out though: PHP's string comparison functions are very susceptible to timing attacks!

First, this is why there is a password_verify function: to take this responsibility out of your hands. Still, though, digests output from BCrypt can generally be compared with each other using standard string comparison functions safely, because an attacker has no practical way to input strings that hash to single-character differences.

I have a login / registration form in the works that uses this API. Thanks to Panique's original project, I was able to do this quicker.

http://projects.mrxxiv.com/php55login/

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.