
The new Secure Password Hashing API in PHP 5.5 - nikic
https://gist.github.com/3707231
======
tptacek
Defaults to bcrypt with a cost factor of 10. A totally sane default. The
interface is clear, simple, and easy to audit for. This is a definite step
forward.

~~~
16s
Auditing bcrypt (OpenBSD Blowfish) hashed passwords with JtR is much less
satisfying than auditing md5 hashed passwords ;) Joking aside, I agree that
this is a good move in the right direction.

~~~
tptacek
Yeah, I'm just saying, the new standard password hash has a simple function
signature; it's now easy to take a new PHP codebase and quickly check it to
see if they're doing something wacky with passwords.

------
16s
During the Defcon password cracking contest this year, there were 5,150 bcrypt
hashes. In 48 hours, only 76 of them were cracked. In contrast, there were
8,544 plain sha1 hashes, 4,111 of those were cracked. You can see the full
list of hash types and how many were cracked here:

<http://contest-2012.korelogic.com/stats.html>

This is probably the premier password cracking contest in the world. I placed
7th overall and was first to crack most of the TrueCrypt volumes and I despise
attempting to crack bcrypt hashes (most sane people do). You really have to
try and crack them for yourself to fully appreciate how difficult and slow
they are to attack.

~~~
JakeSc
Were most people using rainbow tables or brute forcing do crack the hashes?

~~~
baudehlo
The point of bcrypt is you can't use a rainbow table.

~~~
tptacek
No modern hash function, including "salted SHA" _and Unix crypt()_ is
vulnerable to "rainbow tables". Rainbow tables work exclusively against the
dumbest possible hashing schemes.

Bcrypt isn't a defense against "rainbow tables"; the hash function bcrypt
supplanted (PHK's MD5 crypt) was also not "rainbow-table-able".

~~~
ZoFreX
Correct me if I'm wrong but couldn't you construct rainbow tables for any hash
function which takes a single input (including salted SHA, if the salt is the
same for all passwords)?

Of course, you'd need a seriously large set of passwords on your hands for it
to be worth the effort, but it could be done right?

(Possibly worth mentioning, possibly not, that if your salts are _extremely_
weak then the combined hash might show up in regular rainbow tables, whether
your salts are unique or not - it seems unlikely this would ever happen in
practice though)

~~~
danielweber
The point of rainbow tables is that you can pre-calculate them. I wrote some
myself for fun, and it does take a while to build stuff up.

Even if someone was dumb enough to use the same salt everywhere, once you know
that you would just start brute-forcing, not building a rainbow table for the
joy of searching it. (Unless you are into that like me.)

~~~
astrodust
It's a fun academic exercise, but Google seems to be a better rainbow table
than anyone can construct on their own. Search for hashes. The results are
frightening.

~~~
ZoFreX
That's a lookup table, not a rainbow table, strictly speaking.

------
RossM
For clarity, the current and new ways to use bcrypt in PHP:

    
    
        // pre-5.5
        $salt = substr(strtr(base64_encode(openssl_random_pseudo_bytes(16)), ['+' => '.']), 0, 22);
        $hash = crypt($password, '$2a$10$'.$salt);
    
        $match = crypt($password, $hash) == $hash;
    
        // post-5.5
        $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 10]);
        $match = password_verify($password, $hash);
    

Brilliant effort and should really help to ensure password security on the
web.

~~~
nikic
Just to clarify a bit more: Usually you need more (a lot more) code than this
on pre 5.5. In particular you can't normally assume that
`openssl_random_pseudo_bytes` is available. Instead you'll go through various
entropy sources (typically `mcrypt_create_iv`, `/dev/urandom`, maybe COM, with
fallback to `mt_rand`).

People often get the salt generation wrong, e.g. by just using a substring of
`md5(mt_rand())`, which is obviously wrong (in several respects) :/

~~~
AgentConundrum
I know this is a dumb question, but I haven't found a great answer to it yet:
why does it matter how the salt is generated, so long as its done on a per-
user basis?

If the salt is allowed to be "less than secret" (by which I mean it can be
stored in plain-text, not that it should be published on your website), then
what does it matter if it's "pretty random" versus "cryptographically random"?

