Ticket #1217: validpass.php

File validpass.php, 6.5 KB (added by Volker Strähle, 9 years ago)
Line 
1<?php
2/* -----------------------------------------------------------------------------------------
3 $Id$
4
5 modified eCommerce Shopsoftware
6 http://www.modified-shop.org
7
8 Copyright (c) 2009 - 2013 [www.modified-shop.org]
9 -----------------------------------------------------------------------------------------
10 based on:
11 (c) 2004-2006 Solar Designer <solar at openwall.com>
12 (c) 2009-2011 Sam Weiss http://eschercms.org/
13
14 See the original project's web site: http://www.openwall.com/phpass/
15
16 changed portable hash implementation to use sha1 instead of md5, and changed
17 the hash signature to "$Q$" so that the hash is not mistaken for an md5 hash.
18
19 -----------------------------------------------------------------------------------------
20 Released under the GNU General Public License
21 ---------------------------------------------------------------------------------------*/
22
23
24class validpass {
25 private $itoa64;
26 private $iteration_count_log2;
27 private $portable_hashes;
28 private $random_state;
29 private $cryptType;
30
31
32 public function __construct($iterationCountLog2=10, $portableHashes=false, $cryptType = '$2a$')
33 {
34 $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
35
36 if ($iterationCountLog2 < 4 || $iterationCountLog2 > 31)
37 {
38 $iterationCountLog2 = 8;
39 }
40
41 $this->iteration_count_log2 = $iterationCountLog2;
42 $this->portable_hashes = $portableHashes;
43 $this->random_state = microtime() . getmyinode() . '-' . getmypid();
44 $this->cryptType =$cryptType;
45 }
46
47
48 public function encrypt_password($plain)
49 {
50 $random = '';
51
52 if (CRYPT_BLOWFISH === 1 && !$this->portable_hashes && function_exists('version_compare') && version_compare(phpversion(), '5.3.7', '>='))
53 {
54 $random = $this->getRandomBytes(16);
55 $hash = crypt($plain, $this->gensalt_blowfish($random));
56 if (strlen($hash) === 60 && $this->validate_password($plain, $hash) === true)
57 {
58 return $hash;
59 }
60 }
61
62 if (CRYPT_EXT_DES === 1 && !$this->portable_hashes && function_exists('version_compare') && version_compare(phpversion(), '5.3.0', '>='))
63 {
64 if (strlen($random) != 3)
65 {
66 $random = $this->getRandomBytes(3);
67 }
68 $hash = crypt($plain, $this->gensalt_ext_des($random));
69 if (strlen($hash) === 20 && $this->validate_password($plain, $hash) === true)
70 {
71 return $hash;
72 }
73 }
74
75 if (strlen($random) != 6)
76 {
77 $random = $this->getRandomBytes(6);
78 }
79 $hash = $this->crypt_portable($plain, $this->gensalt_portable($random));
80 if (strlen($hash) === 39 && $this->validate_password($plain, $hash) === true)
81 {
82 return $hash;
83 }
84
85 # Returning '*' on error is safe here, but would _not_ be safe
86 # in a crypt(3)-like function used _both_ for generating new
87 # hashes and for validating messages against existing hashes.
88
89 return '*';
90 }
91
92
93 public function validate_password($plain, $encrypted)
94 {
95 $hash = $this->crypt_portable($plain, $encrypted);
96
97 if ($hash[0] === '*')
98 {
99 $hash = crypt($plain, $encrypted);
100 }
101
102 // ??? BETTER ???
103 // return hash_equals($hash, $encrypted);
104 return ($hash === $encrypted);
105 }
106
107
108 protected function getRandomBytes($count)
109 {
110 $output = '';
111
112 if (strlen($output) < $count)
113 {
114 $output = '';
115 for ($i = 0; $i < $count; $i += 16)
116 {
117 $this->random_state = sha1(microtime() . $this->random_state);
118 $output .= sha1($this->random_state, true);
119 }
120 $output = substr($output, 0, $count);
121 }
122
123 return $output;
124 }
125
126
127 protected function encode64($input, $count)
128 {
129 $output = '';
130 $i = 0;
131
132 do
133 {
134 $value = ord($input[$i++]);
135 $output .= $this->itoa64[$value & 0x3f];
136 if ($i < $count)
137 {
138 $value |= ord($input[$i]) << 8;
139 }
140 $output .= $this->itoa64[($value >> 6) & 0x3f];
141 if ($i++ >= $count)
142 {
143 break;
144 }
145 if ($i < $count)
146 {
147 $value |= ord($input[$i]) << 16;
148 }
149 $output .= $this->itoa64[($value >> 12) & 0x3f];
150 if ($i++ >= $count)
151 {
152 break;
153 }
154 $output .= $this->itoa64[($value >> 18) & 0x3f];
155 } while ($i < $count);
156
157 return $output;
158 }
159
160
161 protected function crypt_portable($message, $setting)
162 {
163 $output = '*0';
164
165 if (substr($setting, 0, 2) === $output)
166 {
167 $output = '*1';
168 }
169
170 if (substr($setting, 0, 3) != '$Q$')
171 {
172 return $output;
173 }
174
175 $count_log2 = strpos($this->itoa64, $setting[3]);
176 if ($count_log2 < 7 || $count_log2 > 30)
177 {
178 return $output;
179 }
180
181 $count = 1 << $count_log2;
182
183 $salt = substr($setting, 4, 8);
184 if (strlen($salt) != 8)
185 {
186 return $output;
187 }
188
189 $hash = sha1($salt . $message, TRUE);
190 do
191 {
192 $hash = sha1($hash . $message, TRUE);
193 } while (--$count);
194
195 $output = substr($setting, 0, 12) . $this->encode64($hash, 20);
196
197 return $output;
198 }
199
200
201 protected function gensalt_portable($input)
202 {
203 $output = '$Q$';
204 $output .= $this->itoa64[min($this->iteration_count_log2 + 5, 30)];
205 $output .= $this->encode64($input, 6);
206
207 return $output;
208 }
209
210
211 protected function gensalt_ext_des($input)
212 {
213 $count_log2 = min($this->iteration_count_log2 + 8, 24);
214 # This should be odd to not reveal weak DES keys, and the
215 # maximum valid value is (2**24 - 1) which is odd anyway.
216 $count = (1 << $count_log2) - 1;
217
218 $output = '_';
219 $output .= $this->itoa64[$count & 0x3f];
220 $output .= $this->itoa64[($count >> 6) & 0x3f];
221 $output .= $this->itoa64[($count >> 12) & 0x3f];
222 $output .= $this->itoa64[($count >> 18) & 0x3f];
223
224 $output .= $this->encode64($input, 3);
225
226 return $output;
227 }
228
229
230 protected function gensalt_blowfish($input)
231 {
232 # This one needs to use a different order of characters and a
233 # different encoding scheme from the one in encode64() above.
234 # We care because the last character in our encoded string will
235 # only represent 2 bits. While two known implementations of
236 # bcrypt will happily accept and correct a salt string which
237 # has the 4 unused bits set to non-zero, we do not want to take
238 # chances and we also do not want to waste an additional byte
239 # of entropy.
240 $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
241
242 //$output = '$2a$';
243 //$output = '$2y$';
244 $output = $this->cryptType;
245 $output .= chr(ord('0') + $this->iteration_count_log2 / 10);
246 $output .= chr(ord('0') + $this->iteration_count_log2 % 10);
247 $output .= '$';
248
249 $i = 0;
250 do
251 {
252 $c1 = ord($input[$i++]);
253 $output .= $itoa64[$c1 >> 2];
254 $c1 = ($c1 & 0x03) << 4;
255 if ($i >= 16)
256 {
257 $output .= $itoa64[$c1];
258 break;
259 }
260
261 $c2 = ord($input[$i++]);
262 $c1 |= $c2 >> 4;
263 $output .= $itoa64[$c1];
264 $c1 = ($c2 & 0x0f) << 2;
265
266 $c2 = ord($input[$i++]);
267 $c1 |= $c2 >> 6;
268 $output .= $itoa64[$c1];
269 $output .= $itoa64[$c2 & 0x3f];
270 } while (1);
271
272 return $output;
273 }
274}
275?>