A deeper look into PHP Hash Algorithms
Tags: php, security, hashes, performance
Published: 2019-11-25 18:39:56

In my first article about [PHP Hash Algorithms](https://blog.kovah.de/en/2019/php-hash-algorithms-in-detail/) I explored the current status of the algorithms and how they perform in a race against each other. In this post I want to dive a bit deeper into the results generated by an updated version of my code.

Like with the first version of the results, the code and the generated .csv file can be found in the [repository](https://github.com/Kovah/php-hashes).


## Updated results for all Hash Algorithms

As explained in the first post, I wanted to generate more results based on the different string lengths passed into the `hash()` function. Besides the normal generation of hashes for the CLI, I added another file containing the code for generating a complex .csv file. This file contains the time it takes for each algorithm to generate strings from a length of `512` up to a length of `33554432`, which equals `2^25`. The hash is generated for the string lengths in steps of the last length times two, so `512`, `1024`, `2048` and so on.

But that's not all. For each string length, the best performing algorithm is calculated and appended to the results.


## String length matter for hash generation

What sounds obvious is indeed an interesting factor to consider when generation hashes. As those algorithms are all some kind of mathematical constructs, each algorithm can perform way better or much worse than another. But let's take a look at the actual results.  
I took 5 different string lengths out of all generated ones to get a simple overview on the performing algorithms and how they behave if the string length changes exponentially.


### Slowest Algorithms

| String length | Time | Algorithm | 2nd slowest Algorithm |
|:--------------|-----:|:----------|:----------------------|
| `2^9`  | `0.0751019 ms`    | gost (raw) | snefru (`0.0660419 ms`)      |
| `2^13` | `0.8158684 ms`    | md2 (raw)  | sha3-512 (`0.4711151 ms`)    |
| `2^17` | `13.1139755 ms`   | md2 (raw)  | sha3-512 (`7.2920322 ms`)    |
| `2^21` | `204.2510509 ms`  | md2 (raw)  | sha3-512 (`113.0850315 ms`)  |
| `2^25` | `3277.1139145 ms` | md2 (raw)  | sha3-512 (`1859.3208790 ms`) |

As you can see, [MD2](https://en.wikipedia.org/wiki/MD2_(hash_function)) is the slowest algorithm, except for the shortest string length. It is followed by SHA3-513 but with a large gap in between. For the longest string length of `2^25`, MD2 tool almost twice as long to generate a hash as SHA3-512.


### Fastest Algorithms

| String length | Time | Algorithm | 2nd fastest Algorithm |
|:--------------|-----:|:----------|:----------------------|
| `2^13` | `0.0088215 ms`  | adler32 (raw) | fnv164 (`0.0119209 ms`) |
| `2^17` | `0.0741482 ms`  | adler32 (raw) | md4 (`0.1289845 ms`)    |
| `2^21` | `1.9049644 ms`  | adler32 (hex) | md4 (`1.9161701 ms`)    |
| `2^25` | `17.7400112 ms` | adler32 (raw) | md4 (`30.8220387 ms`)   |

In case of the fastest algorithm, [Adler-32](https://en.wikipedia.org/wiki/Adler-32) made the race. Please notice that I left out the generation for a string length of `512`, because _several_ algorithms took `0.0050068 ms` to complete, so a clear winner can't be selected. [MD4](https://en.wikipedia.org/wiki/MD4) made it to the second place.


#### Investigation of the fastest Algorithms

Let's have a look at both algorithms because the speed, especially of Adler-32, is astonishing. According to sources such as the [RFC 3309](https://tools.ietf.org/html/rfc3309) which describes the checksum change from Adler-32 to CRC32 for the SCTP protocol, Adler-32 is weak if used for either short messages or when the source string does not change very much.
MD4 on the other side, is already known as insecure, as you can read in the "Security" section of the Wikipedia page. Several tests have successfully completed a collision, which is the worst that can happen to an algorithm.

Just to make sure, let's have another look at our data and pick the next fastest algorithm: [FNV](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function) in different variations. According to our data, fnv1a64 locked in on the third place with execution times of `34,1711044 ms` for a length of `2^25`. The gap between MD4 and FNV is negligible with just `4 ms`. 


### A word about Security

Hashes are meant to convert an input string into a fixed-length output string. Regarding security, we can divide hash algorithms into two groups: cryptographically and non-cryptographically algorithms. The former are used, for example, to generate password hashes with the `password_hash()` function and can be considered secure. The latter, in contrast, are used to store information in a more dense way or verify that a large given value matches another one by computing and comparing their hashes.

If you are new to the programming world and want to take off by storing your first user passwords in the database, then **never ever** use something like `hash('md5', $user_password)`!!! The only correct way to properly generate a secure and trustworthy hash of a user password, is to use the function mentioned above:

```php
// PHP < 7.2
$hash = password_hash($user_password, PASSWORD_BCRYPT);

// or for PHP starting at 7.2
$hash = password_hash($user_password, PASSWORD_ARGON2I);
```

To be able to verify the resulting hash when a user wants to login again, use the corresponding function for this:

```php
$password_matches = password_verify($user_password, $hash);
```

---

_This article was first published on [Blog.Kovah.de](https://blog.kovah.de/en/2019/deeper-look-into-php-hash-algorithms/)_