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