What's wrong with something like this:

    
    
        $salt_length = 22;
        $cost_factor = 10;
        
        $lower   = range('a', 'z');
        $upper   = range('A', 'Z');
        $numeric = range('0', '9');
        $special = array('/', '.');
        
        $salt_chars = array_merge(
                        $lower, 
                        $upper, 
                        $numeric, 
                        $special
                      );
        $char_count = count($salt_chars);
        
        $salt = '';
        for ($i = 0; $i < $salt_length; $i++) {
          $salt += $salt_chars[mt_rand(0, $char_count -1)];
        }
        
        $hash = crypt(
                  $password, 
                  '$2a$' . $cost_factor . '$' . $salt
                );

~~~
ircmaxell
I did a breakdown on a similar snippet here:
[http://www.reddit.com/r/PHP/comments/zrprk/the_new_secure_pa...](http://www.reddit.com/r/PHP/comments/zrprk/the_new_secure_password_hashing_api_in_php_55/c67cw8z)

But for this case, the salt generation is much better (assuming that `mt_rand`
is a good enough source of entropy, which may or may not be the case).

The rest definitely applies though.

In short, you're using the wrong algorithm ($2y$ is the better one, the one
you're using has a known bug). You're not checking for errors from `crypt()`
prior to storing the hash. So you can wind up significantly messing up your
database and potentially leaving it in a worse state than if you just used
`md5($password)`... And the minor note about timing attacks...

Not to mention that you currently have an issue in your code (it needs to be
.= for a string, not +=)...

~~~
AgentConundrum
> But for this case, the salt generation is much better

This is the only question I was asking, and you haven't really addressed it in
any detail. The reddit comment you linked was replying to some obviously bad
code. I mean, limiting your salt to use only 16 possible characters? Really?

> _you're using the wrong algorithm ($2y$ is the better one, the one you're
> using has a known bug)_

I didn't know about that bug until just after writing my last comment (don't
worry, I don't do this for a living). I just used `2a` because that's a) the
example I see most often, and b) that's what was used in this HN comment
thread.

The security fix notice[1] linked from the manual page for crypt() mentions
that `2a`, on systems where `2y` is available, has countermeasures to try to
combat the vulnerability for newly generated hashes, and even says "if the app
prefers security and correctness over backwards compatibility, no action is
needed - just upgrade to new PHP and use its new behavior (with $2a$)" which
doesn't make it sound like it's a huge issue to use `2a` on newer installs,
just that you should prefer `2y` where possible.

That said, I'll make a note to use the new one since it _is_ superior. I do
find your comment that "if you're on too old of a PHP version to use that
(5.3.7 IIRC), then don't even talk about security..." to be needlessly
flippant. You don't even bother to offer an alternative to the poor bastards
that are stuck on older versions.

> _You're not checking for errors from `crypt()` prior to storing the hash._

It's example code, not production code. Maybe I should have made that clearer,
but I thought it would be pretty obvious.

> _And the minor note about timing attacks..._

What's the timing attack on my (non-production, air code)? Your comment on
timing in the reddit comment was about verification, which my code doesn't
mention.

> _Not to mention that you currently have an issue in your code (it needs to
> be .= for a string, not +=)..._

That's just a stupid typo/brain fart. I didn't actually run this; it's just
"air code".

\---

Can you elaborate more on the salt generation specifically, since that's all I
was really trying to ask here? Is the point of using a more cryptographically
secure RNG just to make it more likely that each new salt will be unique? How
important is absolute uniqueness?

If you could also elaborate on the problems with mt_rand() while you're at it,
I'd appreciate it. The only thing the manual mentions, as big a problem as it
may be on its own, is that it prefers even numbers on 64-bit systems in
certain configurations. Is there more to it than that?

I'm not a PHP pro, as should be clear by now, so I appreciate any information
you can pass along. I'm just trying to learn.

[1] <http://www.php.net/security/crypt_blowfish.php>

~~~
ircmaxell
> This is the only question I was asking, and you haven't really addressed it
> in any detail.

I thought you meant the function in its entirety.

So, to your specific point, it's not _bad_. That doesn't mean it can't be
improved upon.

