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 ;-) ).
9 * For license file, original README and CREDITS file, see
10 * inc/classes/third_party/scrypt/.
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.
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
25 * This class abstracts away from scrypt module, allowing for easy use.
27 * You can create a new hash for a password by calling Scrypt:hashScrypt($password)
29 * You can check a password by calling Scrypt:checkScrypt($password, $hash)
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
38 abstract class Scrypt extends BaseFrameworkSystem
43 * @var int The key length
45 private static $_keyLength = 32;
48 * Get the byte-length of the given string
50 * @param string $str Input string
54 protected static function strlen( $str ) {
55 static $isShadowed = null;
57 if ($isShadowed === null) {
58 $isShadowed = extension_loaded('mbstring') &&
59 ini_get('mbstring.func_overload') & 2;
63 return mb_strlen($str, '8bit');
70 * Generates a random salt
72 * @param int $length The length of the salt
74 * @return string The salt
76 public static function generateScryptSalt($length = 8)
79 $buffer_valid = false;
80 if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
81 $buffer = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
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) {
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);
101 if ($read >= $length) {
102 $buffer_valid = true;
105 if (!$buffer_valid || static::strlen($buffer) < $length) {
106 $bl = static::strlen($buffer);
107 for ($i = 0; $i < $length; $i++) {
109 $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
111 $buffer .= chr(mt_rand(0, 255));
115 $salt = str_replace(array('+', '$'), array('.', ''), base64_encode($buffer));
121 * Create a password hash
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
129 * @return string The hashed password
131 public static function hashScrypt($password, $salt = false, $N = 16384, $r = 8, $p = 1)
133 if ($N == 0 || ($N & ($N - 1)) != 0) {
134 throw new \InvalidArgumentException('N must be > 0 and a power of 2');
137 if ($N > PHP_INT_MAX / 128 / $r) {
138 throw new \InvalidArgumentException('Parameter N is too large');
141 if ($r > PHP_INT_MAX / 128 / $p) {
142 throw new \InvalidArgumentException('Parameter r is too large');
145 if ($salt === false) {
146 $salt = self::generateScryptSalt();
148 // Remove dollar signs from the salt, as we use that as a separator.
149 $salt = str_replace(array('+', '$'), array('.', ''), base64_encode($salt));
152 $hash = scrypt($password, $salt, $N, $r, $p, self::$_keyLength);
154 return $N . '$' . $r . '$' . $p . '$' . $salt . '$' . $hash;
158 * Check a clear text password against a hash
160 * @param string $password The clear text password
161 * @param string $hash The hashed password
163 * @return boolean If the clear text matches
165 public static function checkScrypt($password, $hash)
167 // Is there actually a hash?
172 list ($N, $r, $p, $salt, $hash) = explode('$', $hash);
175 if (empty($N) or empty($r) or empty($p) or empty($salt) or empty($hash)) {
179 // Are numeric values numeric?
180 if (!is_numeric($N) or !is_numeric($r) or !is_numeric($p)) {
184 $calculated = scrypt($password, $salt, $N, $r, $p, self::$_keyLength);
186 // Use compareScryptHashes to avoid timeing attacks
187 return self::compareScryptHashes($hash, $calculated);
191 * Zend Framework (http://framework.zend.com/)
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
197 * Compare two strings to avoid timing attacks
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).
204 * @param string $expected
205 * @param string $actual
207 * @return boolean If the two strings match.
209 public static function compareScryptHashes($expected, $actual)
211 $expected = (string) $expected;
212 $actual = (string) $actual;
213 $lenExpected = static::strlen($expected);
214 $lenActual = static::strlen($actual);
215 $len = min($lenExpected, $lenActual);
218 for ($i = 0; $i < $len; $i ++) {
219 $result |= ord($expected[$i]) ^ ord($actual[$i]);
221 $result |= $lenExpected ^ $lenActual;
223 return ($result === 0);