Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP password_verify() and slow equals comparison

Tags:

security

php

hash

I've been trying to find information about whether password_verify() uses length-constant time comparison to avoid timing attack.

Now, simple example:

$hash = '$2y$10$HH3906lfby7HOy1N3duQh.Kju.84ct6AcMZm2p/SYZsZSXuYWvvT.';

$startTime = microtime(TRUE);
password_verify('rasmuslerdorf', $hash);
$endTime = microtime(TRUE);

$time = $endTime - $startTime;

This always produces slightly different output, which, according to this article ("Why does the hashing code on this page compare the hashes in "length-constant" time?" paragraph), could be used in timing attack to pick up a hash. I think those results look rather a bit random, but they surely not constant.

Question is, does password_verify() uses length-constant time comparison to avoid timing attack? No info on it in the docs, and due to my shallow experience I cannot interpret function processing time results well.

like image 684
Damaged Organic Avatar asked Apr 07 '15 10:04

Damaged Organic


2 Answers

The answer is YES it uses length-constant time comparison.

This is an excerpt of php's password_verify function

    /* We're using this method instead of == in order to provide
 * resistance towards timing attacks. This is a constant time
 * equality check that will always check every byte of both
 * values. */
for (i = 0; i < hash_len; i++) {
    status |= (ret->val[i] ^ hash[i]);
}

You can have a look at the full source code at https://github.com/php/php-src/blob/master/ext/standard/password.c

like image 103
shock_gone_wild Avatar answered Oct 03 '22 14:10

shock_gone_wild


Short answer: yes, it does.

Long answer: it's not necessary.

To understand why it's not necessary, we need to look at the strings being compared:

$2y$10$9JxHB8U1QKsLS/ynplKzm.iIO7f6gtTKYA61ppVuANYxWNCA5DW1S
$2y$10$ILlWQrYyDJvHHkxcCgjm7OThLRAmMcTzsJOZOwjaSYiRUHq8LVYde
$2y$10$8JfydDKUNbOeiybwZ9m.j.5TC8CBqkc3RZu2DX42A4dFNpNYPWfzm
$2y$10$qeG.53lr9PVVGN4Yk.kSZuOMpfone5kINyWVpAf2gUXPseU2WdSzK
$2y$10$nZUgPUwiXIvCJ9BY1wbtbuV5vH6yff9CNyumFsI/NN2eJmf20iec.

That's 5 different hashes of the same password. The format is:

$2y$10$saltsaltsaltsaltsaltsahashhashhashhashhashhashhashhas

Now, to a remote attacker (one who would be running timing attacks), the salt is a secret. And the salt is the same when we re-hash their try. For example:

stored password "test":
hash = $2y$10$9JxHB8U1QKsLS/ynplKzm.iIO7f6gtTKYA61ppVuANYxWNCA5DW1S

If the attacker tries the password "abc", internally password_verify() will call crypt("abc", hash). Which will result in:

$2y$10$9JxHB8U1QKsLS/ynplKzm.FTYpGS/gNDw4SB6YD0wEtCSPgGvtPim

Now, let's look at those two hashes, side by side:

$2y$10$9JxHB8U1QKsLS/ynplKzm.iIO7f6gtTKYA61ppVuANYxWNCA5DW1S
$2y$10$9JxHB8U1QKsLS/ynplKzm.FTYpGS/gNDw4SB6YD0wEtCSPgGvtPim

Notice the salt is the same? Notice that everything up until the first . is the same. Also notice that the attacker has no idea what the salt is.

If the attacker was able to timing attack the comparison, it would do no good. Because they don't know the salt (and hence deducing what the hash is simply wastes time since without the salt they can't determine the password).

So timing safety isn't strictly necessary.

Why is it included then? Because everyone makes mistakes. Because defense-in-depth is a good idea. Because this analysis assumes that nothing about the hash is useful without the salt (ex: what if a flaw in bcrypt biased the hashes based on the password, so without knowing the salt the keyspace is reduced from 72^255).

In short, it's a good thing to have, but it's not strictly necessary...

like image 43
ircmaxell Avatar answered Oct 03 '22 14:10

ircmaxell