Project

General

Profile

1
<?php
2
/**
3
 * @category     Core
4
 * @package      Core_security
5
 * @author       Werner v.d.Decken
6
 * @copyright    ISTeasy-project(http://isteasy.de/)
7
 * @license      Creative Commons BY-SA 3.0 http://creativecommons.org/licenses/by-sa/3.0/
8
 * @version      $Id$
9
 * @filesource   $HeadURL:$
10
 * @since        Datei vorhanden seit Release 2.8.2
11
 * @lastmodified $Date:$
12
 *
13
 * this class works with salted md5-hashes with several rounds. 
14
 * For backward compatibility it can compare normal md5-hashes also.
15
 * Minimum requirements: PHP 5.2.2 or higher
16
 *
17
 * *****************************************************************************
18
 * This class is based on the Portable PHP password hashing framework.
19
 * Version 0.3 / genuine. Written by Solar Designer <solar at openwall.com>
20
 * in 2004-2006 and placed in the public domain. Revised in subsequent years,
21
 * still public domain. There's absolutely no warranty.
22
 * The homepage URL for this framework is: http://www.openwall.com/phpass/
23
 * *****************************************************************************
24
 */
25
class PasswordHash {
26

    
27
	const SECURITY_WEAK      = 6;
28
	const SECURITY_MEDIUM    = 8;
29
	const SECURITY_NORMAL    = 10;
30
	const SECURITY_STRONG    = 12;
31
	const SECURITY_STRONGER  = 16;
32

    
33
	private $_itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
34
	private $_iterationCountLog2 = 8;
35
	private $_portableHashes = true;
36
	private $_randomState = '';
37
	
38
	/**
39
	 * @param int $iterationCountLog2 number of iterations as exponent of 2
40
	 * @param bool $portableHashes TRUE = use MD5 only | FALSE = automatic
41
	 */
42
	public function __construct($iterationCountLog2, $portableHashes = true)
43
	{
44

    
45
		if ($iterationCountLog2 < 4 || $iterationCountLog2 > 31) {
46
			$iterationCountLog2 = 8;
47
		}
48
		$this->_iterationCountLog2 = $iterationCountLog2;
49
		$this->_portableHashes = $portableHashes;
50
		$this->_randomState = microtime();
51
		if (function_exists('getmypid')) {
52
			$this->_randomState .= getmypid();
53
		}
54
	}
55

    
56

    
57
	private function _getRandomBytes($count)
58
	{
59
		$output = '';
60
		if (is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb'))) {
61
			$output = fread($fh, $count);
62
			fclose($fh);
63
		}
64
		if (strlen($output) < $count) {
65
			$output = '';
66
			for ($i = 0; $i < $count; $i += 16) {
67
				$this->_randomState = md5(microtime() . $this->_randomState);
68
				$output .= pack('H*', md5($this->_randomState));
69
			}
70
			$output = substr($output, 0, $count);
71
		}
72
		return $output;
73
	}
74

    
75
	private function _Encode64($input, $count)
76
	{
77
		$output = '';
78
		$i = 0;
79
		do {
80
			$value = ord($input[$i++]);
81
			$output .= $this->_itoa64[$value & 0x3f];
82
			if ($i < $count) {
83
				$value |= ord($input[$i]) << 8;
84
			}
85
			$output .= $this->_itoa64[($value >> 6) & 0x3f];
86
			if ($i++ >= $count) { break; }
87
			if ($i < $count) {
88
				$value |= ord($input[$i]) << 16;
89
			}
90
			$output .= $this->_itoa64[($value >> 12) & 0x3f];
91
			if ($i++ >= $count) { break; }
92
			$output .= $this->_itoa64[($value >> 18) & 0x3f];
93
		} while ($i < $count);
94
		return $output;
95
	}
96

    
97
	private function _GenSaltPrivate($input)
98
	{
99
		$output = '$P$';
100
		$output .= $this->_itoa64[min($this->_iterationCountLog2 + 5, 30)];
101
		$output .= $this->_Encode64($input, 6);
102
		return $output;
103
	}
104

    
105
	private function _CryptPrivate($password, $setting)
106
	{
107
		$output = '*0';
108
		if (substr($setting, 0, 2) == $output) {
109
			$output = '*1';
110
		}
111
		$id = substr($setting, 0, 3);
112
		# We use "$P$", phpBB3 uses "$H$" for the same thing
113
		if ($id != '$P$' && $id != '$H$') {
114
			return $output;
115
		}
116
		$count_log2 = strpos($this->_itoa64, $setting[3]);
117
		if ($count_log2 < 7 || $count_log2 > 30) {
118
			return $output;
119
		}
120
		$count = 1 << $count_log2;
121
		$salt = substr($setting, 4, 8);
122
		if (strlen($salt) != 8) {
123
			return $output;
124
		}
125
		# We're kind of forced to use MD5 here since it's the only
126
		# cryptographic primitive available in all versions of PHP
127
		# currently in use.  To implement our own low-level crypto
128
		# in PHP would result in much worse performance and
129
		# consequently in lower iteration counts and hashes that are
130
		# quicker to crack (by non-PHP code).
131
		$hash = md5($salt . $password, TRUE);
132
		do {
133
			$hash = md5($hash . $password, TRUE);
134
		} while (--$count);
135
		$output = substr($setting, 0, 12);
136
		$output .= $this->_Encode64($hash, 16);
137
		return $output;
138
	}
139

    
140
	/**
141
	 * calculate the hash from a given password
142
	 * @param string $password password as original string
143
	 * @return string generated hash | '*' on error
144
	 */
145
	public function HashPassword($password, $md5 = false)
146
	{
147
		if ($md5) { return(md5($password)); }
148
		$random = '';
149
		if (strlen($random) < 6) {
150
			$random = $this->_getRandomBytes(6);
151
		}
152
		$hash = $this->_CryptPrivate($password, $this->_GenSaltPrivate($random));
153
		if (strlen($hash) == 34) {
154
			return $hash;
155
		}
156
		# Returning '*' on error is safe here, but would _not_ be safe
157
		# in a crypt(3)-like function used _both_ for generating new
158
		# hashes and for validating passwords against existing hashes.
159
		return '*';
160
	}
161

    
162
	/**
163
	 * encodes the password and compare it against the given hash
164
	 * @param string $password clear password
165
	 * @param string $stored_hash the hash to compare against
166
	 * @return bool
167
	 */
168
	public function CheckPassword($password, $stored_hash)
169
	{
170
	// compare against a normal, simple md5-hash
171
		if(preg_match('/^[0-9a-f]{32}$/i', $stored_hash)) {
172
			return md5($password) == $stored_hash;
173
		}
174
	// compare against a rounded, salted md5-hash
175
		$hash = $this->_CryptPrivate($password, $stored_hash);
176
		if ($hash[0] == '*') {
177
			$hash = crypt($password, $stored_hash);
178
		}
179
		return $hash == $stored_hash;
180
	}
181
	/**
182
	 * generate a case sensitive mnemonic password including numbers and special chars
183
	 * makes no use of lowercase 'l', uppercase 'I', 'O' or number '0'
184
	 * @param int $length length of the generated password. default = 8
185
	 * @return string
186
	 */
187
	public static function NewPassword($length = self::SECURITY_MEDIUM)
188
	{
189
		$chars = array(
190
			array('b','c','d','f','g','h','j','k','l','m','n','p','r','s','t','v','w','x','y','z'),
191
			array('a','e','i','o','u'),
192
			array('!','-','@','_',':','.','+','%','/','*')
193
		);
194
		if($length < self::SECURITY_WEAK) { $length = self::SECURITY_WEAK; }
195
		$length = ceil($length / 2);
196
		$Password = array();
197
	// at first fill array alternating with vowels and consonants
198
		for($x = 0; $x < $length; $x++) {
199
			$char = $chars[0][rand(1000, 10000) % sizeof($chars[0])];
200
			$Password[] = $char == 'l' ? 'L' : $char;
201
			$Password[] = $chars[1][rand(1000, 10000) % sizeof($chars[1])];
202
		}
203
	// transform some random chars into uppercase
204
		$pos = ((rand(1000, 10000) % 3) + 1);
205
		while($pos < sizeof($Password)) {
206
			$Password[$pos] = ($Password[$pos] == 'i' || $Password[$pos] == 'o')
207
			                  ? $Password[$pos] : strtoupper($Password[$pos]);
208
			$pos += ((rand(1000, 10000) % 3) + 1);
209
		}
210
	// insert some numeric chars, between 1 and 9
211
		$specialChars = array();
212
		$specialCharsCount = floor(sizeof($Password) / 4);
213
		while(sizeof($specialChars) < $specialCharsCount) {
214
			$key = (rand(1000, 10000) % sizeof($Password));
215
			if(!isset($specialChars[$key])) {
216
				$specialChars[$key] = (rand(1000, 10000) % 9) + 1;
217
			}
218
		}
219
	// insert some punctuation chars, but not leading or trailing
220
		$specialCharsCount += floor((sizeof($Password)-1) / 6);
221
		while(sizeof($specialChars) < $specialCharsCount) {
222
			$key = (rand(1000, 10000) % (sizeof($Password)-2))+1;
223
			if(!isset($specialChars[$key])) {
224
				$specialChars[$key] = $chars[2][(rand(1000, 10000) % sizeof($chars[2]))];
225
			}
226
		}
227
		foreach($specialChars as $key=>$val) {
228
			$Password[$key] = $val;
229
		}
230

    
231
		return implode($Password);
232
	}
233

    
234
} // end of class
(4-4/25)