Skip to content

Instantly share code, notes, and snippets.

@nikic
Created September 12, 2012 15:04
Star You must be signed in to star a gist
Save nikic/3707231 to your computer and use it in GitHub Desktop.
The new Secure Password Hashing API in PHP 5.5

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.

@sparticvs
Copy link

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.

@sprice
Copy link

sprice commented Sep 12, 2012

How to safely store a password

tl;dr: use bcrypt

@sprice
Copy link

sprice commented Sep 12, 2012

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

@viccherubini
Copy link

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

@mloberg
Copy link

mloberg commented Sep 12, 2012

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

@nikic
Copy link
Author

nikic commented Sep 12, 2012

@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.

@realityking
Copy link

@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.

@tecknicaltom
Copy link

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.

@Francisc
Copy link

Awesome. Using bcrypt will be easier now.

@bigwhoop
Copy link

+1

@JAStanton
Copy link

+1 for bcrypt

@nikic
Copy link
Author

nikic commented Sep 12, 2012

@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 :)

@nikic
Copy link
Author

nikic commented Sep 12, 2012

@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.

@ChrisMcKee
Copy link

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.

@ChrisMcKee
Copy link

@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.

@squeed
Copy link

squeed commented Sep 12, 2012

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.

@dserodio
Copy link

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

@nikic
Copy link
Author

nikic commented Sep 12, 2012

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

@squeed
Copy link

squeed commented Sep 12, 2012

@nikic: So it does :-)

@mattvh
Copy link

mattvh commented Sep 12, 2012

@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...

@stouset
Copy link

stouset commented Sep 13, 2012

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.

@wyred
Copy link

wyred commented Sep 13, 2012

+1

@reconbot
Copy link

+1

@hinzundcode
Copy link

+1 ;)

Copy link

ghost commented Feb 13, 2013

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/

@hobnob
Copy link

hobnob commented Jun 20, 2013

👍

@panique
Copy link

panique commented May 20, 2014

@MRXXIV Thanks :)

@guischumacher
Copy link

hello, first of all, nice post! My question is: now that we have a specific function for password hash, the best method for verifying if the user is logged in some system is still by using sessions? or there's a better way on some new 5.5 functions? Thanks in advance!

@strapin3
Copy link

strapin3 commented Jan 5, 2016

is PHP 5.5 Password Hashing API available for free?

@peter279k
Copy link

Hi @strapin3. Yes, it's available for free and you can see this link to know more details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment