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