Project

General

Profile

1
<?php
2
/**
3
 * Portable PHP password hashing framework.
4
 *
5
 * Version 0.4 / WebsiteBaker Rev. 0.1
6
 * WB changes: added SHA-256, SHA-512 (2012/10/27 DarkViper)
7
 *
8
 * Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
9
 * the public domain.  Revised in subsequent years, still public domain.
10
 *
11
 * There's absolutely no warranty.
12
 *
13
 * The homepage URL for this framework is:
14
 *
15
 *	http://www.openwall.com/phpass/
16
 *
17
 * Please be sure to update the Version line if you edit this file in any way.
18
 * It is suggested that you leave the main version number intact, but indicate
19
 * your project name (after the slash) and add your own revision information.
20
 *
21
 * Please do not change the "private" password hashing method implemented in
22
 * here, thereby making your hashes incompatible.  However, if you must, please
23
 * change the hash type identifier (the "$P$") to something different.
24
 *
25
 * Obviously, since this code is in the public domain, the above are not
26
 * requirements (there can be none), but merely suggestions.
27
 */
28

    
29
class PasswordHash implements PasswordHashInterface {
30
	protected $itoa64;
31
	protected $itoa64BlowFish;
32
	protected $random_state;
33

    
34
	public $iteration_count_log2;
35
	public $portable_hashes;
36

    
37
	public function __construct($iteration_count_log2, $portable_hashes)
38
	{
39
		$this->itoa64         = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
40
		$this->itoa64BlowFish = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
41

    
42
		if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) {
43
			$iteration_count_log2 = 8;
44
		}
45
		$this->iteration_count_log2 = $iteration_count_log2;
46
		$this->portable_hashes = $portable_hashes;
47
		$this->random_state = microtime();
48
		if (function_exists('getmypid')) {
49
			$this->random_state .= getmypid();
50
		}
51
	}
52
/** Begin inserted function for WebsiteBaker by M.v.d.Decken **/
53
/**
54
 * Interface compatibility methode to set values
55
 * @param int  $iIterations number of iterations
56
 * @param bool $bHashType   type of encoding
57
 */
58
	public function setParams($iIterations, $bHashType){
59
		$this->iteration_count_log2 = $iIterations;
60
		$this->portable_hashes = $bHashType;
61
	}
62
/** End inserted function for WebsiteBaker by M.v.d.Decken **/
63

    
64
	private function get_random_bytes($count)
65
	{
66
		$output = '';
67
		if (@is_readable('/dev/urandom') &&
68
		    ($fh = @fopen('/dev/urandom', 'rb'))) {
69
			$output = fread($fh, $count);
70
			fclose($fh);
71
		}
72
		if (strlen($output) < $count) {
73
			$output = '';
74
			for ($i = 0; $i < $count; $i += 16) {
75
				$this->random_state = md5(microtime() . $this->random_state);
76
				$output .= pack('H*', md5($this->random_state));
77
			}
78
			$output = substr($output, 0, $count);
79
		}
80
		return $output;
81
	}
82

    
83
	private function encode64($input, $count)
84
	{
85
		$output = '';
86
		$i = 0;
87
		do {
88
			$value = ord($input[$i++]);
89
			$output .= $this->itoa64[$value & 0x3f];
90
			if ($i < $count) { $value |= ord($input[$i]) << 8; }
91
			$output .= $this->itoa64[($value >> 6) & 0x3f];
92
			if ($i++ >= $count) { break; }
93
			if ($i < $count) { $value |= ord($input[$i]) << 16; }
94
			$output .= $this->itoa64[($value >> 12) & 0x3f];
95
			if ($i++ >= $count) { break; }
96
			$output .= $this->itoa64[($value >> 18) & 0x3f];
97
		} while ($i < $count);
98

    
99
		return $output;
100
	}
101

    
102
	private function gensalt_private($input)
103
	{
104
		$output = '$P$';
105
		$output .= $this->itoa64[min($this->iteration_count_log2 +
106
			((PHP_VERSION >= '5') ? 5 : 3), 30)];
107
		$output .= $this->encode64($input, 6);
108
		return $output;
109
	}
110

    
111
	private function crypt_private($password, $setting)
112
	{
113
		$output = '*0';
114
		if (substr($setting, 0, 2) == $output) { $output = '*1'; }
115
		$id = substr($setting, 0, 3);
116
		# We use "$P$", phpBB3 uses "$H$" for the same thing
117
		if ($id != '$P$' && $id != '$H$') { return $output; }
118
		$count_log2 = strpos($this->itoa64, $setting[3]);
119
		if ($count_log2 < 7 || $count_log2 > 30) { return $output; }
120
		$count = 1 << $count_log2;
121
		$salt = substr($setting, 4, 8);
122
		if (strlen($salt) != 8) { return $output; }
123

    
124
		# We're kind of forced to use MD5 here since it's the only
125
		# cryptographic primitive available in all versions of PHP
126
		# currently in use.  To implement our own low-level crypto
127
		# in PHP would result in much worse performance and
128
		# consequently in lower iteration counts and hashes that are
129
		# quicker to crack (by non-PHP code).
130
		if (PHP_VERSION >= '5') {
131
			$hash = md5($salt . $password, TRUE);
132
			do {
133
				$hash = md5($hash . $password, TRUE);
134
			} while (--$count);
135
		} else {
136
			$hash = pack('H*', md5($salt . $password));
137
			do {
138
				$hash = pack('H*', md5($hash . $password));
139
			} while (--$count);
140
		}
141
		$output = substr($setting, 0, 12);
142
		$output .= $this->encode64($hash, 16);
143
		return $output;
144
	}
