renamed lib-local.php -> lib-lfdb.php because it really loads the "legendary"
[core.git] / inc / main / classes / scrypt / class_Scrypt.php
1 <?php
2 // Own namespace
3 namespace Scrypt;
4
5 // Import framework stuff
6 use CoreFramework\Object\BaseFrameworkSystem;
7
8 /**
9  * This file contains a 'core-d' version of the example helper classes for the
10  * php-scrypt extension. It has been renamed from scrypt.php to this name so the
11  * framework can easily find it. Also it now extends BaseFrameworkSystem, no
12  * other modifications has been done (except this comment ;-) ).
13  *
14  * For license file, original README and CREDITS file, see
15  * inc/main/third_party/scrypt/.
16  *
17  * As with all cryptographic code; it is recommended that you use a tried and
18  * tested library which uses this library; rather than rolling your own.
19  *
20  * PHP version 5
21  *
22  * @category Security
23  * @package  Scrypt
24  * @author   Dominic Black <thephenix@gmail.com>
25  * @license  http://www.opensource.org/licenses/BSD-2-Clause BSD 2-Clause License
26  * @link     http://github.com/DomBlack/php-scrypt
27  */
28
29 /**
30  * This class abstracts away from scrypt module, allowing for easy use.
31  *
32  * You can create a new hash for a password by calling Scrypt:hashScrypt($password)
33  *
34  * You can check a password by calling Scrypt:checkScrypt($password, $hash)
35  *
36  * @category Security
37  * @package  Scrypt
38  * @author   Dominic Black <thephenix@gmail.com>
39  * @author   Roland Haeder <roland@mxchange.org>
40  * @license  http://www.opensource.org/licenses/BSD-2-Clause BSD 2-Clause License
41  * @link     http://github.com/DomBlack/php-scrypt
42  */
43 abstract class Scrypt extends BaseFrameworkSystem
44 {
45
46     /**
47      *
48      * @var int The key length
49      */
50     private static $_keyLength = 32;
51
52     /**
53      * Get the byte-length of the given string
54      *
55      * @param string $str Input string
56      *
57      * @return int
58      */
59     protected static function strlen ($str) {
60         static $isShadowed = null;
61
62         if ($isShadowed === null) {
63             $isShadowed = extension_loaded('mbstring') &&
64                 ini_get('mbstring.func_overload') & 2;
65         }
66
67         if ($isShadowed) {
68             return mb_strlen($str, '8bit');
69         } else {
70             return strlen($str);
71         }
72     }
73
74     /**
75      * Generates a random salt
76      *
77      * @param int $length The length of the salt
78      *
79      * @return string The salt
80      */
81     public static function generateScryptSalt ($length = 8)
82     {
83         $buffer = '';
84         $buffer_valid = false;
85         if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
86             $buffer = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
87             if ($buffer) {
88                 $buffer_valid = true;
89             }
90         }
91         if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
92             $cryptoStrong = false;
93             $buffer = openssl_random_pseudo_bytes($length, $cryptoStrong);
94             if ($buffer && $cryptoStrong) {
95                 $buffer_valid = true;
96             }
97         }
98         if (!$buffer_valid && BaseFrameworkSystem::isReadableFile('/dev/urandom')) {
99             $f = fopen('/dev/urandom', 'r');
100             $read = static::strlen($buffer);
101             while ($read < $length) {
102                 $buffer .= fread($f, $length - $read);
103                 $read = static::strlen($buffer);
104             }
105             fclose($f);
106             if ($read >= $length) {
107                 $buffer_valid = true;
108             }
109         }
110         if (!$buffer_valid || static::strlen($buffer) < $length) {
111             $bl = static::strlen($buffer);
112             for ($i = 0; $i < $length; $i++) {
113                 if ($i < $bl) {
114                     $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
115                 } else {
116                     $buffer .= chr(mt_rand(0, 255));
117                 }
118             }
119         }
120         $salt = str_replace(array('+', '$'), array('.', ''), base64_encode($buffer));
121
122         return $salt;
123     }
124
125     /**
126      * Create a password hash
127      *
128      * @param string $password The clear text password
129      * @param string $salt     The salt to use, or null to generate a random one
130      * @param int    $N        The CPU difficultly (must be a power of 2, > 1)
131      * @param int    $r        The memory difficultly
132      * @param int    $p        The parallel difficultly
133      *
134      * @return string The hashed password
135      */
136     public static function hashScrypt ($password, $salt = false, $N = 16384, $r = 8, $p = 1)
137     {
138         if (!FrameworkFeature::isFeatureAvailable('hubcoin_reward')) {
139             // Feature has been disabled
140             throw new \InvalidArgumentException('Feature "scrypt" disabled.');
141         }
142
143         if ($N == 0 || ($N & ($N - 1)) != 0) {
144             throw new \InvalidArgumentException('N must be > 0 and a power of 2');
145         }
146
147         if ($N > PHP_INT_MAX / 128 / $r) {
148             throw new \InvalidArgumentException('Parameter N is too large');
149         }
150
151         if ($r > PHP_INT_MAX / 128 / $p) {
152             throw new \InvalidArgumentException('Parameter r is too large');
153         }
154
155         if ($salt === false) {
156             $salt = self::generateScryptSalt();
157         } else {
158             // Remove dollar signs from the salt, as we use that as a separator.
159             $salt = str_replace(array('+', '$'), array('.', ''), base64_encode($salt));
160         }
161
162         $hash = scrypt($password, $salt, $N, $r, $p, self::$_keyLength);
163
164         return $N . '$' . $r . '$' . $p . '$' . $salt . '$' . $hash;
165     }
166
167     /**
168      * Check a clear text password against a hash
169      *
170      * @param string $password The clear text password
171      * @param string $hash     The hashed password
172      *
173      * @return boolean If the clear text matches
174      */
175     public static function checkScrypt ($password, $hash)
176     {
177         // Is there actually a hash?
178         if (!$hash) {
179             return false;
180         }
181
182         if (!FrameworkFeature::isFeatureAvailable('hubcoin_reward')) {
183             // Feature has been disabled
184             throw new \InvalidArgumentException('Feature "scrypt" disabled.');
185         }
186
187         list ($N, $r, $p, $salt, $hash) = explode('$', $hash);
188
189         // No empty fields?
190         if (empty($N) or empty($r) or empty($p) or empty($salt) or empty($hash)) {
191             return false;
192         }
193
194         // Are numeric values numeric?
195         if (!is_numeric($N) or !is_numeric($r) or !is_numeric($p)) {
196             return false;
197         }
198
199         $calculated = scrypt($password, $salt, $N, $r, $p, self::$_keyLength);
200
201         // Use compareScryptHashes to avoid timeing attacks
202         return self::compareScryptHashes($hash, $calculated);
203     }
204
205     /**
206      * Zend Framework (http://framework.zend.com/)
207      *
208      * @link      http://github.com/zendframework/zf2 for the canonical source repository
209      * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
210      * @license   http://framework.zend.com/license/new-bsd New BSD License
211      *
212      * Compare two strings to avoid timing attacks
213      *
214      * C function memcmp() internally used by PHP, exits as soon as a difference
215      * is found in the two buffers. That makes possible of leaking
216      * timing information useful to an attacker attempting to iteratively guess
217      * the unknown string (e.g. password).
218      *
219      * @param string $expected
220      * @param string $actual
221      *
222      * @return boolean If the two strings match.
223      */
224     public static function compareScryptHashes ($expected, $actual)
225     {
226         $expected    = (string) $expected;
227         $actual      = (string) $actual;
228         $lenExpected = static::strlen($expected);
229         $lenActual   = static::strlen($actual);
230         $len         = min($lenExpected, $lenActual);
231
232         $result = 0;
233         for ($i = 0; $i < $len; $i ++) {
234             $result |= ord($expected[$i]) ^ ord($actual[$i]);
235         }
236         $result |= $lenExpected ^ $lenActual;
237
238         return ($result === 0);
239     }
240 }