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/12 02:36] – Add discussion points section, change password_make_salt to take flag for second parameter 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.8+  * 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, int $salt_type = PASSWORD_SALT_BCRYPT)// - 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 53: Line 51:
  
   * //PASSWORD_BCRYPT = 1// - 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.
-  * //PASSWORD_SALT_RAW = 1// - When passed to password_make_salt(), it returns a raw output (0-255). +
-  * //PASSWORD_SALT_BCRYPT = 2// - When passed to password_make_salt(), it returns output compatible with crypt() (base64 encoded with the alphabet a-zA-Z0-9./).+
  
 ==== Supported Algorithms ==== ==== Supported Algorithms ====
Line 107: 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 salt_type parameter is //PASSWORD_SALT_BCRYPT// (default), the function will generate a string of the specified length consisting of random characters from the alphabet //a-zA-Z0-9/.//. When it is //PASSWORD_SALT_RAW//, 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 239: 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 317: Line 286:
 === Current Position === === Current Position ===
  
-The current position of this RFC sides with the "should be exposed" argument. Therefore, the function is exposed to PHP user land code. +The current position of this RFC sides with the "should not be exposed" argument. The function has been removed from the proposal.
- +
-==== password_make_salt()'s second parameter should be an integer flag ==== +
- +
-There has been discussion around the second parameter of //password_make_salt()// (the raw_output) and that it should take a flag to allow for future improvements to add new salt formats other than base64 and raw.  +
- +
-=== Should Be A Flag === +
- +
-The "Should be a flag" argument is based around the future compatibility argument. Future versions of algorithms implemented by //password_hash()// may require different alphabet salts. By taking a flag instead of a boolean, this can be handled transparently by introducing new constants. +
- +
-=== Should Be A Boolean === +
- +
-Currently the output type of //password_make_salt()// is compatible with all //crypt()// methods. The chances that a new format will need to be introduced is rather slim. Additionally, by making it a boolean makes the API simpler. +
- +
-=== Current Position === +
- +
-The current position of this RFC is that the parameter should be a flag to allow for future compatibility with unknown requirements.+
  
 ==== password_needs_rehash() is not needed ==== ==== password_needs_rehash() is not needed ====
Line 351: Line 304:
 The current position of this RFC is that the function is needed, and is implemented. The current position of this RFC is that the function is needed, and is implemented.
  
-=== The Existence Of PASSWORD_DEFAULT ===+==== The Existence Of PASSWORD_DEFAULT ====
  
 There's been some discussion around the existence of the PASSWORD_DEFAULT constant. There's been some discussion around the existence of the PASSWORD_DEFAULT constant.
Line 382: Line 335:
  
 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 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 424: 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 434: Line 417:
   * 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   * 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.1342060575.txt.gz · Last modified: 2017/09/22 13:28 (external edit)