aboutsummaryrefslogtreecommitdiffstats
path: root/phpBB/includes/core/security.php
blob: f5aca65e8d9202e6f967d3d7719de7ea7af282cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
<?php
/**
*
* @package core
* @version $Id$
* @copyright (c) 2008 phpBB Group
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*
*/

/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
	exit();
}
/**
* Class for generating random numbers, unique ids, unique keys, seeds, hashes...
* @package core
*/
class phpbb_security extends phpbb_plugin_support
{
	/**
	* @var array required phpBB objects
	*/
	public $phpbb_required = array();

	/**
	* @var array Optional phpBB objects
	*/
	public $phpbb_optional = array('config');

	/**
	* @var string Used hash type. The default type is $P$, phpBB uses a different one.
	*/
	public $hash_type = '$H$';

	/**
	* @var bool Is true if random seed got updated.
	*/
	private $dss_seeded = false;

	/**
	* Constructor
	* @access public
	*/
	public function __construct() {}

	/**
	* Generates an alphanumeric random string of given length
	*
	* @param int	$num_chars	Number of characters to return
	* @return string	Random string of $num_chars characters.
	* @access public
	*/
	public function gen_rand_string($num_chars = 8)
	{
		$rand_str = $this->unique_id();
		$rand_str = str_replace('0', 'Z', strtoupper(base_convert($rand_str, 16, 35)));

		return substr($rand_str, 0, $num_chars);
	}

	/**
	* Return unique id
	*
	* @param string	$extra	Additional entropy
	* @return string	Unique id
	* @access public
	*/
	public function unique_id($extra = 'c')
	{
		if (!isset(phpbb::$config['rand_seed']))
		{
			$val = md5(md5($extra) . microtime());
			$val = md5(md5($extra) . $val . $extra);
			return substr($val, 4, 16);
		}


		$val = phpbb::$config['rand_seed'] . microtime();
		$val = md5($val);
		phpbb::$config['rand_seed'] = md5(phpbb::$config['rand_seed'] . $val . $extra);

		if (!$this->dss_seeded && phpbb::$config['rand_seed_last_update'] < time() - rand(1, 10))
		{
			set_config('rand_seed', phpbb::$config['rand_seed'], true);
			set_config('rand_seed_last_update', time(), true);

			$this->dss_seeded = true;
		}

		return substr($val, 4, 16);
	}

	/**
	* Hash passwords
	*
	* @version Version 0.1
	*
	* Portable PHP password hashing framework.
	*
	* Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
	* the public domain.
	*
	* There's absolutely no warranty.
	*
	* The homepage URL for this framework is:
	*
	*	http://www.openwall.com/phpass/
	*
	* Please be sure to update the Version line if you edit this file in any way.
	* It is suggested that you leave the main version number intact, but indicate
	* your project name (after the slash) and add your own revision information.
	*
	* Please do not change the "private" password hashing method implemented in
	* here, thereby making your hashes incompatible.  However, if you must, please
	* change the hash type identifier (the "$P$") to something different.
	*
	* Obviously, since this code is in the public domain, the above are not
	* requirements (there can be none), but merely suggestions.
	*
	* @param string	$password	Password to hash
	* @return string	Hashed password
	* @access public
	*/
	public function hash_password($password)
	{
		$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

		$random_state = $this->unique_id();
		$random = '';
		$count = 6;

		if (($fh = @fopen('/dev/urandom', 'rb')))
		{
			$random = fread($fh, $count);
			fclose($fh);
		}

		if (strlen($random) < $count)
		{
			$random = '';

			for ($i = 0; $i < $count; $i += 16)
			{
				$random_state = md5($this->unique_id() . $random_state);
				$random .= pack('H*', md5($random_state));
			}
			$random = substr($random, 0, $count);
		}

		$hash = $this->_hash_crypt_private($password, $this->_hash_gensalt_private($random, $itoa64), $itoa64);
		$result = (strlen($hash) == 34) ? $hash : md5($password);

		return $result;
	}

	/**
	* Check for correct password
	*
	* If the hash length is != 34, then a md5($password) === $hash comparison is done. The correct hash length is 34.
	*
	* @param string	$password	The password in plain text
	* @param string	$hash		The stored password hash
	*
	* @return bool	Returns true if the password is correct, false if not.
	* @access public
	*/
	public function check_password($password, $hash)
	{
		$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
		if (strlen($hash) == 34)
		{
			$result = ($this->_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false;
		}
		else
		{
			$result = (md5($password) === $hash) ? true : false;
		}

		return $result;
	}

	/**
	* Generate salt for hash generation
	* @access private
	*/
	private function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6)
	{
		if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
		{
			$iteration_count_log2 = 8;
		}

		$output = $this->hash_type;
		$output .= $itoa64[min($iteration_count_log2 + 5, 30)];
		$output .= $this->_hash_encode64($input, 6, $itoa64);

		return $output;
	}

	/**
	* Encode hash
	* @access private
	*/
	private function _hash_encode64($input, $count, &$itoa64)
	{
		$output = '';
		$i = 0;

		do
		{
			$value = ord($input[$i++]);
			$output .= $itoa64[$value & 0x3f];

			if ($i < $count)
			{
				$value |= ord($input[$i]) << 8;
			}

			$output .= $itoa64[($value >> 6) & 0x3f];

			if ($i++ >= $count)
			{
				break;
			}

			if ($i < $count)
			{
				$value |= ord($input[$i]) << 16;
			}

			$output .= $itoa64[($value >> 12) & 0x3f];

			if ($i++ >= $count)
			{
				break;
			}

			$output .= $itoa64[($value >> 18) & 0x3f];
		}
		while ($i < $count);

		return $output;
	}

	/**
	* The crypt function/replacement
	* @access private
	*/
	private function _hash_crypt_private($password, $setting, &$itoa64)
	{
		$output = '*';

		// Check for correct hash
		if (substr($setting, 0, 3) != $this->hash_type)
		{
			return $output;
		}

		$count_log2 = strpos($itoa64, $setting[3]);

		if ($count_log2 < 7 || $count_log2 > 30)
		{
			return $output;
		}

		$count = 1 << $count_log2;
		$salt = substr($setting, 4, 8);

		if (strlen($salt) != 8)
		{
			return $output;
		}

		/**
		* We're kind of forced to use MD5 here since it's the only
		* cryptographic primitive available in all versions of PHP
		* currently in use.  To implement our own low-level crypto
		* in PHP would result in much worse performance and
		* consequently in lower iteration counts and hashes that are
		* quicker to crack (by non-PHP code).
		*/
		$hash = md5($salt . $password, true);
		do
		{
			$hash = md5($hash . $password, true);
		}
		while (--$count);

		$output = substr($setting, 0, 12);
		$output .= $this->_hash_encode64($hash, 16, $itoa64);

		return $output;
	}
}

?>