145

    
146
	private function gensalt_extended($input)
147
	{
148
		$count_log2 = min($this->iteration_count_log2 + 8, 24);
149
		# This should be odd to not reveal weak DES keys, and the
150
		# maximum valid value is (2**24 - 1) which is odd anyway.
151
		$count = (1 << $count_log2) - 1;
152
		$output = '_';
153
		$output .= $this->itoa64[$count & 0x3f];
154
		$output .= $this->itoa64[($count >> 6) & 0x3f];
155
		$output .= $this->itoa64[($count >> 12) & 0x3f];
156
		$output .= $this->itoa64[($count >> 18) & 0x3f];
157
		$output .= $this->encode64($input, 3);
158
		return $output;
159
	}
160
/** Begin inserted function for WebsiteBaker by M.v.d.Decken **/
161
/**
162
 * 
163
 * @param type $input
164
 * @param type $sType
165
 * @return type
166
 */
167
	private function gensalt_sha($input, $sType = 'SHA512')
168
	{
169
		$iType = ($sType === 'SHA256' ? 5 : 6);
170
		$iIterations = min(max(pow(2, $this->iteration_count_log2), 10000), 999999999);
171
		$output = '$'.(string)$iType.'$rounds='.(string)$iIterations.'$';
172
		$output .= $this->encode64($input, 16);
173
		return $output;
174
	}
175
/** End inserted function for WebsiteBaker by W.v.d.Decken **/	
176

    
177
	private function gensalt_blowfish($input)
178
	{
179
		# This one needs to use a different order of characters and a
180
		# different encoding scheme from the one in encode64() above.
181
		# We care because the last character in our encoded string will
182
		# only represent 2 bits.  While two known implementations of
183
		# bcrypt will happily accept and correct a salt string which
184
		# has the 4 unused bits set to non-zero, we do not want to take
185
		# chances and we also do not want to waste an additional byte
186
		# of entropy.
187

    
188
		$output = '$2a$';
189
		$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
190
		$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
191
		$output .= '$';
192
		$i = 0;
193
		do {
194
			$c1 = ord($input[$i++]);
195
			$output .= $this->itoa64BlowFish[$c1 >> 2];
196
			$c1 = ($c1 & 0x03) << 4;
197
			if ($i >= 16) {
198
				$output .= $this->itoa64BlowFish[$c1];
199
				break;
200
			}
201
			$c2 = ord($input[$i++]);
202
			$c1 |= $c2 >> 4;
203
			$output .= $this->itoa64BlowFish[$c1];
204
			$c1 = ($c2 & 0x0f) << 2;
205
			$c2 = ord($input[$i++]);
206
			$c1 |= $c2 >> 6;
207
			$output .= $this->itoa64BlowFish[$c1];
208
			$output .= $this->itoa64BlowFish[$c2 & 0x3f];
209
		} while (1);
210
		return $output;
211
	}
212

    
213
	public function HashPassword($password)
214
	{
215
		$random = '';
216

    
217
/** Begin inserted function for WebsiteBaker by W.v.d.Decken **/	
218
		if (CRYPT_SHA512 == 1 && !$this->portable_hashes) {
219
			$random = $this->get_random_bytes(16);
220
			$hash = crypt($password, $this->gensalt_sha($random, 'SHA512'));
221
			if (strlen(substr(strrchr($hash, 36), 1)) == 86) { return $hash; }
222
		}
223

    
224
		if (CRYPT_SHA256 == 1 && !$this->portable_hashes) {
225
			$random = $this->get_random_bytes(16);
226
			$hash = crypt($password, $this->gensalt_sha($random, 'SHA256'));
227
			if (strlen(substr(strrchr($hash, 36), 1)) == 43) { return $hash; }
228
		}
229
/** End inserted function for WebsiteBaker by W.v.d.Decken **/	
230

    
231
		if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) {
232
			$random = $this->get_random_bytes(16);
233
			$hash = crypt($password, $this->gensalt_blowfish($random));
234
			if (strlen($hash) == 60) { return $hash; }
235
		}
236

    
237
		if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) {
238
			if (strlen($random) < 3) {
239
				$random = $this->get_random_bytes(3);
240
			}
241
			$hash = crypt($password, $this->gensalt_extended($random));
242
			if (strlen($hash) == 20) { return $hash; }
243
		}
244

    
245
		if (strlen($random) < 6) {
246
			$random = $this->get_random_bytes(6);
247
		}
248
		$hash = $this->crypt_private($password, $this->gensalt_private($random));
249
		if (strlen($hash) == 34) { return $hash; }
250

    
251
		# Returning '*' on error is safe here, but would _not_ be safe
252
		# in a crypt(3)-like function used _both_ for generating new
253
		# hashes and for validating passwords against existing hashes.
254
		return '*';
255
	}
256

    
257
	public function CheckPassword($password, $stored_hash)
258
	{
259
		$hash = $this->crypt_private($password, $stored_hash);
260
		if ($hash[0] == '*') {
261
			$hash = crypt($password, $stored_hash);
262
		}
263
		return $hash == $stored_hash;
264
	}
265
}
266

    
    (1-1/1)