For example, `mt_rand()` is susceptible to certain types of seed poisoning
attacks. That's because the state that it uses is process specific. So when
running PHP in a case similar to what happens with mod_php, that state is
shared among all php instances (just like with APC). What that means is that
the security and randomness of your usage depends on everyone else's usage. So
if someone calls `mt_srand()` in one app over and over with the same value,
your randomness can be thrown out of the window.

Now, that's a very significant edge case with very limited attack potential.
However, when it comes to security if there's a better way, why not use it.
And in this case, there is (/dev/urandom). Just read from that source (via
fopen, via mcrypt_create_iv, via openssl_random_pseudo_bytes, etc).

I'd much rather edge on the safer side as long as there are not significant
downsides...

As far as 2a vs 2y, I would stick with 2y unless you have a very good reason
for sticking with 2a.

As far as the error checking, I thought it was worth mentioning, since it
seems that $hash = crypt(...) is all you need, when in reality it isn't. Which
goes to further my point that crypt() is too difficult to use out of the
box...

> That's just a stupid typo/brain fart.

I realize that. I was just pointing it out.

> That said, I'll make a note to use the new one since it is superior. I do
> find your comment that "if you're on too old of a PHP version to use that
> (5.3.7 IIRC), then don't even talk about security..." to be needlessly
> flippant. You don't even bother to offer an alternative to the poor bastards
> that are stuck on older versions.

Correct. Because older versions have fairly significant vulnerabilities
associated with them. Two major DOS vulnerabilities come to mind. Is the
comment flippant? Perhaps. Does that make it wrong? No...

And as far as "offer an alternative to the poor bastards that are stuck on
older versions", there are plenty of those. PHPass supports PHP all the way
back to like 4.2... If you need a password hashing algorithm for an
unsupported version (or 5.3.x < 5.3.7), just use that.

Which actually brings me to the entire point (I don't need to tell you, just
making the point again). Just use a library for this. It may seem easy to just
do it yourself, but there's a lot to it. Just use a library and be done with
it. There's no reason to re-implement it every time...

Hope that helps...

~~~
AgentConundrum
Thanks, that helps a lot.

I apologize if I got a bit crass in my earlier comment. I think the Wil
Wheaton's Guide To Depression post has me a bit sensitive today. I probably
took the worst view of your comments and got annoyed over my own warped
perception.

I probably should just use a library for this, but I've been in a pretty
"reinvent the wheel to learn about wheels" mode with the thing that I'm
building (the latest in a series of projects which continue to elude actual
completion). I even started writing a framework a while back, before switching
to CodeIgniter since it's widely used and easy (before switching back to
writing a framework after getting annoyed fighting CI... kidding).

Since there's likely only ever going to be a single user for this thing, I
doubt the password implementation actually matters much, but it's certainly
going to be a debate for a day or two.

Thanks again.

------
DigitalSea
This is great news. One of the things that PHP has been lacking for some time
is easy to use secure password hashing. You'll still have instances where
developers are either too lazy, forget or don't learn these new methods and
the same problems will occur.

It's great to see the PHP team thinking ahead, and I completely understand and
agree with why they chose procedural over an object oriented approach to
implementing the new functionality: ease of use.

Next step I hope is some kind of native support for web sockets, I'm tired of
using third party libraries that don't implement a web socket server
correctly. That would be an amazing feature.

~~~
astrodust
As much as the PHP team is thinking ahead and working hard, there's a ton of
inertia in the PHP community that will take another decade to overcome.

The one thing that would help the PHP community immensely is nuking w3schools
from orbit. It's like a museum of bad ideas that somehow is the first place
people end up when looking to learn PHP.

------
Kudos
A standard API with sane defaults is an excellent idea for a language with so
many developers of varying competencies.

~~~
romaniv
Considering that this will make password-related code much shorter and more
readable, I think it's a good idea regardless of what level of competence you
expect from the developers.

~~~
underdown
I'll take "more secure" over short and readable any day.

~~~
Kudos
In some cases short and readable ends up as "more secure" by virtue of being
implemented correctly.

~~~
ErrantX
Or in this case; short and readable _is_ more secure because attack vectors,
like generation of salts, are wrapped inside the new API.

------
nthitz
So you are supposed to md5 the results of the password_hash() function before
storing them in a database right?

~~~
mikle
Don't forget double rot13.

