rfc:password_hash

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:password_hash [2012/07/09 15:17] – Implement password_get_info() and password_needs_rehash() ircmaxellrfc: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: 0.7+  * Version: 1.4
   * Date: 2012-06-26   * Date: 2012-06-26
   * Author: Anthony Ferrara <ircmaxell@php.net>   * Author: Anthony Ferrara <ircmaxell@php.net>
-  * Status: Draft+  * Status: Implemented
   * First Published at: http://wiki.php.net/rfc/password_hash   * First Published at: http://wiki.php.net/rfc/password_hash
  
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, 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 [[remote timing attacks|http://en.wikipedia.org/wiki/Timing_attack]].+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|remote timing attacks]].
  
 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 in a system. There is no requirement for them 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 statistically globally unique. There is no requirement for them to be true random (as you would need for an encryption key). This means that ///dev/urandom// is acceptable, while //mt_rand()// and //rand()// are not (except as fallbacks).
  
 ==== Hash(password + salt) Is Fine ==== ==== Hash(password + salt) Is Fine ====
Line 44: Line 43:
   * //string password_hash(string $password, int $algo, array $options = array())// - The function which creates new password hashes. The second parameter //algo// indicates which algorithm should be used to execute the hash. You can use the default constant if you want the algorithm to automatically update itself to the strongest algorithm available as PHP is upgraded. If called with two parameters, it will auto-generate a salt. 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).   * //string password_hash(string $password, int $algo, array $options = array())// - The function which creates new password hashes. The second parameter //algo// indicates which algorithm should be used to execute the hash. You can use the default constant if you want the algorithm to automatically update itself to the strongest algorithm available as PHP is upgraded. If called with two parameters, it will auto-generate a salt. 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.   * //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//. 
   * //bool password_needs_rehash(string $hash, int $algo, array $options = array())// - This function checks to see if the supplied hash implements the algorithm and options provided. If not, it is assumed that the hash needs to be rehashed.   * //bool password_needs_rehash(string $hash, int $algo, array $options = array())// - This function checks to see if the supplied hash implements the algorithm and options provided. If not, it is assumed that the hash needs to be rehashed.
   * //array password_get_info(string $hash)// - This function gets the information used to generate a hash. The returned array has two keys, algo and options.   * //array password_get_info(string $hash)// - This function gets the information used to generate a hash. The returned array has two keys, algo and options.
Line 50: Line 48:
 ==== New Constants ==== ==== New Constants ====
  
-Initially, two constants are defined:+Initially, several new constants are defined:
  
-  * //PASSWORD_BCRYPT = "2y"// - Create new password hashes using the //CRYPT_BLOWFISH// algorithm +  * //PASSWORD_BCRYPT = 1// - 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).+  * //PASSWORD_DEFAULT = PASSWORD_BCRYPT// - The default algorithm to use for hashing if no algorithm is provided. This can change in future releases when a new, stronger hashing algorithm (such as //scrypt//is supported.
  
 ==== Supported Algorithms ==== ==== Supported Algorithms ====
Line 105: Line 103:
  
 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_hash()//. 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_hash()//.
- 
-=== password_make_salt() === 
- 
-Errors: 
-  * E_WARNING - When supplied an incorrect number of parameters. 
-  * E_WARNING - If the length parameter is less than or equal to zero 
-  * E_WARNING - If the length parameter is greater than //PHP_INT_MAX / 3// (needed to ensure safe allocations) 
- 
-Additionally, an E_WARNING error can be thrown if the generated salt is too short to encode fully. This should never happen, and is just a sanity check to prevent an inconsistent state due to a failure in other parts of the system. 
- 
-On error, it will return false; 
- 
-Normal Operation: 
- 
-When the raw_output parameter is false (default), the function will generate a string of the specified length consisting of random characters from the alphabet //a-zA-Z0-9/.//. When raw_output is true, the function will generate a string of the specified length consisting of random bytes (characters 0 - 255). 
- 
-It will use non-cryptographically safe, but strong random entropy sources, if possible for the salt generation. On windows, it will use //php_win32_get_random_bytes()//. On other platforms, it will read from /dev/urandom. If neither can generate enough entropy for the request, it will fall back to using //php_rand()// to supplement the provided randomness (it xor-s the php_rand() value with the existing one). 
  
 === password_get_info() === === password_get_info() ===
Line 237: Line 218:
 </file> </file>
  
-===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, true); 
-?> 
-</file> 
 ==== Possible Future Implementation Details ==== ==== Possible Future Implementation Details ====
  
Line 281: Line 252:
   - The default algorithm is specified by a constant //PASSWORD_DEFAULT//. As new and stronger algorithms are added, this constant can be updated to point to the strongest at the time.    - The default algorithm is specified by a constant //PASSWORD_DEFAULT//. As new and stronger algorithms are added, this constant can be updated to point to the strongest at the time. 
   - 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 value (if not set in PHP.ini) can be updated in the source from release to release to compensate for faster hardware.   - 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 value (if not set in PHP.ini) can be updated in the source from release to release to compensate for faster hardware.
 +  - If an implemented algorithm is ever grossly compromised (to the point of uselessness), password_hash() can be changed to either reject creation of new hashes for that algorithm, or raise warnings to notify users about said problems... Additionally, password_needs_rehash() can be changed to always return true for the compromised algorithm.
 +
 +===== 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 have a default setting" argument is that it makes the API easier to use. All you would need to do is //password_hash($password)// to safely hash a password. The default would be updated according to the "Updating PASSWORD_DEFAULT" guidelines above. The API would become //string password_hash(string $password, int $algo = PASSWORD_DEFAULT, array $options = array())//
 +
 +=== 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 "should not have a default" argument. Therefore, the function has a required second argument.
 +
 +==== password_make_salt() Is Not Needed ====
 +
 +There has also been discussion around whether or not //password_make_salt()// should be exposed to user-land. 
 +
 +=== 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 //mcrypt_create_iv()// and //base64_encode()//. Therefore, its existence is not really necessary.
 +
 +=== 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(), so it should be exposed so that it can be used for other things in userland. One of these other uses is that the format for the string (a-zA-Z0-9./) is already correct for //crypt()//. Therefore, a single function call can create salts for the other crypt() algorithms. Whereas to safely create them now requires a combination of at least 3 function calls.
 +
 +=== Current Position ===
 +
 +The current position of this RFC sides with the "should not be exposed" argument. The function has been removed from the proposal.
 +
 +==== password_needs_rehash() is not needed ====
 +
 +The function //password_needs_rehash()// can be implemented in user-land with the information returned by //password_get_info()//.
 +
 +=== 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's been some discussion around the existence of the PASSWORD_DEFAULT constant.
 +
 +=== 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, it would prevent migration issues that could be caused by a changing algorithm (storage requirements, etc).
 +
 +=== 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's been discussion about the return value on parameter parse errors.
 +
 +=== 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(..))// would not accidentally return a false condition when the password was not verified. As such, it would become possible for password_verify() to return falsy, but non-false results when it did not successfully verify the password hash.
 +
 +=== 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 have" pepper argument is that it provides an added level of defense in case a database with salts and hashes is leaked.
 +
 +=== Should Not Have Pepper ===
 +
 +There are a few reasons we should not use peppers:
 +
 +  * No standard cryptographic algorithm or function accepts a unique "pepper" argument.
 +  * 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 "pepper" is in its secrecy, it becomes a cryptographic secret. PHP variables are not conducive to holding secrets (in that they cannot be cleared or overwritten directly).
 +
 +Additionally, the same benefit can be had by encrypting the hash using the secret "pepper" value prior to storage. In practice this will be the better alternative (although for most use-cases not necessary) because it uses standard algorithms with correct inputs for them.
 +
 +=== Current Position ===
 +
 +This RFC takes the position that the core API should not directly use a pepper.
  
 ===== References =====  ===== References ===== 
