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