4 * This file contains an example helper classes for the php-scrypt extension.
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.
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
19 * This class abstracts away from scrypt module, allowing for easy use.
21 * You can create a new hash for a password by calling Password::hash($password)
23 * You can check a password by calling Password::check($password, $hash)
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
36 * @var int The key length
38 private static $_keyLength = 32;
41 * Get the byte-length of the given string
43 * @param string $str Input string
47 protected static function strlen( $str ) {
48 static $isShadowed = null;
50 if ($isShadowed === null) {
51 $isShadowed = extension_loaded('mbstring') &&
52 ini_get('mbstring.func_overload') & 2;
56 return mb_strlen($str, '8bit');
63 * Generates a random salt
65 * @param int $length The length of the salt
67 * @return string The salt
69 public static function generateSalt($length = 8)
72 $buffer_valid = false;
73 if (function_exists('random_bytes')) {
75 $buffer = random_bytes($length);
77 } catch (Exception $ignored) { }
80 if (!$buffer_valid && function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
81 $buffer = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
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);
95 if ($read >= $length) {
100 if (!$buffer_valid) {
101 throw new Exception("No suitable random number generator available");
104 $salt = str_replace(array('+', '$'), array('.', ''), base64_encode($buffer));
110 * Create a password hash
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
118 * @return string The hashed password
120 public static function hash($password, $salt = false, $N = 16384, $r = 8, $p = 1)
122 if ($N == 0 || ($N & ($N - 1)) != 0) {
123 throw new \InvalidArgumentException("N must be > 0 and a power of 2");
126 if ($N > PHP_INT_MAX / 128 / $r) {
127 throw new \InvalidArgumentException("Parameter N is too large");
130 if ($r > PHP_INT_MAX / 128 / $p) {
131 throw new \InvalidArgumentException("Parameter r is too large");
134 if ($salt === false) {
135 $salt = self::generateSalt();
137 // Remove dollar signs from the salt, as we use that as a separator.
138 $salt = str_replace(array('+', '$'), array('.', ''), base64_encode($salt));
141 $hash = scrypt($password, $salt, $N, $r, $p, self::$_keyLength);
143 return $N . '$' . $r . '$' . $p . '$' . $salt . '$' . $hash;
147 * Check a clear text password against a hash
149 * @param string $password The clear text password
150 * @param string $hash The hashed password
152 * @return boolean If the clear text matches
154 public static function check($password, $hash)
156 // Is there actually a hash?
161 list ($N, $r, $p, $salt, $hash) = explode('$', $hash);
164 if (empty($N) or empty($r) or empty($p) or empty($salt) or empty($hash)) {
168 // Are numeric values numeric?
169 if (!is_numeric($N) or !is_numeric($r) or !is_numeric($p)) {
173 $calculated = scrypt($password, $salt, $N, $r, $p, self::$_keyLength);
175 // Use compareStrings to avoid timeing attacks
176 return self::compareStrings($hash, $calculated);
180 * Zend Framework (http://framework.zend.com/)
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
186 * Compare two strings to avoid timing attacks
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).
193 * @param string $expected
194 * @param string $actual
196 * @return boolean If the two strings match.
198 public static function compareStrings($expected, $actual)
200 $expected = (string) $expected;
201 $actual = (string) $actual;
202 $lenExpected = static::strlen($expected);
203 $lenActual = static::strlen($actual);
204 $len = min($lenExpected, $lenActual);
207 for ($i = 0; $i < $len; $i ++) {
208 $result |= ord($expected[$i]) ^ ord($actual[$i]);
210 $result |= $lenExpected ^ $lenActual;
212 return ($result === 0);