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 {
30
	private $itoa64;
31
	private $itoa64BlowFish;
32
	private $iteration_count_log2;
33
	private $portable_hashes;
34
	private $random_state;
35

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

    
41
		if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) {
42
			$iteration_count_log2 = 8;
43
		}
44
		$this->iteration_count_log2 = $iteration_count_log2;
45
		$this->portable_hashes = $portable_hashes;
46
		$this->random_state = microtime();
47
		if (function_exists('getmypid')) {
48
			$this->random_state .= getmypid();
49
		}
50
	}
51

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

    
71
	private function encode64($input, $count)
72
	{
73
		$output = '';
74
		$i = 0;
75
		do {
76
			$value = ord($input[$i++]);
77
			$output .= $this->itoa64[$value & 0x3f];
78
			if ($i < $count) { $value |= ord($input[$i]) << 8; }
79
			$output .= $this->itoa64[($value >> 6) & 0x3f];
80
			if ($i++ >= $count) { break; }
81
			if ($i < $count) { $value |= ord($input[$i]) << 16; }
82
			$output .= $this->itoa64[($value >> 12) & 0x3f];
83
			if ($i++ >= $count) { break; }
84
			$output .= $this->itoa64[($value >> 18) & 0x3f];
85
		} while ($i < $count);
86

    
87
		return $output;
88
	}
89

    
90
	private function gensalt_private($input)
91
	{
92
		$output = '$P$';
93
		$output .= $this->itoa64[min($this->iteration_count_log2 +
94
			((PHP_VERSION >= '5') ? 5 : 3), 30)];
95
		$output .= $this->encode64($input, 6);
96
		return $output;
97
	}
98

    
99
	private function crypt_private($password, $setting)
100
	{
101
		$output = '*0';
102
		if (substr($setting, 0, 2) == $output) { $output = '*1'; }
103
		$id = substr($setting, 0, 3);
104
		# We use "$P$", phpBB3 uses "$H$" for the same thing
105
		if ($id != '$P$' && $id != '$H$') { return $output; }
106
		$count_log2 = strpos($this->itoa64, $setting[3]);
107
		if ($count_log2 < 7 || $count_log2 > 30) { return $output; }
108
		$count = 1 << $count_log2;
109
		$salt = substr($setting, 4, 8);
110
		if (strlen($salt) != 8) { return $output; }
111

    
112
		# We're kind of forced to use MD5 here since it's the only
113
		# cryptographic primitive available in all versions of PHP
114
		# currently in use.  To implement our own low-level crypto
115
		# in PHP would result in much worse performance and
116
		# consequently in lower iteration counts and hashes that are
117
		# quicker to crack (by non-PHP code).
118
		if (PHP_VERSION >= '5') {
119
			$hash = md5($salt . $password, TRUE);
120
			do {
121
				$hash = md5($hash . $password, TRUE);
122
			} while (--$count);
123
		} else {
124
			$hash = pack('H*', md5($salt . $password));
125
			do {
126
				$hash = pack('H*', md5($hash . $password));
127
			} while (--$count);
128
		}
129
		$output = substr($setting, 0, 12);
130
		$output .= $this->encode64($hash, 16);
131
		return $output;
132
	}
133

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

    
166
	private function gensalt_blowfish($input)
167
	{
168
		# This one needs to use a different order of characters and a
169
		# different encoding scheme from the one in encode64() above.
170
		# We care because the last character in our encoded string will
171
		# only represent 2 bits.  While two known implementations of
172
		# bcrypt will happily accept and correct a salt string which
173
		# has the 4 unused bits set to non-zero, we do not want to take
174
		# chances and we also do not want to waste an additional byte
175
		# of entropy.
176

    
177
		$output = '$2a$';
178
		$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
179
		$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
180
		$output .= '$';
181
		$i = 0;
182
		do {
183
			$c1 = ord($input[$i++]);
184
			$output .= $this->itoa64BlowFish[$c1 >> 2];
185
			$c1 = ($c1 & 0x03) << 4;
186
			if ($i >= 16) {
187
				$output .= $this->itoa64BlowFish[$c1];
188
				break;
189
			}
190
			$c2 = ord($input[$i++]);
191
			$c1 |= $c2 >> 4;
192
			$output .= $this->itoa64BlowFish[$c1];
193
			$c1 = ($c2 & 0x0f) << 2;
194
			$c2 = ord($input[$i++]);
195
			$c1 |= $c2 >> 6;
196
			$output .= $this->itoa64BlowFish[$c1];
197
			$output .= $this->itoa64BlowFish[$c2 & 0x3f];
198
		} while (1);
199
		return $output;
200
	}
201

    
202
	public function HashPassword($password)
203
	{
204
		$random = '';
205

    
206
/** Begin inserted function for WebsiteBaker by W.v.d.Decken **/	
207
		if (CRYPT_SHA512 == 1 && !$this->portable_hashes) {
208
			$random = $this->get_random_bytes(16);
209
			$hash = crypt($password, $this->gensalt_sha($random, 'SHA512'));
210
			if (strlen(substr(strrchr($hash, 36), 1)) == 86) { return $hash; }
211
		}
212

    
213
		if (CRYPT_SHA256 == 1 && !$this->portable_hashes) {
214
			$random = $this->get_random_bytes(16);
215
			$hash = crypt($password, $this->gensalt_sha($random, 'SHA256'));
216
			if (strlen(substr(strrchr($hash, 36), 1)) == 43) { return $hash; }
217
		}
218
/** End inserted function for WebsiteBaker by W.v.d.Decken **/	
219

    
220
		if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) {
221
			$random = $this->get_random_bytes(16);
222
			$hash = crypt($password, $this->gensalt_blowfish($random));
223
			if (strlen($hash) == 60) { return $hash; }
224
		}
225

    
226
		if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) {
227
			if (strlen($random) < 3) {
228
				$random = $this->get_random_bytes(3);
229
			}
230
			$hash = crypt($password, $this->gensalt_extended($random));
231
			if (strlen($hash) == 20) { return $hash; }
232
		}
233

    
234
		if (strlen($random) < 6) {
235
			$random = $this->get_random_bytes(6);
236
		}
237
		$hash = $this->crypt_private($password, $this->gensalt_private($random));
238
		if (strlen($hash) == 34) { return $hash; }
239

    
240
		# Returning '*' on error is safe here, but would _not_ be safe
241
		# in a crypt(3)-like function used _both_ for generating new
242
		# hashes and for validating passwords against existing hashes.
243
		return '*';
244
	}
245

    
246
	public function CheckPassword($password, $stored_hash)
247
	{
248
		$hash = $this->crypt_private($password, $stored_hash);
249
		if ($hash[0] == '*') {
250
			$hash = crypt($password, $stored_hash);
251
		}
252
		return $hash == $stored_hash;
253
	}
254
}
255

    
    (1-1/1)