password_hash_library.php
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 &copy; 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'] < || $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($output0$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 530)];
108         
$output .= $this->encode64($input6);
109
110         return 
$output;
111     }
112
113     protected function 
crypt_private($password$setting) {
114         
$output '*0';
115         if (
substr($setting02) == $output) {
116             
$output '*1';
117         }
118         
$id substr($setting03);
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 || $count_log2 30) {
125             return 
$output;
126         }
127         
$count << $count_log2;
128
129         
$salt substr($setting48);
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 $passwordTRUE);
140         do {
141             
$hash md5($hash $passwordTRUE);
142         } while (--
$count);
143
144         
$output substr($setting012);
145         
$output .= $this->encode64($hash16);
146
147         return 
$output;
148     }
149
150     protected function 
gensalt_extended($input) {
151         
$count_log2 min($this->_iteration_count_log2 824);
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 = (<< $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($input3);
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 == && ! $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 == && ! $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/