1
<?php
2
/**
3
* Portable PHP password hashing framework
4
*
5
* @author Zhou Yuan <yuanzhou19@gmail.com>
6
* @link http://www.infopotato.com/
7
* @copyright Copyright © 2009-2011 Zhou Yuan
8
* @license http://www.opensource.org/licenses/mit-license.php MIT Licence
9
* @link based on http://www.openwall.com/phpass/
10
* @version Version 0.3 / genuine
11
*/
12
class Password_Hash_Library {
13
private $_itoa64;
14
15
/**
16
* Base-2 logarithm of the iteration count used for password stretching
17
*
18
* @var string
19
*/
20
private $_iteration_count_log2;
21
22
/**
23
* Do we require the hashes to be portable to older systems (less secure)?
24
*
25
* @var boolean
26
*/
27
private $_portable_hashes;
28
29
private $_random_state;
30
31
/**
32
* Constructor
33
*
34
* 'iteration_count_log2'
35
* 'portable_hashes'
36
*/
37
public function __construct(array $config = NULL) {
38
if (count($config) > 0) {
39
$this->_itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
40
41
if ($config['iteration_count_log2'] < 4 || $config['iteration_count_log2'] > 31) {
42
$config['iteration_count_log2'] = 8;
43
}
44
$this->_iteration_count_log2 = $config['iteration_count_log2'];
45
46
// Set $config['portable_hashes'] = FALSE to use stronger but system-specific hashes,
47
// with a possible fallback to the weaker portable hashes.
48
// If set to TRUE, then force the use of weaker portable hashes.
49
$this->_portable_hashes = $config['portable_hashes'];
50
51
$this->_random_state = microtime();
52
if (function_exists('getmypid')) {
53
$this->_random_state .= getmypid();
54
}
55
}
56
}
57
58
protected function get_random_bytes($count) {
59
$output = '';
60
if (is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb'))) {
61
$output = fread($fh, $count);
62
fclose($fh);
63
}
64
65
if (strlen($output) < $count) {
66
$output = '';
67
for ($i = 0; $i < $count; $i += 16) {
68
$this->_random_state =
69
md5(microtime() . $this->_random_state);
70
$output .=
71
pack('H*', md5($this->_random_state));
72
}
73
$output = substr($output, 0, $count);
74
}
75
76
return $output;
77
}
78
79
protected function encode64($input, $count) {
80
$output = '';
81
$i = 0;
82
do {
83
$value = ord($input[$i++]);
84
$output .= $this->_itoa64[$value & 0x3f];
85
if ($i < $count) {
86
$value |= ord($input[$i]) << 8;
87
}
88
$output .= $this->_itoa64[($value >> 6) & 0x3f];
89
if ($i++ >= $count) {
90
break;
91
}
92
if ($i < $count) {
93
$value |= ord($input[$i]) << 16;
94
}
95
$output .= $this->_itoa64[($value >> 12) & 0x3f];
96
if ($i++ >= $count) {
97
break;
98
}
99
$output .= $this->_itoa64[($value >> 18) & 0x3f];
100
} while ($i < $count);
101
102
return $output;
103
}
104
105
protected function gensalt_private($input) {
106
$output = '$P$';
107
$output .= $this->_itoa64[min($this->_iteration_count_log2 + 5, 30)];
108
$output .= $this->encode64($input, 6);
109
110
return $output;
111
}
112
113
protected function crypt_private($password, $setting) {
114
$output = '*0';
115
if (substr($setting, 0, 2) == $output) {
116
$output = '*1';
117
}
118
$id = substr($setting, 0, 3);
119
// We use '$P$', phpBB3 uses '$H$' for the same thing
120
if ($id != '$P$' && $id != '$H$') {
121
return $output;
122
}
123
$count_log2 = strpos($this->_itoa64, $setting[3]);
124
if ($count_log2 < 7 || $count_log2 > 30) {
125
return $output;
126
}
127
$count = 1 << $count_log2;
128
129
$salt = substr($setting, 4, 8);
130
if (strlen($salt) != 8) {
131
return $output;
132
}
133
// We're kind of forced to use MD5 here since it's the only
134
// cryptographic primitive available in all versions of PHP
135
// currently in use (We only use PHP5). To implement our own low-level crypto
136
// in PHP would result in much worse performance and
137
// consequently in lower iteration counts and hashes that are
138
// quicker to crack (by non-PHP code).
139
$hash = md5($salt . $password, TRUE);
140
do {
141
$hash = md5($hash . $password, TRUE);
142
} while (--$count);
143
144
$output = substr($setting, 0, 12);
145
$output .= $this->encode64($hash, 16);
146
147
return $output;
148
}
149
150
protected function gensalt_extended($input) {
151
$count_log2 = min($this->_iteration_count_log2 + 8, 24);
152
// This should be odd to not reveal weak DES keys, and the
153
// maximum valid value is (2**24 - 1) which is odd anyway.
154
$count = (1 << $count_log2) - 1;
155
156
$output = '_';
157
$output .= $this->_itoa64[$count & 0x3f];
158
$output .= $this->_itoa64[($count >> 6) & 0x3f];
159
$output .= $this->_itoa64[($count >> 12) & 0x3f];
160
$output .= $this->_itoa64[($count >> 18) & 0x3f];
161
162
$output .= $this->encode64($input, 3);
163
164
return $output;
165
}
166
167
protected function gensalt_blowfish($input) {
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
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
177
178
$output = '$2a$';
179
$output .= chr(ord('0') + $this->_iteration_count_log2 / 10);
180
$output .= chr(ord('0') + $this->_iteration_count_log2 % 10);
181
$output .= '$';
182
183
$i = 0;
184
do {
185
$c1 = ord($input[$i++]);
186
$output .= $itoa64[$c1 >> 2];
187
$c1 = ($c1 & 0x03) << 4;
188
if ($i >= 16) {
189
$output .= $itoa64[$c1];
190
break;
191
}
192
193
$c2 = ord($input[$i++]);
194
$c1 |= $c2 >> 4;
195
$output .= $itoa64[$c1];
196
$c1 = ($c2 & 0x0f) << 2;
197
198
$c2 = ord($input[$i++]);
199
$c1 |= $c2 >> 6;
200
$output .= $itoa64[$c1];
201
$output .= $itoa64[$c2 & 0x3f];
202
} while (1);
203
204
return $output;
205
}
206
207
/**
208
* Generate the hashed password
209
*
210
* @return string
211
*/
212
public function hash_password($password) {
213
$random = '';
214
215
if (CRYPT_BLOWFISH == 1 && ! $this->_portable_hashes) {
216
$random = $this->get_random_bytes(16);
217
$hash = crypt($password, $this->gensalt_blowfish($random));
218
if (strlen($hash) == 60) {
219
return $hash;
220
}
221
}
222
223
if (CRYPT_EXT_DES == 1 && ! $this->_portable_hashes) {
224
if (strlen($random) < 3) {
225
$random = $this->get_random_bytes(3);
226
}
227
$hash = crypt($password, $this->gensalt_extended($random));
228
if (strlen($hash) == 20) {
229
return $hash;
230
}
231
}
232
233
if (strlen($random) < 6) {
234
$random = $this->get_random_bytes(6);
235
}
236
$hash = $this->crypt_private($password, $this->gensalt_private($random));
237
if (strlen($hash) == 34) {
238
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
/**
247
* Check the supplied password against the hash
248
*
249
* @return boolean
250
*/
251
public function check_password($password, $stored_hash) {
252
$hash = $this->crypt_private($password, $stored_hash);
253
if ($hash[0] == '*') {
254
$hash = crypt($password, $stored_hash);
255
}
256
return $hash == $stored_hash;
257
}
258
}
259
260
/* End of file: ./system/libraries/password_hash/password_hash_library.php */
Page URI: http://www.infopotato.com/index.php/code/library/password_hash/
