Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
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.

@g105b

This comment has been minimized.

Show comment Hide comment
@g105b

g105b Sep 12, 2012

This isn't any more secure...

g105b commented Sep 12, 2012

This isn't any more secure...

@pavellishin

This comment has been minimized.

Show comment Hide comment
@pavellishin

pavellishin Sep 12, 2012

Any more secure than what?

Any more secure than what?

@benwerd

This comment has been minimized.

Show comment Hide comment
@benwerd

benwerd Sep 12, 2012

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

benwerd commented Sep 12, 2012

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

@jbraithwaite

This comment has been minimized.

Show comment Hide comment
@jbraithwaite

jbraithwaite Sep 12, 2012

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

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

@sparticvs

This comment has been minimized.

Show comment Hide comment
@sparticvs

sparticvs Sep 12, 2012

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.

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

This comment has been minimized.

Show comment Hide comment
@sprice

sprice Sep 12, 2012

How to safely store a password

tl;dr: use bcrypt

sprice commented Sep 12, 2012

How to safely store a password

tl;dr: use bcrypt

@sprice

This comment has been minimized.

Show comment Hide comment
@sprice

sprice Sep 12, 2012

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

sprice commented Sep 12, 2012

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

@viccherubini

This comment has been minimized.

Show comment Hide comment
@viccherubini

viccherubini Sep 12, 2012

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

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

This comment has been minimized.

Show comment Hide comment
@mloberg

mloberg Sep 12, 2012

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

mloberg commented Sep 12, 2012

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

@nikic

This comment has been minimized.

Show comment Hide comment
@nikic

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

Owner

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

This comment has been minimized.

Show comment Hide comment
@realityking

realityking Sep 12, 2012

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

@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

This comment has been minimized.

Show comment Hide comment
@tecknicaltom

tecknicaltom Sep 12, 2012

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.

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

This comment has been minimized.

Show comment Hide comment
@Francisc

Francisc Sep 12, 2012

Awesome. Using bcrypt will be easier now.

Awesome. Using bcrypt will be easier now.

@bigwhoop

This comment has been minimized.

Show comment Hide comment
@bigwhoop

bigwhoop Sep 12, 2012

+1

+1

@JAStanton

This comment has been minimized.

Show comment Hide comment
@JAStanton

JAStanton Sep 12, 2012

+1 for bcrypt

+1 for bcrypt

@nikic

This comment has been minimized.

Show comment Hide comment
@nikic

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

Owner

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

This comment has been minimized.

Show comment Hide comment
@nikic

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

Owner

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

This comment has been minimized.

Show comment Hide comment
@ChrisMcKee

ChrisMcKee Sep 12, 2012

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.

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

This comment has been minimized.

Show comment Hide comment
@ChrisMcKee

ChrisMcKee Sep 12, 2012

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

@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

This comment has been minimized.

Show comment Hide comment
@squeed

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

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

This comment has been minimized.

Show comment Hide comment
@dserodio

dserodio Sep 12, 2012

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

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

@nikic

This comment has been minimized.

Show comment Hide comment
@nikic

nikic Sep 12, 2012

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

Owner

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

This comment has been minimized.

Show comment Hide comment
@squeed

squeed Sep 12, 2012

@nikic: So it does :-)

squeed commented Sep 12, 2012

@nikic: So it does :-)

@mattvh

This comment has been minimized.

Show comment Hide comment
@mattvh

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

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

This comment has been minimized.

Show comment Hide comment
@stouset

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

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

This comment has been minimized.

Show comment Hide comment
@wyred

wyred Sep 13, 2012

+1

wyred commented Sep 13, 2012

+1

@reconbot

This comment has been minimized.

Show comment Hide comment
@reconbot

reconbot Sep 13, 2012

+1

+1

@hinzundcode

This comment has been minimized.

Show comment Hide comment
@hinzundcode

hinzundcode Sep 13, 2012

+1 ;)

+1 ;)

@MrXXIV

This comment has been minimized.

Show comment Hide comment
@MrXXIV

MrXXIV 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/

MrXXIV 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

This comment has been minimized.

Show comment Hide comment
@hobnob

hobnob Jun 20, 2013

👍

hobnob commented Jun 20, 2013

👍

@panique

This comment has been minimized.

Show comment Hide comment
@panique

panique May 20, 2014

@MrXXIV Thanks :)

panique commented May 20, 2014

@MrXXIV Thanks :)

@guischumacher

This comment has been minimized.

Show comment Hide comment
@guischumacher

guischumacher Jul 14, 2014

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!

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

This comment has been minimized.

Show comment Hide comment
@strapin3

strapin3 Jan 5, 2016

is PHP 5.5 Password Hashing API available for free?

strapin3 commented Jan 5, 2016

is PHP 5.5 Password Hashing API available for free?

@peter279k

This comment has been minimized.

Show comment Hide comment
@peter279k

peter279k Jan 3, 2017

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

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

@kingwash

This comment has been minimized.

Show comment Hide comment
@kingwash

kingwash Jan 11, 2017

Nice solutions. I was having trouble with password encryption and how to change password. Thanks a lot!

Nice solutions. I was having trouble with password encryption and how to change password. Thanks a lot!

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