diff options
Diffstat (limited to 'phpBB/phpbb/passwords/manager.php')
| -rw-r--r-- | phpBB/phpbb/passwords/manager.php | 366 | 
1 files changed, 366 insertions, 0 deletions
| diff --git a/phpBB/phpbb/passwords/manager.php b/phpBB/phpbb/passwords/manager.php new file mode 100644 index 0000000000..fbb49d86a0 --- /dev/null +++ b/phpBB/phpbb/passwords/manager.php @@ -0,0 +1,366 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords; + +class manager +{ +	/** +	* Default hashing method +	*/ +	protected $type = false; + +	/** +	* Hashing algorithm type map +	* Will be used to map hash prefix to type +	*/ +	protected $type_map = false; + +	/** +	* Service collection of hashing algorithms +	* Needs to be public for passwords helper +	*/ +	public $algorithms = false; + +	/** +	* Password convert flag. Signals that password should be converted +	*/ +	public $convert_flag = false; + +	/** +	* Passwords helper +	* @var \phpbb\passwords\helper +	*/ +	protected $helper; + +	/** +	* phpBB configuration +	* @var \phpbb\config\config +	*/ +	protected $config; + +	/** +	* Construct a passwords object +	* +	* @param \phpbb\config\config $config phpBB configuration +	* @param array $hashing_algorithms Hashing driver +	*			service collection +	* @param \phpbb\passwords\helper $helper Passwords helper object +	* @param string $defaults List of default driver types +	*/ +	public function __construct(\phpbb\config\config $config, $hashing_algorithms, helper $helper, $defaults) +	{ +		$this->config = $config; +		$this->helper = $helper; + +		$this->fill_type_map($hashing_algorithms); +		$this->register_default_type($defaults); +	} + +	/** +	* Register default type +	* Will register the first supported type from the list of default types +	* +	* @param array $defaults List of default types in order from first to +	*			use to last to use +	*/ +	protected function register_default_type($defaults) +	{ +		foreach ($defaults as $type) +		{ +			if ($this->algorithms[$type]->is_supported()) +			{ +				$this->type = $this->algorithms[$type]->get_prefix(); +				break; +			} +		} +	} + +	/** +	* Fill algorithm type map +	* +	* @param \phpbb\di\service_collection $hashing_algorithms +	*/ +	protected function fill_type_map($hashing_algorithms) +	{ +		foreach ($hashing_algorithms as $algorithm) +		{ +			if (!isset($this->type_map[$algorithm->get_prefix()])) +			{ +				$this->type_map[$algorithm->get_prefix()] = $algorithm; +			} +		} +		$this->algorithms = $hashing_algorithms; +	} + +	/** +	* Get the algorithm specified by a specific prefix +	* +	* @param string $prefix Password hash prefix +	* +	* @return object|bool The hash type object or false if prefix is not +	*			supported +	*/ +	protected function get_algorithm($prefix) +	{ +		if (isset($this->type_map[$prefix])) +		{ +			return $this->type_map[$prefix]; +		} +		else +		{ +			return false; +		} +	} + +	/** +	* Detect the hash type of the supplied hash +	* +	* @param string $hash Password hash that should be checked +	* +	* @return object|bool The hash type object or false if the specified +	*			type is not supported +	*/ +	public function detect_algorithm($hash) +	{ +		/* +		* preg_match() will also show hashing algos like $2a\H$, which +		* is a combination of bcrypt and phpass. Legacy algorithms +		* like md5 will not be matched by this and need to be treated +		* differently. +		*/ +		if (!preg_match('#^\$([a-zA-Z0-9\\\]*?)\$#', $hash, $match)) +		{ +			return false; +		} + +		// Be on the lookout for multiple hashing algorithms +		// 2 is correct: H\2a > 2, H\P > 2 +		if (strlen($match[1]) > 2) +		{ +			$hash_types = explode('\\', $match[1]); +			$return_ary = array(); +			foreach ($hash_types as $type) +			{ +				// we do not support the same hashing +				// algorithm more than once +				if (isset($return_ary[$type])) +				{ +					return false; +				} + +				$return_ary[$type] = $this->get_algorithm('$' . $type . '$'); + +				if (empty($return_ary[$type])) +				{ +					return false; +				} +			} +			return $return_ary; +		} + +		// get_algorithm() will automatically return false if prefix +		// is not supported +		return $this->get_algorithm($match[0]); +	} + +	/** +	* Hash supplied password +	* +	* @param string $password Password that should be hashed +	* @param string $type Hash type. Will default to standard hash type if +	*			none is supplied +	* @return string|bool Password hash of supplied password or false if +	*			if something went wrong during hashing +	*/ +	public function hash($password, $type = '') +	{ +		if (strlen($password) > 4096) +		{ +			// If the password is too huge, we will simply reject it +			// and not let the server try to hash it. +			return false; +		} + +		// Try to retrieve algorithm by service name if type doesn't +		// start with dollar sign +		if (!is_array($type) && strpos($type, '$') !== 0 && isset($this->algorithms[$type])) +		{ +			$type = $this->algorithms[$type]->get_prefix(); +		} + +		$type = ($type === '') ? $this->type : $type; + +		if (is_array($type)) +		{ +			return $this->combined_hash_password($password, $type); +		} + +		if (isset($this->type_map[$type])) +		{ +			$hashing_algorithm = $this->type_map[$type]; +		} +		else +		{ +			return false; +		} + +		return $hashing_algorithm->hash($password); +	} + +	/** +	* Check supplied password against hash and set convert_flag if password +	* needs to be converted to different format (preferrably newer one) +	* +	* @param string $password Password that should be checked +	* @param string $hash Stored hash +	* @param array	$user_row User's row in users table +	* @return string|bool True if password is correct, false if not +	*/ +	public function check($password, $hash, $user_row = array()) +	{ +		if (strlen($password) > 4096) +		{ +			// If the password is too huge, we will simply reject it +			// and not let the server try to hash it. +			return false; +		} + +		// Empty hashes can't be checked +		if (empty($hash)) +		{ +			return false; +		} + +		// First find out what kind of hash we're dealing with +		$stored_hash_type = $this->detect_algorithm($hash); +		if ($stored_hash_type == false) +		{ +			// Still check MD5 hashes as that is what the installer +			// will default to for the admin user +			return $this->get_algorithm('$H$')->check($password, $hash); +		} + +		// Multiple hash passes needed +		if (is_array($stored_hash_type)) +		{ +			$correct = $this->check_combined_hash($password, $stored_hash_type, $hash); +			$this->convert_flag = ($correct === true) ? true : false; +			return $correct; +		} + +		if ($stored_hash_type->get_prefix() !== $this->type) +		{ +			$this->convert_flag = true; +		} +		else +		{ +			$this->convert_flag = false; +		} + +		// Check all legacy hash types if prefix is $CP$ +		if ($stored_hash_type->get_prefix() === '$CP$') +		{ +			// Remove $CP$ prefix for proper checking +			$hash = substr($hash, 4); + +			foreach ($this->type_map as $algorithm) +			{ +				if ($algorithm->is_legacy() && $algorithm->check($password, $hash, $user_row) === true) +				{ +					return true; +				} +			} +		} + +		return $stored_hash_type->check($password, $hash); +	} + +	/** +	* Create combined hash from already hashed password +	* +	* @param string $password_hash Complete current password hash +	* @param string $type Type of the hashing algorithm the password hash +	*		should be combined with +	* @return string|bool Combined password hash if combined hashing was +	*		successful, else false +	*/ +	public function combined_hash_password($password_hash, $type) +	{ +		$data = array( +			'prefix' => '$', +			'settings' => '$', +		); +		$hash_settings = $this->helper->get_combined_hash_settings($password_hash); +		$hash = $hash_settings[0]; + +		// Put settings of current hash into data array +		$stored_hash_type = $this->detect_algorithm($password_hash); +		$this->helper->combine_hash_output($data, 'prefix', $stored_hash_type->get_prefix()); +		$this->helper->combine_hash_output($data, 'settings', $stored_hash_type->get_settings_only($password_hash)); + +		// Hash current hash with the defined types +		foreach ($type as $cur_type) +		{ +			if (isset($this->algorithms[$cur_type])) +			{ +				$new_hash_type = $this->algorithms[$cur_type]; +			} +			else +			{ +				$new_hash_type = $this->get_algorithm($cur_type); +			} + +			if (!$new_hash_type) +			{ +				return false; +			} + +			$new_hash = $new_hash_type->hash(str_replace($stored_hash_type->get_settings_only($password_hash), '', $hash)); +			$this->helper->combine_hash_output($data, 'prefix', $new_hash_type->get_prefix()); +			$this->helper->combine_hash_output($data, 'settings', substr(str_replace('$', '\\', $new_hash_type->get_settings_only($new_hash, true)), 0)); +			$hash = str_replace($new_hash_type->get_settings_only($new_hash), '', $this->helper->obtain_hash_only($new_hash)); +		} +		return $this->helper->combine_hash_output($data, 'hash', $hash); +	} + +	/** +	* Check combined password hash against the supplied password +	* +	* @param string $password Password entered by user +	* @param array $stored_hash_type An array containing the hash types +	*				as described by stored password hash +	* @param string $hash Stored password hash +	* +	* @return bool True if password is correct, false if not +	*/ +	public function check_combined_hash($password, $stored_hash_type, $hash) +	{ +		$i = 0; +		$data = array( +			'prefix' => '$', +			'settings' => '$', +		); +		$hash_settings = $this->helper->get_combined_hash_settings($hash); +		foreach ($stored_hash_type as $key => $hash_type) +		{ +			$rebuilt_hash = $this->helper->rebuild_hash($hash_type->get_prefix(), $hash_settings[$i]); +			$this->helper->combine_hash_output($data, 'prefix', $key); +			$this->helper->combine_hash_output($data, 'settings', $hash_settings[$i]); +			$cur_hash = $hash_type->hash($password, $rebuilt_hash); +			$password = str_replace($rebuilt_hash, '', $cur_hash); +			$i++; +		} +		return ($hash === $this->helper->combine_hash_output($data, 'hash', $password)); +	} +} | 
