|
| 1 | +<?php |
| 2 | +namespace Mdespeuilles\DrupalPasswordEncoderBundle\Services; |
| 3 | + |
| 4 | +use Mdespeuilles\DrupalPasswordEncoderBundle\Services\Password\PhpassHashedPassword; |
| 5 | +use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; |
| 6 | + |
| 7 | +class DrupalPasswordEncoder implements PasswordEncoderInterface |
| 8 | +{ |
| 9 | + const DRUPAL_HASH_COUNT = 15; |
| 10 | + |
| 11 | + protected $drupalPasswordService; |
| 12 | + |
| 13 | + public function __construct() |
| 14 | + { |
| 15 | + $this->drupalPasswordService = new PhpassHashedPassword(self::DRUPAL_HASH_COUNT); |
| 16 | + } |
| 17 | + |
| 18 | + public function encodePassword($password, $salt) |
| 19 | + { |
| 20 | + return $this->drupalPasswordService->hash($password); |
| 21 | + //return $this->_password_crypt('sha512', $password, $this->_password_generate_salt(self::DRUPAL_HASH_COUNT)); |
| 22 | + } |
| 23 | + |
| 24 | + public function isPasswordValid($encoded, $raw, $salt) |
| 25 | + { |
| 26 | + return $this->drupalPasswordService->check($raw, $encoded); |
| 27 | + //return $this->user_check_password($raw, $encoded); |
| 28 | + } |
| 29 | + |
| 30 | + private function _password_generate_salt($count_log2) { |
| 31 | + $output = '$S$'; |
| 32 | + // We encode the final log2 iteration count in base 64. |
| 33 | + $itoa64 = $this->_password_itoa64(); |
| 34 | + $output .= $itoa64[$count_log2]; |
| 35 | + // 6 bytes is the standard salt for a portable phpass hash. |
| 36 | + $output .= $this->_password_base64_encode($this->drupal_random_bytes(6), 6); |
| 37 | + return $output; |
| 38 | + } |
| 39 | + |
| 40 | + private function _password_itoa64() { |
| 41 | + return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; |
| 42 | + } |
| 43 | + |
| 44 | + private function _password_base64_encode($input, $count) { |
| 45 | + $output = ''; |
| 46 | + $i = 0; |
| 47 | + $itoa64 = $this->_password_itoa64(); |
| 48 | + do { |
| 49 | + $value = ord($input[$i++]); |
| 50 | + $output .= $itoa64[$value & 0x3f]; |
| 51 | + if ($i < $count) { |
| 52 | + $value |= ord($input[$i]) << 8; |
| 53 | + } |
| 54 | + $output .= $itoa64[($value >> 6) & 0x3f]; |
| 55 | + if ($i++ >= $count) { |
| 56 | + break; |
| 57 | + } |
| 58 | + if ($i < $count) { |
| 59 | + $value |= ord($input[$i]) << 16; |
| 60 | + } |
| 61 | + $output .= $itoa64[($value >> 12) & 0x3f]; |
| 62 | + if ($i++ >= $count) { |
| 63 | + break; |
| 64 | + } |
| 65 | + $output .= $itoa64[($value >> 18) & 0x3f]; |
| 66 | + } while ($i < $count); |
| 67 | + |
| 68 | + return $output; |
| 69 | + } |
| 70 | + |
| 71 | + private function drupal_random_bytes($count) { |
| 72 | + // $random_state does not use drupal_static as it stores random bytes. |
| 73 | + static $random_state, $bytes, $has_openssl; |
| 74 | + |
| 75 | + $missing_bytes = $count - strlen($bytes); |
| 76 | + |
| 77 | + if ($missing_bytes > 0) { |
| 78 | + // PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes() |
| 79 | + // locking on Windows and rendered it unusable. |
| 80 | + if (!isset($has_openssl)) { |
| 81 | + $has_openssl = version_compare(PHP_VERSION, '5.3.4', '>=') && function_exists('openssl_random_pseudo_bytes'); |
| 82 | + } |
| 83 | + |
| 84 | + // openssl_random_pseudo_bytes() will find entropy in a system-dependent |
| 85 | + // way. |
| 86 | + if ($has_openssl) { |
| 87 | + $bytes .= openssl_random_pseudo_bytes($missing_bytes); |
| 88 | + } |
| 89 | + |
| 90 | + // Else, read directly from /dev/urandom, which is available on many *nix |
| 91 | + // systems and is considered cryptographically secure. |
| 92 | + elseif ($fh = @fopen('/dev/urandom', 'rb')) { |
| 93 | + // PHP only performs buffered reads, so in reality it will always read |
| 94 | + // at least 4096 bytes. Thus, it costs nothing extra to read and store |
| 95 | + // that much so as to speed any additional invocations. |
| 96 | + $bytes .= fread($fh, max(4096, $missing_bytes)); |
| 97 | + fclose($fh); |
| 98 | + } |
| 99 | + |
| 100 | + // If we couldn't get enough entropy, this simple hash-based PRNG will |
| 101 | + // generate a good set of pseudo-random bytes on any system. |
| 102 | + // Note that it may be important that our $random_state is passed |
| 103 | + // through hash() prior to being rolled into $output, that the two hash() |
| 104 | + // invocations are different, and that the extra input into the first one - |
| 105 | + // the microtime() - is prepended rather than appended. This is to avoid |
| 106 | + // directly leaking $random_state via the $output stream, which could |
| 107 | + // allow for trivial prediction of further "random" numbers. |
| 108 | + if (strlen($bytes) < $count) { |
| 109 | + // Initialize on the first call. The contents of $_SERVER includes a mix of |
| 110 | + // user-specific and system information that varies a little with each page. |
| 111 | + if (!isset($random_state)) { |
| 112 | + $random_state = print_r($_SERVER, TRUE); |
| 113 | + if (function_exists('getmypid')) { |
| 114 | + // Further initialize with the somewhat random PHP process ID. |
| 115 | + $random_state .= getmypid(); |
| 116 | + } |
| 117 | + $bytes = ''; |
| 118 | + } |
| 119 | + |
| 120 | + do { |
| 121 | + $random_state = hash('sha256', microtime() . mt_rand() . $random_state); |
| 122 | + $bytes .= hash('sha256', mt_rand() . $random_state, TRUE); |
| 123 | + } |
| 124 | + while (strlen($bytes) < $count); |
| 125 | + } |
| 126 | + } |
| 127 | + $output = substr($bytes, 0, $count); |
| 128 | + $bytes = substr($bytes, $count); |
| 129 | + return $output; |
| 130 | + } |
| 131 | + |
| 132 | + private function _password_get_count_log2($setting) { |
| 133 | + $itoa64 = $this->_password_itoa64(); |
| 134 | + return strpos($itoa64, $setting[3]); |
| 135 | + } |
| 136 | + |
| 137 | + private function user_check_password($password, $hash) { |
| 138 | + if (substr($hash, 0, 2) == 'U$') { |
| 139 | + // This may be an updated password from user_update_7000(). Such hashes |
| 140 | + // have 'U' added as the first character and need an extra md5(). |
| 141 | + $stored_hash = substr($hash, 1); |
| 142 | + $password = md5($password); |
| 143 | + } |
| 144 | + else { |
| 145 | + $stored_hash = $hash; |
| 146 | + } |
| 147 | + |
| 148 | + $type = substr($stored_hash, 0, 3); |
| 149 | + switch ($type) { |
| 150 | + case '$S$': |
| 151 | + // A normal Drupal 7 password using sha512. |
| 152 | + $hash = $this->_password_crypt('sha512', $password, $stored_hash); |
| 153 | + break; |
| 154 | + case '$H$': |
| 155 | + // phpBB3 uses "$H$" for the same thing as "$P$". |
| 156 | + case '$P$': |
| 157 | + // A phpass password generated using md5. This is an |
| 158 | + // imported password or from an earlier Drupal version. |
| 159 | + $hash = $this->_password_crypt('md5', $password, $stored_hash); |
| 160 | + break; |
| 161 | + default: |
| 162 | + return FALSE; |
| 163 | + } |
| 164 | + return ($hash && $stored_hash == $hash); |
| 165 | + } |
| 166 | + |
| 167 | + private function _password_crypt($algo, $password, $setting) { |
| 168 | + // Prevent DoS attacks by refusing to hash large passwords. |
| 169 | + if (strlen($password) > 512) { |
| 170 | + return FALSE; |
| 171 | + } |
| 172 | + // The first 12 characters of an existing hash are its setting string. |
| 173 | + $setting = substr($setting, 0, 12); |
| 174 | + |
| 175 | + if ($setting[0] != '$' || $setting[2] != '$') { |
| 176 | + return FALSE; |
| 177 | + } |
| 178 | + $count_log2 = $this->_password_get_count_log2($setting); |
| 179 | + // Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT |
| 180 | + if ($count_log2 < 7 || $count_log2 > 30) { |
| 181 | + return FALSE; |
| 182 | + } |
| 183 | + $salt = substr($setting, 4, 8); |
| 184 | + // Hashes must have an 8 character salt. |
| 185 | + if (strlen($salt) != 8) { |
| 186 | + return FALSE; |
| 187 | + } |
| 188 | + |
| 189 | + // Convert the base 2 logarithm into an integer. |
| 190 | + $count = 1 << $count_log2; |
| 191 | + |
| 192 | + // We rely on the hash() function being available in PHP 5.2+. |
| 193 | + $hash = hash($algo, $salt . $password, TRUE); |
| 194 | + do { |
| 195 | + $hash = hash($algo, $hash . $password, TRUE); |
| 196 | + } while (--$count); |
| 197 | + |
| 198 | + $len = strlen($hash); |
| 199 | + $output = $setting . $this->_password_base64_encode($hash, $len); |
| 200 | + // _password_base64_encode() of a 16 byte MD5 will always be 22 characters. |
| 201 | + // _password_base64_encode() of a 64 byte sha512 will always be 86 characters. |
| 202 | + $expected = 12 + ceil((8 * $len) / 6); |
| 203 | + return (strlen($output) == $expected) ? substr($output, 0, 55) : FALSE; |
| 204 | + } |
| 205 | +} |
0 commit comments