Added scrypt stuff.
[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:hash($password)
28  *
29  * You can check a password by calling Scrypt:check($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 generateSalt($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 && is_readable('/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 hash($password, $salt = false, $N = 16384, $r = 8, $p = 1)
132     {
133         if ($N == 0 || ($N & ($N - 1)) != 0) {
134             throw new \InvalidArgumentException("N must be > 0 and a power of 2");
135         }
136
137         if ($N > PHP_INT_MAX / 128 / $r) {
138             throw new \InvalidArgumentException("Parameter N is too large");
139         }
140
141         if ($r > PHP_INT_MAX / 128 / $p) {
142             throw new \InvalidArgumentException("Parameter r is too large");
143         }
144
145         if ($salt === false) {
146             $salt = self::generateSalt();
147         } else {
148             // Remove dollar signs from the salt, as we use that as a separator.
149             $salt = str_replace(array('+', '$'), array('.', ''), base64_encode($salt));
150         }
151
152         $hash = scrypt($password, $salt, $N, $r, $p, self::$_keyLength);
153
154         return $N . '$' . $r . '$' . $p . '$' . $salt . '$' . $hash;
155     }
156
157     /**
158      * Check a clear text password against a hash
159      *
160      * @param string $password The clear text password
161      * @param string $hash     The hashed password
162      *
163      * @return boolean If the clear text matches
164      */
165     public static function check($password, $hash)
166     {
167         // Is there actually a hash?
168         if (!$hash) {
169             return false;
170         }
171
172         list ($N, $r, $p, $salt, $hash) = explode('$', $hash);
173
174         // No empty fields?
175         if (empty($N) or empty($r) or empty($p) or empty($salt) or empty($hash)) {
176             return false;
177         }
178
179         // Are numeric values numeric?
180         if (!is_numeric($N) or !is_numeric($r) or !is_numeric($p)) {
181             return false;
182         }
183
184         $calculated = scrypt($password, $salt, $N, $r, $p, self::$_keyLength);
185
186         // Use compareStrings to avoid timeing attacks
187         return self::compareStrings($hash, $calculated);
188     }
189
190     /**
191      * Zend Framework (http://framework.zend.com/)
192      *
193      * @link      http://github.com/zendframework/zf2 for the canonical source repository
194      * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
195      * @license   http://framework.zend.com/license/new-bsd New BSD License
196      *
197      * Compare two strings to avoid timing attacks
198      *
199      * C function memcmp() internally used by PHP, exits as soon as a difference
200      * is found in the two buffers. That makes possible of leaking
201      * timing information useful to an attacker attempting to iteratively guess
202      * the unknown string (e.g. password).
203      *
204      * @param string $expected
205      * @param string $actual
206      *
207      * @return boolean If the two strings match.
208      */
209     public static function compareStrings($expected, $actual)
210     {
211         $expected    = (string) $expected;
212         $actual      = (string) $actual;
213         $lenExpected = static::strlen($expected);
214         $lenActual   = static::strlen($actual);
215         $len         = min($lenExpected, $lenActual);
216
217         $result = 0;
218         for ($i = 0; $i < $len; $i ++) {
219             $result |= ord($expected[$i]) ^ ord($actual[$i]);
220         }
221         $result |= $lenExpected ^ $lenActual;
222
223         return ($result === 0);
224     }
225 }