EN 中文

Password Hashing

Use PHP to generate various password hashes.

Simply type a password into the box and click the button. Your password will be hashed using various algorithms. For more information keep scrolling.

Input your password and click the button

Note, the passwords you input here are not stored anywhere.

An Introduction to Password Hashing

What Not to Do

If users are using passwords to log into your website, they need a password, and you need to save this password somewhere to verify against. One way to do this would be to set up a database (SQLite, MySQL, Postgre-SQL, MongoDB, Neo4j – you've got plenty of options!) to store the required information. At it's simplest form you need a unique ID for each user, an email and a password.

Example Users Table | Plain Text

+----+---------------------------+---------------+
| id | email                     | password      |
+----+---------------------------+---------------+
|  1 | ifor@designedbywaldo.com  | opensesame    |
|  2 | joebloggs@blog.com        | topsecret!    |
+----+---------------------------+---------------+

Warning: Do not save passwords as plain text!

If your site is compromised and your users’ passwords are saved in plain text, the hacker can now use those passwords to try and gain access to other sites registered with the same email address, social networks, online banking etc, (another reason to have different passwords for different sites).

You would think that securely storing passwords in the database would be the first thing to do, but even huge companies like Verizon stored all personal user information in plain text. When the site was hacked, the hacker didn't need to decrypt anything. So if we shouldn't save passwords in plain text, what should we do? Instead of saving the plain text password in the database, we save a hashed version.

If someone gets hold of the user data, they need to decrypt the passwords before they can use them. Depending on the encryption algorithm used, but more importantly the complexity and strength of the password, decryption could take anywhere between seconds to years.

Ye Olde Hashing Algorithms

Before we get onto the hashing functions you should be using today, let's see what used to be considered best practice.

MD5

MD5 (Message Digest algorithm, version 5) is a hashing algorithm which takes an arbitrary-length sequence of characters and converts it to a unique sequence of characters of a fixed length (128 bits = 16 bytes = 32 hexadecimal digits).

It is theoretically impossible to reverse a hash, i.e. calculate the plain text password from an MD5 hash. The problem is that most users use simple passwords (think, 123456, abc123), meaning MD5 reverse dictionaries (rainbow tables) exist – dictionaries containing millions of MD5 hashes and the original string which created it, I found one which was 690 GB in size and contained 13,759,005,997,841,642 entries! So if you have a strong password, even if MD5 hashing is employed, you're data is much safer than if you used a weak password.

Example Users Table | MD5

+----+---------------------------+----------------------------------+
| id | email                     | password_hash                    |
+----+---------------------------+----------------------------------+
|  1 | ifor@designedbywaldo.com  | e6078b9b1aac915d11b9fd59791030bf |
|  2 | joebloggs@blog.com        | f722e006cb4e45d444a849461a701f93 |
+----+---------------------------+----------------------------------+

SHA-1

SHA-1 (Secure Hash Algorithm 1) is a cryptographic hash function designed by the NSA, similar to the MD series of functions, it produces a hash value known as a message digest. It takes a text sequence and produces a 40 digit hash (160 bits). It is considered more secure than the MD5 algorithm, but suffers from the same vulnerability as MD5, in that rainbow tables exist, so reverse lookups of SHA-1 hashes is possible.

Example Users Table | SHA-1

+----+--------------------------+------------------------------------------+
| id | email                     | password_hash                           |
+----+--------------------------+------------------------------------------+
|  1 | ifor@designedbywaldo.com | 17618f01a3a21b911c925bcb525a1d21abd30673 |
|  2 | joebloggs@blog.com       | 92618a850be2173d3bee599033d78a5138f3b9ef |
+----+--------------------------+------------------------------------------+

Add Some Salt

So how do we make it mode difficult for someone to crack a hashed password? We add salt.

A salt is a random string which is appended (prepended, or used by some function to add) to the original password before it is hashed. E.g. if the salt value was f5H1cJ0cysf7BrFG, and our password was password, we would run passwordf5H1cJ0cysf7BrFG through our hashing algorithm and store the resulting hash and salt in the database.

Now that every password has a different hash, even if the original password was the same. I.e. if two people had the same password, password, the hashes for those passwords in the database would be different, as they used random salts. Checking these hashes against a rainbow table, you're much less likely to get a result.

When the user logs in, they type their password, the salt is added to the password, it's hashed and checked against the value in the database.

Example Users Table | MD5 with Salt

+----+---------------------------+----------------+----------------------------------+
| id | email                     | salt           | password_hash                    |
+----+---------------------------+----------------+----------------------------------+
|  1 | ifor@designedbywaldo.com  | bee599033d78a5 | 42313b5d4e3074a0c9d753783684d673 |
|  2 | joebloggs@blog.com        | 5a1d21abd30673 | a58cc50abff91db6527c5f44540a8090 |
+----+---------------------------+----------------+----------------------------------+

Current Best Practices

In the past the MD5 plus Salt combo was considered best practice, but as time goes by, technology evolves and best practices of old become the “What the f$%# are you doing?!” of today. So what should we be using for password hashing now?

As of version 5.5, PHP has built-in functions for generating, verifying and returning information about hashes.

password_hash

password_hash() creates a new password hash using a strong one-way hashing algorithm. password_hash() is compatible with crypt(). Therefore, password hashes created by crypt() can be used with password_hash().

Password Hashing Syntax

string  password_hash         ( string $password , integer $algo [, array $options ] )
boolean password_needs_rehash ( string $hash , integer $algo [, array $options ] )
boolean password_verify       ( string $password , string $hash )
array   password_get_info     ( string $hash )

The following algorithms are currently supported:

  • PASSWORD_DEFAULT - Use the bcrypt algorithm (default as of PHP 5.5.0). Note that this constant is designed to change over time as new and stronger algorithms are added to PHP. For that reason, the length of the result from using this identifier can change over time. Therefore, it is recommended to store the result in a database column that can expand beyond 60 characters (255 characters would be a good choice).
  • PASSWORD_BCRYPT - Use the CRYPT_BLOWFISH algorithm to create the hash. This will produce a standard crypt() compatible hash using the "$2y$" identifier. The result will always be a 60 character string, or FALSE on failure.
    Supported Options:
    • salt – to manually provide a salt to use when hashing the password. Note that this will override and prevent a salt from being automatically generated.

      Warning: The salt option has been deprecated as of PHP 7.0.0. It is now preferred to simply use the salt that is generated by default.

    • cost – which denotes the algorithmic cost that should be used. Examples of these values can be found on the crypt() page.

      If omitted, a default value of 10 will be used. This is a good baseline cost, but you may want to consider increasing it depending on your hardware.

Example Users Table | password_hash

+----+---------------------------+--------------------------------------------------------------+
| id | email                     | password_hash                                                |
+----+---------------------------+--------------------------------------------------------------+
|  1 | ifor@designedbywaldo.com  | $2y$11$9rcrU1Bo/4KxW7ur/a0qJ.LJG7QiIU.wtkB6AovOpHGPqvpNs7Uhy |
|  2 | joebloggs@blog.com        | $2y$11$wwMi3kqf8SmFC7Y8vANicerepQOwDtiQ5GBR1d10Wbc4MKeiEBCKm |
+----+---------------------------+--------------------------------------------------------------+

Password Protecting Folders in Apache

Basic Authentication

There are four formats that Apache recognizes for basic-authentication passwords. Note that not all formats work on every platform:

  • Plain text (i.e. unencrypted) – Windows, BEOS, & Netware only.
  • Crypt (Unix only) – Uses the traditional Unix crypt(3) function with a randomly-generated 32-bit salt (only 12 bits used) and the first 8 characters of the password.
  • MD5"$apr1$" + the result of an Apache-specific algorithm using an iterated (1,000 times) MD5 digest of various combinations of a random 32-bit salt and the password.
  • SHA-1"{SHA}" + Base64-encoded SHA-1 digest of the password.

Apache .htaccess and .htpasswd Files

For a username of ifor and password of 1234, using the APR1-MD5 algorithm the .htpasswd file would look like this (new line for each user):

.htpasswd File

The relevant part of the .htaccess file would look something like this:

.htaccess File

Determining .htpasswd Path

If you do not know the full path to the .htpasswd file copy the following code and save as fullpath.php and upload to the directory the .htpasswd file will be located. Once uploaded, browse to http://www.your-domain.com/path/to/fullpath.php to view the path.

fullpath.php Expand

<?php

/**
 * Echo current directory path.
 */
$dir = dirname(__FILE__);
echo "<p>Full path to this dir: $dir</p>";
echo "<p>Full path to a .htpasswd file in this dir: $dir";
echo DIRECTORY_SEPARATOR . ".htpasswd</p>";

Appendix: PHP Functions

Click the expand button on the right to view the entire code. Clicking into the textarea at the bottom of each example will highlight the text for you to copy and use yourself.

Bcrypt Expand

/**
 * Hash a password using the Bcrypt hashing algorithm (PHP 5.5+).
 *
 * Note that setting the second argument to PASSWORD_DEFAULT will
 * use the default hashing algorithm of the PHP version you're
 * currently using.
 *
 * @param  string   $password     Password to be hashed
 * @param  string   $cost         Manually set cost
 *                                (higher value = longer
 *                                processing time)
 * @return string                 Hashed password
 */
function cryptBcrypt($password, $cost = 11){
    return password_hash($password, PASSWORD_BCRYPT, ['cost' => $cost]);
}

MD5 (built-in function)Expand

/**
 * Hash a password using the MD5 hashing algorithm.
 *
 * @param  string   $password     Password to be hashed
 * @return string                 Hashed password
 */
md5($password);

SHA-1 (built-in function)Expand

/**
 * Hash a password using the SHA-1 hashing algorithm.
 *
 * @param  string   $password     Password to be hashed
 * @return string                 Hashed password
 */
sha1($password);

Crypt Cost Expand

/**
 * Calculate optimal cost for password hashing for current server.
 *
 * General baseline is 8-10, with a target time of ≤ 50 milliseconds.
 *
 * @return string $cost
 */
function cryptCost(){

    $timeTarget = 0.05; // 50 milliseconds

    $cost = 8;
    do {
        $cost++;
        $start = microtime(true);
        password_hash("test", PASSWORD_BCRYPT, ["cost" => $cost]);
        $end = microtime(true);
    } while (($end - $start) < $timeTarget);

    return "Appropriate Cost Found: " . $cost . "\n";
}

.htpasswd | Crypt Expand

/**
 * Create .htpasswd compatible password hash using the
 * Crypt hashing algorithm (UNIX only).
 *
 * @param  string   $password     Password to be hashed
 * @return string                 Hashed password
 */
function cryptCrypt($password){
    return crypt($password, base64_encode($password);
}

.htpasswd | APR1-MD5 Expand

/**
 * Create .htpasswd compatible password hash using the
 * APR1-MD5 hashing algorithm (windows compatible).
 *
 * Original source: https://www.virendrachandak.com
 *
 * @param  string   $password     Password to be hashed
 * @return string                 Hashed password
 */
function cryptApr1Md5($password)
{
    $salt = substr(
        str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"),
        0, 8);

    $len = strlen($password);

    $text = $password.'$apr1$'.$salt;

    $bin = pack("H32", md5($password.$salt.$password);

    $tmp = '';

    for ($i = $len; $i > 0; $i -= 16) {
        $text .= substr($bin, 0, min(16, $i);
    }

    for ($i = $len; $i > 0; $i >>= 1) {
        $text .= ($i & 1) ? chr(0) : $password{0};
    }

    $bin = pack("H32", md5($text);

    for ($i = 0; $i < 1000; $i++) {
        $new = ($i & 1) ? $password : $bin;
        if ($i % 3) $new .= $salt;
        if ($i % 7) $new .= $password;
        $new .= ($i & 1) ? $bin : $password;
        $bin = pack("H32", md5($new);
    }

    for ($i = 0; $i < 5; $i++) {
        $k = $i + 6;
        $j = $i + 12;
        if ($j == 16) $j = 5;
        $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;
    }

    $tmp = chr(0) . chr(0) . $bin[11] . $tmp;

    $tmp = strtr(strrev(substr(base64_encode($tmp), 2)),
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
    "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");

    return "$"."apr1"."$".$salt."$".$tmp;
}

.htpasswd | SHA-1 Expand

/**
 * Create .htpasswd compatible password hash using the
 * SHA-1 hashing algorithm.
 *
 * @param  string   $password     Password to be hashed
 * @return string                 Hashed password
 */
function cryptSha1($password){
    return "{SHA}" . base64_encode(sha1($password, true);
}

`
Prev Project Next Project