"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.10/core/lib/Drupal/Core/Password/PhpassHashedPassword.php" (26 Nov 2020, 8607 Bytes) of package /linux/www/drupal-8.9.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "PhpassHashedPassword.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 namespace Drupal\Core\Password;
    4 
    5 /**
    6  * Secure password hashing functions based on the Portable PHP password
    7  * hashing framework.
    8  *
    9  * @see http://www.openwall.com/phpass/
   10  */
   11 class PhpassHashedPassword implements PasswordInterface {
   12   /**
   13    * The minimum allowed log2 number of iterations for password stretching.
   14    */
   15   const MIN_HASH_COUNT = 7;
   16 
   17   /**
   18    * The maximum allowed log2 number of iterations for password stretching.
   19    */
   20   const MAX_HASH_COUNT = 30;
   21 
   22   /**
   23    * The expected (and maximum) number of characters in a hashed password.
   24    */
   25   const HASH_LENGTH = 55;
   26 
   27   /**
   28    * Returns a string for mapping an int to the corresponding base 64 character.
   29    *
   30    * @var string
   31    */
   32   public static $ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
   33 
   34   /**
   35    * Specifies the number of times the hashing function will be applied when
   36    * generating new password hashes. The number of times is calculated by
   37    * raising 2 to the power of the given value.
   38    *
   39    * @var int
   40    */
   41   protected $countLog2;
   42 
   43   /**
   44    * Constructs a new password hashing instance.
   45    *
   46    * @param int $countLog2
   47    *   Password stretching iteration count. Specifies the number of times the
   48    *   hashing function will be applied when generating new password hashes.
   49    *   The number of times is calculated by raising 2 to the power of the given
   50    *   value.
   51    */
   52   public function __construct($countLog2) {
   53     // Ensure that $countLog2 is within set bounds.
   54     $this->countLog2 = $this->enforceLog2Boundaries($countLog2);
   55   }
   56 
   57   /**
   58    * Encodes bytes into printable base 64 using the *nix standard from crypt().
   59    *
   60    * @param string $input
   61    *   The string containing bytes to encode.
   62    * @param int $count
   63    *   The number of characters (bytes) to encode.
   64    *
   65    * @return string
   66    *   Encoded string.
   67    */
   68   protected function base64Encode($input, $count) {
   69     $output = '';
   70     $i = 0;
   71     do {
   72       $value = ord($input[$i++]);
   73       $output .= static::$ITOA64[$value & 0x3f];
   74       if ($i < $count) {
   75         $value |= ord($input[$i]) << 8;
   76       }
   77       $output .= static::$ITOA64[($value >> 6) & 0x3f];
   78       if ($i++ >= $count) {
   79         break;
   80       }
   81       if ($i < $count) {
   82         $value |= ord($input[$i]) << 16;
   83       }
   84       $output .= static::$ITOA64[($value >> 12) & 0x3f];
   85       if ($i++ >= $count) {
   86         break;
   87       }
   88       $output .= static::$ITOA64[($value >> 18) & 0x3f];
   89     } while ($i < $count);
   90 
   91     return $output;
   92   }
   93 
   94   /**
   95    * Generates a random base 64-encoded salt prefixed with hash settings.
   96    *
   97    * Proper use of salts may defeat a number of attacks, including:
   98    *  - The ability to try candidate passwords against multiple hashes at once.
   99    *  - The ability to use pre-hashed lists of candidate passwords.
  100    *  - The ability to determine whether two users have the same (or different)
  101    *    password without actually having to guess one of the passwords.
  102    *
  103    * @return string
  104    *   A 12 character string containing the iteration count and a random salt.
  105    */
  106   protected function generateSalt() {
  107     $output = '$S$';
  108     // We encode the final log2 iteration count in base 64.
  109     $output .= static::$ITOA64[$this->countLog2];
  110     // 6 bytes is the standard salt for a portable phpass hash.
  111     $output .= $this->base64Encode(random_bytes(6), 6);
  112     return $output;
  113   }
  114 
  115   /**
  116    * Ensures that $count_log2 is within set bounds.
  117    *
  118    * @param int $count_log2
  119    *   Integer that determines the number of iterations used in the hashing
  120    *   process. A larger value is more secure, but takes more time to complete.
  121    *
  122    * @return int
  123    *   Integer within set bounds that is closest to $count_log2.
  124    */
  125   protected function enforceLog2Boundaries($count_log2) {
  126     if ($count_log2 < static::MIN_HASH_COUNT) {
  127       return static::MIN_HASH_COUNT;
  128     }
  129     elseif ($count_log2 > static::MAX_HASH_COUNT) {
  130       return static::MAX_HASH_COUNT;
  131     }
  132 
  133     return (int) $count_log2;
  134   }
  135 
  136   /**
  137    * Hash a password using a secure stretched hash.
  138    *
  139    * By using a salt and repeated hashing the password is "stretched". Its
  140    * security is increased because it becomes much more computationally costly
  141    * for an attacker to try to break the hash by brute-force computation of the
  142    * hashes of a large number of plain-text words or strings to find a match.
  143    *
  144    * @param string $algo
  145    *   The string name of a hashing algorithm usable by hash(), like 'sha256'.
  146    * @param string $password
  147    *   Plain-text password up to 512 bytes (128 to 512 UTF-8 characters) to
  148    *   hash.
  149    * @param string $setting
  150    *   An existing hash or the output of $this->generateSalt(). Must be at least
  151    *   12 characters (the settings and salt).
  152    *
  153    * @return string
  154    *   A string containing the hashed password (and salt) or FALSE on failure.
  155    *   The return string will be truncated at HASH_LENGTH characters max.
  156    */
  157   protected function crypt($algo, $password, $setting) {
  158     // Prevent DoS attacks by refusing to hash large passwords.
  159     if (strlen($password) > PasswordInterface::PASSWORD_MAX_LENGTH) {
  160       return FALSE;
  161     }
  162 
  163     // The first 12 characters of an existing hash are its setting string.
  164     $setting = substr($setting, 0, 12);
  165 
  166     if ($setting[0] != '$' || $setting[2] != '$') {
  167       return FALSE;
  168     }
  169     $count_log2 = $this->getCountLog2($setting);
  170     // Stored hashes may have been crypted with any iteration count. However we
  171     // do not allow applying the algorithm for unreasonable low and high values
  172     // respectively.
  173     if ($count_log2 != $this->enforceLog2Boundaries($count_log2)) {
  174       return FALSE;
  175     }
  176     $salt = substr($setting, 4, 8);
  177     // Hashes must have an 8 character salt.
  178     if (strlen($salt) != 8) {
  179       return FALSE;
  180     }
  181 
  182     // Convert the base 2 logarithm into an integer.
  183     $count = 1 << $count_log2;
  184 
  185     $hash = hash($algo, $salt . $password, TRUE);
  186     do {
  187       $hash = hash($algo, $hash . $password, TRUE);
  188     } while (--$count);
  189 
  190     $len = strlen($hash);
  191     $output = $setting . $this->base64Encode($hash, $len);
  192     // $this->base64Encode() of a 16 byte MD5 will always be 22 characters.
  193     // $this->base64Encode() of a 64 byte sha512 will always be 86 characters.
  194     $expected = 12 + ceil((8 * $len) / 6);
  195     return (strlen($output) == $expected) ? substr($output, 0, static::HASH_LENGTH) : FALSE;
  196   }
  197 
  198   /**
  199    * Parses the log2 iteration count from a stored hash or setting string.
  200    *
  201    * @param string $setting
  202    *   An existing hash or the output of $this->generateSalt(). Must be at least
  203    *   12 characters (the settings and salt).
  204    *
  205    * @return int
  206    *   The log2 iteration count.
  207    */
  208   public function getCountLog2($setting) {
  209     return strpos(static::$ITOA64, $setting[3]);
  210   }
  211 
  212   /**
  213    * {@inheritdoc}
  214    */
  215   public function hash($password) {
  216     return $this->crypt('sha512', $password, $this->generateSalt());
  217   }
  218 
  219   /**
  220    * {@inheritdoc}
  221    */
  222   public function check($password, $hash) {
  223     if (substr($hash, 0, 2) == 'U$') {
  224       // This may be an updated password from user_update_7000(). Such hashes
  225       // have 'U' added as the first character and need an extra md5() (see the
  226       // Drupal 7 documentation).
  227       $stored_hash = substr($hash, 1);
  228       $password = md5($password);
  229     }
  230     else {
  231       $stored_hash = $hash;
  232     }
  233 
  234     $type = substr($stored_hash, 0, 3);
  235     switch ($type) {
  236       case '$S$':
  237         // A normal Drupal 7 password using sha512.
  238         $computed_hash = $this->crypt('sha512', $password, $stored_hash);
  239         break;
  240 
  241       case '$H$':
  242         // phpBB3 uses "$H$" for the same thing as "$P$".
  243       case '$P$':
  244         // A phpass password generated using md5.  This is an
  245         // imported password or from an earlier Drupal version.
  246         $computed_hash = $this->crypt('md5', $password, $stored_hash);
  247         break;
  248 
  249       default:
  250         return FALSE;
  251     }
  252 
  253     // Compare using hash_equals() instead of === to mitigate timing attacks.
  254     return $computed_hash && hash_equals($stored_hash, $computed_hash);
  255   }
  256 
  257   /**
  258    * {@inheritdoc}
  259    */
  260   public function needsRehash($hash) {
  261     // Check whether this was an updated password.
  262     if ((substr($hash, 0, 3) != '$S$') || (strlen($hash) != static::HASH_LENGTH)) {
  263       return TRUE;
  264     }
  265     // Ensure that $count_log2 is within set bounds.
  266     $count_log2 = $this->enforceLog2Boundaries($this->countLog2);
  267     // Check whether the iteration count used differs from the standard number.
  268     return ($this->getCountLog2($hash) !== $count_log2);
  269   }
  270 
  271 }