44class SecureFuncs
55{
66
7- public static $ secret ;
7+ /**
8+ * @var int The key length
9+ */
10+ private static $ _keyLength = 32 ;
811
912 /**
1013 * @param $input
@@ -67,7 +70,7 @@ public static function getFormToken($id, $token, $limit_time = 300)
6770 // If time limit is set, check if isset
6871 if ($ limit_time !== false ) {
6972 // if time < limit time return true/false
70- if (empty ($ _SESSION ['formtoken_time ' ][$ id ]) || $ _SESSION ['formtoken_time ' ][$ id ] < time () - $ limit_time ){
73+ if (empty ($ _SESSION ['formtoken_time ' ][$ id ]) || $ _SESSION ['formtoken_time ' ][$ id ] < time () - $ limit_time ) {
7174 $ valid = false ;
7275 }
7376 }
@@ -213,4 +216,170 @@ public static function pseudoBytes($length = 1)
213216 }
214217 }
215218
219+ /**
220+ * Scrypt functions
221+ *
222+ * Based on php-scrypt - https://github.com/DomBlack/php-scrypt
223+ */
224+
225+ /**
226+ * Generates a random salt
227+ *
228+ * @param int $length The length of the salt
229+ *
230+ * @return string The salt
231+ */
232+ public static function generateSalt ($ length = 8 )
233+ {
234+ $ buffer = '' ;
235+ $ buffer_valid = false ;
236+ if (function_exists ('mcrypt_create_iv ' ) && !defined ('PHALANGER ' )) {
237+ $ buffer = mcrypt_create_iv ($ length , MCRYPT_DEV_URANDOM );
238+ if ($ buffer ) {
239+ $ buffer_valid = true ;
240+ }
241+ }
242+ if (!$ buffer_valid && function_exists ('openssl_random_pseudo_bytes ' )) {
243+ $ cryptoStrong = false ;
244+ $ buffer = openssl_random_pseudo_bytes ($ length , $ cryptoStrong );
245+ if ($ buffer && $ cryptoStrong ) {
246+ $ buffer_valid = true ;
247+ }
248+ }
249+ if (!$ buffer_valid && is_readable ('/dev/urandom ' )) {
250+ $ f = fopen ('/dev/urandom ' , 'r ' );
251+ $ read = static ::strlen ($ buffer );
252+ while ($ read < $ length ) {
253+ $ buffer .= fread ($ f , $ length - $ read );
254+ $ read = static ::strlen ($ buffer );
255+ }
256+ fclose ($ f );
257+ if ($ read >= $ length ) {
258+ $ buffer_valid = true ;
259+ }
260+ }
261+ if (!$ buffer_valid || static ::strlen ($ buffer ) < $ length ) {
262+ $ bl = static ::strlen ($ buffer );
263+ for ($ i = 0 ; $ i < $ length ; $ i ++) {
264+ if ($ i < $ bl ) {
265+ $ buffer [$ i ] = $ buffer [$ i ] ^ chr (mt_rand (0 , 255 ));
266+ } else {
267+ $ buffer .= chr (mt_rand (0 , 255 ));
268+ }
269+ }
270+ }
271+ $ salt = str_replace (array ('+ ' , '$ ' ), array ('. ' , '' ), base64_encode ($ buffer ));
272+
273+ return $ salt ;
274+ }
275+
276+ /**
277+ * Create a password hash
278+ *
279+ * @param string $password The clear text password
280+ * @param string|bool $salt The salt to use, or null to generate a random one
281+ * @param int $N The CPU difficultly (must be a power of 2, > 1)
282+ * @param int $r The memory difficultly
283+ * @param int $p The parallel difficultly
284+ *
285+ * @throws \Exception
286+ *
287+ * @return string The hashed password
288+ */
289+ public static function scrypthash ($ password , $ salt = false , $ N = 16384 , $ r = 8 , $ p = 1 )
290+ {
291+ // Check if scrypt extension is available
292+ if (!extension_loaded ('scrypt ' )) {
293+ throw new \Exception ('Missing scrypt extension ' );
294+ }
295+
296+ if ($ N == 0 || ($ N & ($ N - 1 )) != 0 ) {
297+ throw new \InvalidArgumentException ("N must be > 0 and a power of 2 " );
298+ }
299+
300+ if ($ N > PHP_INT_MAX / 128 / $ r ) {
301+ throw new \InvalidArgumentException ("Parameter N is too large " );
302+ }
303+
304+ if ($ r > PHP_INT_MAX / 128 / $ p ) {
305+ throw new \InvalidArgumentException ("Parameter r is too large " );
306+ }
307+
308+ if ($ salt === false ) {
309+ $ salt = self ::generateSalt ();
310+ } else {
311+ // Remove dollar signs from the salt, as we use that as a separator.
312+ $ salt = str_replace (array ('+ ' , '$ ' ), array ('. ' , '' ), base64_encode ($ salt ));
313+ }
314+
315+ $ hash = scrypt ($ password , $ salt , $ N , $ r , $ p , self ::$ _keyLength );
316+
317+ return $ N . '$ ' . $ r . '$ ' . $ p . '$ ' . $ salt . '$ ' . $ hash ;
318+ }
319+
320+ /**
321+ * Check a clear text password against a hash
322+ *
323+ * @param string $password The clear text password
324+ * @param string $hash The hashed password
325+ *
326+ * @throws \Exception
327+ *
328+ * @return boolean If the clear text matches
329+ */
330+ public static function scryptcheck ($ password , $ hash )
331+ {
332+ // Check if scrypt extension is available
333+ if (!extension_loaded ('scrypt ' )) {
334+ throw new \Exception ('Missing scrypt extension ' );
335+ }
336+
337+ // Is there actually a hash?
338+ if (!$ hash ) {
339+ return false ;
340+ }
341+
342+ list ($ N , $ r , $ p , $ salt , $ hash ) = explode ('$ ' , $ hash );
343+
344+ // No empty fields?
345+ if (empty ($ N ) or empty ($ r ) or empty ($ p ) or empty ($ salt ) or empty ($ hash )) {
346+ return false ;
347+ }
348+
349+ // Are numeric values numeric?
350+ if (!is_numeric ($ N ) or !is_numeric ($ r ) or !is_numeric ($ p )) {
351+ return false ;
352+ }
353+
354+ $ calculated = scrypt ($ password , $ salt , $ N , $ r , $ p , self ::$ _keyLength );
355+
356+ // Use compareStrings to avoid timeing attacks
357+ return self ::compareStrings ($ hash , $ calculated );
358+ }
359+
360+ /**
361+ * Prevent timing attacks
362+ *
363+ * @param string $expected
364+ * @param string $actual
365+ *
366+ * @return boolean If the two strings match.
367+ */
368+ public static function compareStrings ($ expected , $ actual )
369+ {
370+ $ expected = (string )$ expected ;
371+ $ actual = (string )$ actual ;
372+ $ lenExpected = static ::strlen ($ expected );
373+ $ lenActual = static ::strlen ($ actual );
374+ $ len = min ($ lenExpected , $ lenActual );
375+
376+ $ result = 0 ;
377+ for ($ i = 0 ; $ i < $ len ; $ i ++) {
378+ $ result |= ord ($ expected [$ i ]) ^ ord ($ actual [$ i ]);
379+ }
380+ $ result |= $ lenExpected ^ $ lenActual ;
381+
382+ return ($ result === 0 );
383+ }
384+
216385}
0 commit comments