~~~
16s
For those who may not get this joke:

# One rot13

$ echo test | wm --rot13 --words stdin

grfg

# Double rot13

$ echo test | wm --rot13 --words stdin | wm --rot13 --words stdin

test

------
acabal
Very nice. I'm surprised, though, that they didn't go the OOP route, since
they're transitioning a lot of other core procedural functions to objects.
Even if it would be a super-simple class interface (almost to the point of not
being strictly necessary), it would at least be consistent with the general
drift of next-gen PHP.

~~~
ircmaxell
I wanted to go down the OOP route initially. Then I decided against it for a
number of reasons. Here's a breakdown of that reasoning...
[http://www.reddit.com/r/PHP/comments/zrprk/the_new_secure_pa...](http://www.reddit.com/r/PHP/comments/zrprk/the_new_secure_password_hashing_api_in_php_55/c677yu2)

Additionally, the general drift hasn't been towards OOP within PHP. Some OOP
libraries are being added, but the core remains largely procedural (and
doesn't appear to be shifting significantly yet). It's taking the (very
logical IMHO) route of "Does this make sense to be OOP". If the answer is yes,
then it goes in as a class structure. If not, it goes in procedurally.

I did not feel it made sense to make this a class, and as such I did not...

------
leftnode
This is a great step forward. I wish it was in an object rather than globally
scoped methods, but it's nice to have.

Also, in case you didn't know, you can use bcrypt with PHP5.3 and the crypt()
function. It's not as user friendly as this, but you can do it.

~~~
ircmaxell
Here's an explanation of my rationale for not making it an object instead of a
function:
[http://www.reddit.com/r/PHP/comments/zrprk/the_new_secure_pa...](http://www.reddit.com/r/PHP/comments/zrprk/the_new_secure_password_hashing_api_in_php_55/c677yu2)

Here's the last paragraph (in case it's TLDR, or you don't want to click
through):

> So in short (or not), I just felt that there's room for this API and things
> like PasswordLib to live side by side. And I will continue to maintain that
> project in the long run. But for the generic use-case, I felt that an OOP
> API was too much risk for not enough gain for a core implementation. With
> that said, if you can come up with a clean API, I'd be all ears and willing
> to consider implementing it. But for now, this is the better alternative
> IMHO...

------
onethumb
Seems bizarre to me that hash_pbkdf2() has also been accepted, was authored by
the same author, and provides for "better"* hashing than bcrypt(), and yet
isn't the PASSWORD_DEFAULT or, indeed, even an option for the secure password
hashing algorithm. wtf?

* In the crypto community, "better" usually refers to how long an algorithm has been around, how well reviewed & used it is, and how bug-free it's been. Looking at all of these, PBKDF2 is clearly superior to bcrypt, which has had significant bugs, isn't as widely deployed, etc.

<https://wiki.php.net/rfc/hash_pbkdf2> <http://en.wikipedia.org/wiki/PBKDF2>

~~~
JoachimSchipper
[EDIT: tptacek is right. Listen to tptacek.]

PBKDF2 isn't necessarily better than bcrypt. But yes, it's a good choice.

~~~
tptacek
PBKDF2 is objectively worse than bcrypt, but the difference between them isn't
meaningful. The only meaningful choice in password hashes is between
PBKDF2/bcrypt and scrypt, which is hardened against gate-level-optimized
attacks.

------
mvts
Wow, that actually is very useful. Looking forward to using it.

------
verisimilitude
I was using Marco Arment's class Bcrypt before:
<https://gist.github.com/1053158>

This is a nice improvement.

------
titraxx
I think <http://crackstation.net/hashing-security.htm> is better.

~~~
mylittlepony
Have you even read that? It recommends bcrypt too:
<http://crackstation.net/hashing-security.htm#faq>

~~~
titraxx
Oups, my mistake. I thought that md5 was used by default by the new API (I
made a quick read) but you are all right ! Tiredness can be deceptive... But
it's still a good website where I learned to make secure password hashing for
the first time.

------
16s
Is this officially part pf PHP 5.5 or just an RFC that ___might_ __be included
in PHP 5.5?

~~~
nikic
The RFC was accepted. So it will be in PHP 5.5 (unless some serious flaw with
it comes to light in the meantime).

