rfc:password_hash

This is an old revision of the document!


Request for Comments: Adding simple password hashing API

Introduction

This RFC describes a new API for simplified password hashing.

Why Do We Need Password Hashing?

Password Hashing is a way to convert a user-supplied password into a one-way derived token for storage. By using the derived token, it makes it impossible to reverse the stored token and get the original password used by the user. This adds a layer of defense in case an attacker gets access to the database storing the password.

==== Why Do We Need Strong Password Hashing?

As it turns out, just hashing a password using md5() or even sha512() isn't good enough. Cryptographic hash functions (such as those supplied by hash()) are designed to be fast. This is good for cryptographic needs such as signing. But for password hashing, that's a problem since it allows an attacker to brute force a lot of passwords very quickly. Adding a salt makes it resistent to rainbow tables, but not resistent to brute forcing where that salt is known.

By using either a stretched algorithm (Such as PBKDF2) or an algorithm designed to be slow (Such as bcrypt), a much better defense against brute forcing will be had.

Why Do We Need A Simple API

As recent attacks have shown, strong password hashing is something that the vast majority of PHP developers don't understand, or don't think is worth the effort. The current core implementations of strong password hashing using crypt() are actually fairly difficult to work with. The error states are difficult to check for (returning *0 or *1 on error). The salt format is difficult to generate as it uses a custom base64 alphabet (. instead of + and no padded =). Additionally, salts are reasonably difficult to generate randomly (not too difficult, but requires a fair bit of code). Additionally, checking the return when validating a password can expose the application to http://en.wikipedia.org/wiki/Timing_attack.

By providing a simple API that can be called, which takes care of all of those issues for you, hopefully more projects and developers will be able to use secure password hashing.

Common Misconceptions

Salts Need To Be Cryptographically Secure

Salts exist for a single reason: To make it so that any time (CPU effort) spent cracking a single password hash cannot be amortized across multiple hashes. That means that attacking a single password hash will have no impact on the time it will take attacking another hash. Based on that reason, salts only need to be unique in a system. There is no requirement for them to be cryptographically secure.

Hash(password + salt) Is Fine

No, it's not. There's plenty of information out there to dispel this myth. See the references section for some details.

Proposal and Patch

The proposal is to add a new set of password hashing APIs to the standard PHP library. These hashing APIs will initially be thin wrappers around crypt() to allow for automatic salt generation and better error checking. The APIs are designed such that they can easily be extended in the future as additional strong hashing algorithms are introduced into PHP's core (Such as scrypt).

New Functions

  • string password_hash(string $password, string $algo = PASSWORD_DEFAULT, array $options = array()) - The function which creates new password hashes. If called with one parameter, it will auto-generate a salt, and use the defined default algorithm (currently bcrypt). The $options array allows for passing in algorithm specific options. In the case of bcrypt, two options are supported: salt and cost. The salt parameter, if provided, will be used in place of an auto-generated salt. The cost parameter is passed to crypt() to control the amount of CPU time that should be expended creating the hash (higher is more resistent to brute forcing, lower is kinder on the servers. A balance should be achieved).
  • bool password_verify($password, $hash) - The function which verifies an existing hash. This hash can be created via password_hash(), or a normal crypt() hash. The only thing it provides on top of crypt() is resistance to timing attacks by using a constant-time comparison function.
  • string password_make_salt(int $length, bool $raw_output = false) - This function will create a new random salt of the specified length using psuedo-random algorithms. It will be used by password_hash() if a salt is not provided. But it can also be used to generate salts for other crypt() algorithms that password_hash() does not support. It can also be used to generate strong salts for other algorithms, such as PBKDF2 (which exists as an RFC now), or 3pd libraries like PHPASS.

New Constants

Initially, two constants are defined:

  • PASSWORD_BCRYPT = “2y” - Create new password hashes using the CRYPT_BLOWFISH algorithm
  • PASSWORD_DEFAULT = PASSWORD_BCRYPT - The default algorithm to use for hashing if no algorithm is provided. This can change in future releases if a new, stronger hashing algorithm (such as scrypt is supported).

Supported Algorithms

* BCrypt - The CRYPT_BLOWFISH algorithm. The strongest algorithm currently supported by PHP.

Examples

Basic Usage:

basic_usage.php
<?php
$password = "foo";
$hash = password_hash($password);
// Store Hash
 
if (password_verify($password, $hash)) {
    // Password Is Correct
} else {
    // Password Is Not Correct
}
?>

Specifying Algorithm:

specify_algorithm.php
<?php
$password = "foo";
$hash = password_hash($password, PASSWORD_BCRYPT);
// Store Hash
 
if (password_verify($password, $hash)) {
    // Password Is Correct
} else {
    // Password Is Not Correct
}
?>

Specifying Cost:

specify_cost.php
<?php
$password = "foo";
$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 14);
// Store Hash
 
if (password_verify($password, $hash)) {
    // Password Is Correct
} else {
    // Password Is Not Correct
}
?>

Specifying Salt Manually:

specify_salt.php
<?php
$password = "foo";
$salt = mcrypt_create_iv(22, MCRYPT_DEV_URANDOM); 
$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 14, "salt" => $salt);
// Store Hash
 
if (password_verify($password, $hash)) {
    // Password Is Correct
} else {
    // Password Is Not Correct
}
?>

Generating Salts:

generate_salt.php
<?php
// 15 characters in the alphabet a-zA-Z0-9./
$salt = password_make_salt(15);
 
// 15 characters of binary data (0-255)
$raw_salt = password_make_salt(15);
?>

Possible Implementation Details

  • INI setting for bcrypt cost - Presently, the default cost for bcrypt is determined by a C constant which can be updated in future releases. This could be changed to use an INI constant such as password.bcrypt_cost = 11 so that hosts can change the default cost parameter at runtime. The problem with this is that hosts can also reduce the default cost so significantly that it makes the default hash very weak (relatively speaking).
  • INI setting for default algo - Presently, the default algorithm is identified by a constant that can be updated only with a source-code change. It may be worth while implementing an INI setting to allow that to be chosen by the host. As the proposed implementation has only a single algorithm, this may be a choice to be made in the future.

Patch

Currently, the proposed patch is not yet complete. The basic functionality is there, but it needs some refactoring and testing prior to official proposal. The Work-In-Progress can be seen on the hash_password branch of ircmaxell's fork.

The specific implementation is at password.c

PHP Implementation

Additionally, a compatibility version in PHP is maintained at Github. This can be used in PHP versions 5.3 and 5.4 and for testing.

References

Recent Attacks

Hashing In General

Timing Attacks

Strong Algorithms

Changelog

  • 0.1 - Initial Draft
rfc/password_hash.1340724110.txt.gz · Last modified: 2017/09/22 13:28 (external edit)