Line 323: Line 399:
   * [[http://en.wikipedia.org/wiki/PBKDF2|PBKDF2]]   * [[http://en.wikipedia.org/wiki/PBKDF2|PBKDF2]]
   * [[http://www.tarsnap.com/scrypt.html|SCrypt]]   * [[http://www.tarsnap.com/scrypt.html|SCrypt]]
 +
 +===== Vote =====
 +
 +<doodle 
 +title="Should the simplified password hashing API indicated here be included in master?" auth="ircmaxell" voteType="single" closed="true">
 +   * Yes
 +   * No
 +</doodle>
  
 ===== Changelog ===== ===== Changelog =====
Line 332: Line 416:
   * 0.6 - Make //$algo// parameter to //password_hash()// no longer optional   * 0.6 - Make //$algo// parameter to //password_hash()// no longer optional
   * 0.7 - Implement password_get_info() and password_needs_rehash()   * 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 //password_make_salt()// function from proposal
 +  * 1.3 - Open Voting
 +  * 1.4 - Close Voting - Moving To Accepted
 +  * 1.5 - Implemented!
 +  * 1.5.1 - Fine tune wording of "Cryptographically Secure Salt Requirement"
rfc/password_hash.1341847071.txt.gz · Last modified: 2017/09/22 13:28 (external edit)