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