# Improving WordPress Password Security

> **Note:** With WordPress 6.8 introducing native bcrypt password hashing, the `wp-password-bcrypt` package is now obsolete. [We’ve officially sunset the package](https://roots.io/sunsetting-wp-password-bcrypt-with-wordpress-6-8/).

We've released the [wp-password-bcrypt](https://github.com/roots/wp-password-bcrypt) plugin to improve WordPress password security by using bcrypt instead of insecure MD5 password hashing.

March 21st update: see the [follow-up post](https://roots.io/wordpress-password-security-follow-up/) on password security for more information and corrections on some errors made in this post.

## Outdated PHP requirements

WordPress, and its community, love to [parrot that it powers 25% of the web](https://ma.tt/2015/11/seventy-five-to-go/). The downside is when you're doing something wrong, you're affecting 1/4 of all websites.

Roots has long been critics of the out-dated PHP version requirements in WordPress. They still have 5.2 as the minimum version which has been end of life (EOL) since **January 6th 2011**. It is so old that it doesn't even appear on PHP's [version calendar](http://php.net/supported-versions.php). It is so old that even PHP 5.5 won't be supported in **4 months** (July 10th 2016).

WordPress' core team stance on bumping the PHP version requirement is two fold:

1. Too many WP users are still on old versions like 5.2 and 5.3
2. They don't care about new "features"

This breaks down when it comes to security though. As mentioned above, PHP 5.2 has not received security fixes since 2011. Ditto for 5.3 (August 2014) and 5.4 (September 2015).

## Password hashing

One of the most important features PHP has introduced was strong password hashing in 5.5. This includes [`password_hash`](http://php.net/manual/en/function.password-hash.php) and [`password_verify`](http://php.net/manual/en/function.password-verify.php) functions which *default* to the strong and more secure [bcrypt](https://en.wikipedia.org/wiki/Bcrypt).

Most developers are familiar with password hashing as it's become common knowledge to *never* store passwords in plain text. [Password hashing](https://en.wikipedia.org/wiki/Cryptographic_hash_function#Password_verification) lets us store a hashed version of the password which is practically impossible to invert making it much more secure.

Just because you can't invert a hash to find it's original input (password in this case), doesn't mean we're completely secure. It's possible for attackers to work forwards instead of backwards. They can attempt to [brute-force](https://en.wikipedia.org/wiki/Brute-force_attack) passwords by generating hashes of all *possible* combinations to find a match.

[Rainbow tables](https://en.wikipedia.org/wiki/Rainbow_table), and modern hardware, make calculating every combination faster than ever. As the speed of calculations has increased, the list of [broken hash functions](https://en.wikipedia.org/wiki/Category:Broken_hash_functions) has grown.

### MD5

[MD5](https://en.wikipedia.org/wiki/MD5) is a broken hash function:

> The security of the MD5 has been severely compromised, with its weaknesses having been exploited in the field, most infamously by the Flame malware in 2012. The CMU Software Engineering Institute considers MD5 essentially "cryptographically broken and unsuitable for further use".

MD5 may sound familiar. It's the hashing function that WordPress uses. In fairness to WordPress, they combine MD5 with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)).

Unfortunately, MD5 + salting is also broken and this has been [known](https://codahale.com/how-to-safely-store-a-password/) for a [long](https://blog.mozilla.org/webdev/2012/06/08/lets-talk-about-password-storage/) [time](https://crackstation.net/hashing-security.htm).

MD5 is considered "broken" due to its collision vulnerability, but it's broken more fundamentally for passwords: *it's too cheap and fast to calculate a hash*.

In fact, even the WordPress core team knows it's broken: <https://core.trac.wordpress.org/ticket/21022>.

That ticket was opened over **4 years ago**. WordPress uses a library called [phpass](http://www.openwall.com/phpass/). It's a portable PHP password hashing framework that's included in [WP core](https://github.com/WordPress/WordPress/blob/master/wp-includes/class-phpass.php).

This is how WordPress creates a hasher:

```
$wp_hasher = new PasswordHash( 8, true );

```

The 2nd parameter of `true` is the important one. That boolean flag tells phpass whether to use a "portable" hash. If that flag was `false` then phpass would check for the existence of a strong hashing function such as bcrypt and use it.

Since WordPress is always setting that flag as `true`, **25% of the web defaults to the more insecure MD5 hashing**. To be fair, switching that flag wouldn't magically make all 25% more secure. But any site at least on PHP &gt;= 5.3.7 would, which is at least 54% of all WP sites and likely much more than that.

So what is holding up the switch? Bureaucracy and the unwillingness to make it happen.

The consensus of the [ticket](https://core.trac.wordpress.org/ticket/21022) is that it's actually a *UX problem*. At this point, there is no technical reason why this can't be done.

The WordPress core team simply doesn't care enough about this issue to solve a UX problem which would make every WordPress site, and its users, more secure. You would hope that a CMS/framework powering 25% of the web would take its responsibility as a leader a little more seriously and set an example.

### bcrypt

I've talked a lot about MD5 above and why it's now unsuitable as a password hashing function. Why is bcrypt so much better? It was designed for passwords so it's *slower* and more expensive to calculate.

> bcrypt is an adaptive function: over time, the iteration count can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.

The "interation count", or cost/work factor, [defaults to 10 in PHP](http://php.net/manual/en/function.password-hash.php) and can be configured.

## wp-password-bcrypt

Roots has created a new [wp-password-bcrypt](https://github.com/roots/wp-password-bcrypt) plugin to address this problem. It overrides the default WordPress functions `wp_hash_password` and `wp_check_password` to use the built-in PHP `password_hash` and `password_verify` functions which use bcrypt.

There are other ways to do this but this is the simplest most direct way. The only requirement is you need PHP 5.5.

The best and easiest way to install it is through Composer:

```
$ composer require roots/wp-password-bcrypt

```

 Copy

Or you can add it to your `composer.json` file:

```
"require": {
  "php": ">=8.0",
  "roots/wordpress": "6.1.1",
  "roots/wp-password-bcrypt": "^1.0.0"
}

```

You can also install it manually as an mu-plugin (`wp-password-bcrypt.php`). For more installation details, see the [README](https://github.com/roots/wp-password-bcrypt).

wp-password-bcrypt is also being added as a default plugin to [Bedrock](https://github.com/roots/bedrock) because we believe in more secure defaults (see the [PR](https://github.com/roots/bedrock/pull/243)).