Secure Wordpress by changing hashing algorithm from MD5

The MD5 message-digest algorithm is a cryptographically broken but still widely used hash function producing a 128-bit hash value. - Wikipedia
$hash = md5($salt . $password, TRUE);

To start with a minor positiv thing, Wordpress is not storing users passwords in plain text. The way Wordpress store users passwords is using a MD5 hash of the password and a salt. For years now, the MD5 hashing algorithm is known as unsecure and easy to crack. So why do Wordpress stil use an out of date hash algorithm? Diving into the source code on Github, there is a comment with an explanation.

# We were kind of forced to use MD5 here since it's the only
# cryptographic primitive that was available in all versions
# of PHP in use.  To implement our own low-level crypto in PHP
# would have resulted in much worse performance and
# consequently in lower iteration counts and hashes that are
# quicker to crack (by non-PHP code).
- Wordpress Source Code

Sounds like the reason for Wordpress choice is backwards compatability of PHP versions.

How can I replace the MD5-algorithm?

To fix the problem and get away from MD5 hashing of user passwords, there are two functions that need to be overriden wp_hash_password and wp_check_password. This can be done with plugins and there are Wordpress plugins that fix it. For instance PHP Native password hash. I want more control of the plugins I activate on the Wordpress installations I have and therefore will write my own Wordpress plugin.

Creating a Wordpress plugin to replace default MD5 password hash in Wordpress

Since PHP 5.5.0 there has been a build-in function password_hash and this is the method I am going to use. The plugin is quite small and easy to write, just overriding some of the build-in functions in Wordpress. One upside of writing and have full control of the plugin myself is that if bcrypt that I am now gets old or I want to use another hashing algorithm, I can fully control the plugin. No need to go and find another plugin.

<?php
/**
 * Plugin Name:       Beitostølen Live Password Hasher
 * Plugin URI:        https://github.com/Beitostolen-Live/beitostolenlive-wordpress-theme
 * Description:       Replace the default Wordpress password hash.
 * Version:           1.0.0
 * Requires at least: 5.2
 * Requires PHP:      7.2
 * Author:            Teis Lindemark
 * Author URI:        https://teilin.net/
 * License:           MIT
 * License URI:       https://github.com/Beitostolen-Live/beitostolenlive-wordpress-theme/blob/dev/LICENCE.md
 */

 if(!function_exists('add_filter')) {
     header('Status: 403 Forbidden');
     header('HTTP/1.1 403 Forbidden');
     exit();
 }

 if(version_compare(phpversion(), '5.5', '>=')
    && !function_exists('wp_check_password')
    && !function_exists('wp_hash_password')
    && !function_exists('wp_set_password')
) :

define('WP_OLD_HASH_PREFIX', '$P$');

function wp_check_password($password, $hash, $user_id = '') {
    if(0 === strpos($hash, WP_OLD_HASH_PREFIX)) {
        global $wp_hasher;
        if(empty($wp_hasher)) {
            require_once(ABSPATH . WPINC . '/class-phpass.php');
            $wp_hasher = new PasswordHash(8,true);
        }
        $check = $wp_hasher->CheckPassword($password,$hash);
        if($check && $user_id) {
            $hash = wp_set_password($password, $user_id);
        }
    }
    $check = password_verify($password, $hash);
    return apply_filters('check_password', $check, $password, $hash, $user_id);
}

function wp_hash_password($password) {
    $options = apply_filters('wp_hash_password_options', []);
    return password_hash($password, PASSWORD_BCRYPT, $options);
}

function wp_set_password($password, $user_id) {
    /** @var \wpdb $wpdb */
    global $wpdb;
    $hash = wp_hash_password($password);
    $wpdb->update($wpdb->users, ['user_pass' => $hash, 'user_activation_key' => ''], ['ID' => $user_id]);
    wp_cache_delete($user_id, 'users');
    return $hash;
}

endif;
?>

Conclusion

As Microsoft say in "CA5351 Do Not Use Broken Cryptographic Algorithms", using broken algorithms can expose significant risks. Since Wordpress is so widely popular CMS too use and easy available, I guess that most of the installations out there keep the default sattings as well. Since it does not seems like Wordpress is going to break the compatability to all the PHP versions, it would probably make it more clear when installing Wordpress if a plugin replaceing the default hashing was suggested by default (at least if it was supported by the PHP version).

Teis Lindemark

Read more posts by this author.