rfc:password_hash
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
rfc:password_hash [2012/07/03 11:41] – Remove ini parameter ircmaxell | rfc:password_hash [2017/09/22 13:28] (current) – external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== Request for Comments: Adding simple password hashing API ====== | ====== Request for Comments: Adding simple password hashing API ====== | ||
- | * Version: | + | * Version: |
* Date: 2012-06-26 | * Date: 2012-06-26 | ||
* Author: Anthony Ferrara < | * Author: Anthony Ferrara < | ||
- | * Status: | + | * Status: |
* First Published at: http:// | * First Published at: http:// | ||
Line 22: | Line 22: | ||
==== Why Do We Need A Simple API ==== | ==== 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, | + | 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, |
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. | 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 ===== | ===== Common Misconceptions ===== | ||
- | ==== Salts Need To Be Cryptographically Secure | + | ==== Salts Need To Be True Random |
- | 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 | + | 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 statistically globally |
==== Hash(password + salt) Is Fine ==== | ==== Hash(password + salt) Is Fine ==== | ||
Line 42: | Line 41: | ||
==== New Functions ==== | ==== New Functions ==== | ||
- | * //string password_hash(string $password, | + | * //string password_hash(string $password, |
* //bool password_verify($password, | * //bool password_verify($password, | ||
- | * //string password_make_salt(int $length, bool $raw_output | + | * //bool password_needs_rehash(string $hash, |
+ | * //array password_get_info(string $hash)// - This function gets the information | ||
==== New Constants ==== | ==== New Constants ==== | ||
- | Initially, | + | Initially, |
- | * // | + | * // |
- | * // | + | * // |
==== Supported Algorithms ==== | ==== Supported Algorithms ==== | ||
Line 104: | Line 104: | ||
It's important to note that this function does not take any indication of the algorithm or salt. That's because both are included in the resulting //$hash// return value from // | It's important to note that this function does not take any indication of the algorithm or salt. That's because both are included in the resulting //$hash// return value from // | ||
- | === password_make_salt() === | + | === password_get_info() === |
Errors: | Errors: | ||
- | * E_WARNING - When supplied an incorrect number of parameters. | + | * E_WARNING - When supplied an incorrect number |
- | * E_WARNING - If the length parameter is less than or equal to zero | + | |
- | * E_WARNING - If the length parameter is greater than // | + | |
- | Additionally, | + | On error, |
- | On error, it will return | + | Normal Operation: |
+ | |||
+ | When passed in a valid hash created by a supported password_hash algorithm, this function will return an array of information about that hash. The first associative element, " | ||
+ | |||
+ | === password_needs_rehash() === | ||
+ | |||
+ | Errors: | ||
+ | * E_WARNING - When supplied an incorrect number or type of of parameters. | ||
+ | |||
+ | On error, it will return | ||
Normal Operation: | Normal Operation: | ||
- | When the raw_output | + | The supplied hash parameter is tested to see if the algorithm and options supplied match. Basically, this is similar to a wrapper over password_get_info() to validate if the supplied hash matches |
- | It will use non-cryptographically safe, but strong random entropy sources, if possible for the salt generation. On windows, it will use // | + | <file php basic_usage.php> |
+ | <?php | ||
+ | $password = " | ||
+ | $hash = password_hash($password, PASSWORD_BCRYPT, array(" | ||
+ | if (password_verify($password, $hash)) { | ||
+ | if (password_needs_rehash($hash, | ||
+ | update_password_in_db($password); | ||
+ | } | ||
+ | log_user_in(); | ||
+ | } else { | ||
+ | error_wrong_password(); | ||
+ | } | ||
+ | ?> | ||
+ | </file> | ||
+ | |||
+ | It could be implemented in user-land by: | ||
+ | |||
+ | <file php user_needs_rehash.php> | ||
+ | <?php | ||
+ | function password_needs_rehash($hash, $algo, array $options = array()) { | ||
+ | $info = password_get_info($hash); | ||
+ | $return = $algo != $info[' | ||
+ | | ||
+ | $return |= array() != array_diff_assoc($info[' | ||
+ | return $return; | ||
+ | } | ||
+ | ?> | ||
+ | </ | ||
==== Examples ==== | ==== Examples ==== | ||
Line 127: | Line 161: | ||
<?php | <?php | ||
$password = " | $password = " | ||
- | $hash = password_hash($password); | + | $hash = password_hash($password, PASSWORD_DEFAULT); |
// Store Hash | // Store Hash | ||
Line 184: | Line 218: | ||
</ | </ | ||
- | ===Generating Salts:=== | ||
- | <file php 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 Future Implementation Details ==== | ==== Possible Future Implementation Details ==== | ||
* 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. | * 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. | ||
* Future PHP major releases should update the default bcrypt cost constant to increment it, providing default protection against increasing hardware performance. This will allow the default values for the function password_hash() to remain strong over time as hardware advances. | * Future PHP major releases should update the default bcrypt cost constant to increment it, providing default protection against increasing hardware performance. This will allow the default values for the function password_hash() to remain strong over time as hardware advances. | ||
+ | |||
+ | ==== Updating PASSWORD_DEFAULT ==== | ||
+ | |||
+ | I'd propose the following policy for updating the default hashing algorithm in future releases of PHP. | ||
+ | |||
+ | * Any new algorithm must be in core for at least 1 full release of PHP prior to becoming default. So if scrypt is added in 5.5.5, it wouldn' | ||
+ | * The default should only change on a full release (5.6.0, 6.0.0, etc) and not on a revision release. The only exception to this is in an emergency when a critical security flaw is found in the current default. | ||
+ | * For a normal (non-emergency) change in default, an RFC shall be issued for the update of the default algorithm, following normal RFC rules. | ||
==== Removed Concepts ==== | ==== Removed Concepts ==== | ||
Line 220: | Line 252: | ||
- The default algorithm is specified by a constant // | - The default algorithm is specified by a constant // | ||
- The default cost parameter to BCRYPT is specified in the php.ini file. This allows individual sites to tailor the cost of bcrypt for their needs. Additionally, | - The default cost parameter to BCRYPT is specified in the php.ini file. This allows individual sites to tailor the cost of bcrypt for their needs. Additionally, | ||
+ | - If an implemented algorithm is ever grossly compromised (to the point of uselessness), | ||
+ | |||
+ | ===== Discussion Points ===== | ||
+ | |||
+ | ==== password_hash() Algo Argument as optional ==== | ||
+ | |||
+ | There has been some discussion around the second argument of password_hash() (The algorithm argument) and whether it should have a default value or not. | ||
+ | |||
+ | === Should Have A Default === | ||
+ | |||
+ | The " | ||
+ | |||
+ | === Should Not Have A Default === | ||
+ | |||
+ | By not having a default value (and hence being a mandatory argument), it forces implementing developers to understand that the default argument can change over time. This has a few benefits in that developers need to recognize that storage requirements may change over time, that portability may be affected, etc. | ||
+ | |||
+ | === Current Position === | ||
+ | |||
+ | The current position of this RFC sides with the " | ||
+ | |||
+ | ==== password_make_salt() Is Not Needed ==== | ||
+ | |||
+ | There has also been discussion around whether or not // | ||
+ | |||
+ | === It should not be exposed === | ||
+ | |||
+ | The argument that it should not be exposed is that it's not really doing anything generic. It produces a random salt of the specified length. This can already be accomplished in user-land via combinations of functions such as // | ||
+ | |||
+ | === It should be exposed === | ||
+ | |||
+ | The argument that it should be exposed is that it needs to be implemented in C because it is needed for password_hash(), | ||
+ | |||
+ | === Current Position === | ||
+ | |||
+ | The current position of this RFC sides with the " | ||
+ | |||
+ | ==== password_needs_rehash() is not needed ==== | ||
+ | |||
+ | The function // | ||
+ | |||
+ | === Not Needed === | ||
+ | |||
+ | Since the function can be implemented in user-land, there is no need to implement it in core. | ||
+ | |||
+ | === Needed === | ||
+ | |||
+ | There are a few reasons to include it in core. It makes it significantly easier to implement as otherwise implementation specific changes would need to be made over time to ensure that new algorithms are correctly identified (with their options). It also provides the ability to always reject hashes made using a grossly compromised algorithm (letting password_verify work, but rejecting password_hash attempts)... | ||
+ | |||
+ | === Current Position === | ||
+ | |||
+ | The current position of this RFC is that the function is needed, and is implemented. | ||
+ | |||
+ | ==== The Existence Of PASSWORD_DEFAULT ==== | ||
+ | |||
+ | There' | ||
+ | |||
+ | === Should Have It === | ||
+ | |||
+ | The argument for the constant is that it provides the ability for code to take advantage of the most secure algorithm for the current release of PHP. This would change over time, but over a long term period of time (would only change every major release). Therefore, it would make it easier to implement code that would stay secure over a long period of time. | ||
+ | |||
+ | === Should Not Have It === | ||
+ | |||
+ | By not having the constant, developers would be forced to choose a specific algorithm at author time. This would allow them to understand the different algorithms available and make an intelligent choice. Additionally, | ||
+ | |||
+ | === Current Position === | ||
+ | |||
+ | The current position of this RFC is that the benefits of the constant relating to long term security outweigh the bad parts for the average developer. Therefore, the constant exists. | ||
+ | |||
+ | ==== password_verify() Returns FALSE On Error ==== | ||
+ | |||
+ | There' | ||
+ | |||
+ | === NULL === | ||
+ | |||
+ | The argument that password_verify should return //NULL// on a parameter parse error (invalid types, invalid numbers of types, etc) because that's the standard way PHP internal functions deal with parameter errors. For consistency it should also return //NULL//. | ||
+ | |||
+ | === FALSE === | ||
+ | |||
+ | The argument is that password_verify should always return a strict boolean type. That way, a check of //if (false === password_verify(..))// | ||
+ | |||
+ | === Current Position === | ||
+ | |||
+ | The current position is that the security context of the function justifies the break of consistency with other core functions. Therefore password_verify() currently only ever returns a boolean (never NULL). | ||
+ | |||
+ | ==== The API Does Not Support PEPPER ==== | ||
+ | |||
+ | A Pepper is similar to a salt, except that it's a unique site-wide value which is stored outside of the database. | ||
+ | |||
+ | === Should Have Pepper === | ||
+ | |||
+ | The " | ||
+ | |||
+ | === Should Not Have Pepper === | ||
+ | |||
+ | There are a few reasons we should not use peppers: | ||
+ | |||
+ | * No standard cryptographic algorithm or function accepts a unique " | ||
+ | * There are no peer-reviewed standards or research papers that indicate that using a pepper adds any significant value. | ||
+ | * Since the security value of the " | ||
+ | |||
+ | Additionally, | ||
+ | |||
+ | === Current Position === | ||
+ | |||
+ | This RFC takes the position that the core API should not directly use a pepper. | ||
===== References ===== | ===== References ===== | ||
Line 262: | Line 399: | ||
* [[http:// | * [[http:// | ||
* [[http:// | * [[http:// | ||
+ | |||
+ | ===== Vote ===== | ||
+ | |||
+ | < | ||
+ | title=" | ||
+ | * Yes | ||
+ | * No | ||
+ | </ | ||
===== Changelog ===== | ===== Changelog ===== | ||
Line 269: | Line 414: | ||
* 0.4 - Add behavioral semantics for each function | * 0.4 - Add behavioral semantics for each function | ||
* 0.5 - Remove ini directive for bcrypt cost | * 0.5 - Remove ini directive for bcrypt cost | ||
+ | * 0.6 - Make //$algo// parameter to // | ||
+ | * 0.7 - Implement password_get_info() and password_needs_rehash() | ||
+ | * 0.8 - Add discussion points section, change password_make_salt to take flag for second parameter | ||
+ | * 1.0 - Proposed | ||
+ | * 1.1 - Add pepper discussion | ||
+ | * 1.2 - Removed // | ||
+ | * 1.3 - Open Voting | ||
+ | * 1.4 - Close Voting - Moving To Accepted | ||
+ | * 1.5 - Implemented! | ||
+ | * 1.5.1 - Fine tune wording of " |
rfc/password_hash.1341315697.txt.gz · Last modified: 2017/09/22 13:28 (external edit)