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 */ 31abstract class Password 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('mcrypt_create_iv') && !defined('PHALANGER')) { 74 $buffer = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); 75 if ($buffer) { 76 $buffer_valid = true; 77 } 78 } 79 if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { 80 $cryptoStrong = false; 81 $buffer = openssl_random_pseudo_bytes($length, $cryptoStrong); 82 if ($buffer && $cryptoStrong) { 83 $buffer_valid = true; 84 } 85 } 86 if (!$buffer_valid && is_readable('/dev/urandom')) { 87 $f = fopen('/dev/urandom', 'r'); 88 $read = static::strlen($buffer); 89 while ($read < $length) { 90 $buffer .= fread($f, $length - $read); 91 $read = static::strlen($buffer); 92 } 93 fclose($f); 94 if ($read >= $length) { 95 $buffer_valid = true; 96 } 97 } 98 if (!$buffer_valid || static::strlen($buffer) < $length) { 99 $bl = static::strlen($buffer); 100 for ($i = 0; $i < $length; $i++) { 101 if ($i < $bl) { 102 $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); 103 } else { 104 $buffer .= chr(mt_rand(0, 255)); 105 } 106 } 107 } 108 $salt = str_replace(array('+', '$'), array('.', ''), base64_encode($buffer)); 109 110 return $salt; 111 } 112 113 /** 114 * Create a password hash 115 * 116 * @param string $password The clear text password 117 * @param string $salt The salt to use, or null to generate a random one 118 * @param int $N The CPU difficultly (must be a power of 2, > 1) 119 * @param int $r The memory difficultly 120 * @param int $p The parallel difficultly 121 * 122 * @return string The hashed password 123 */ 124 public static function hash($password, $salt = false, $N = 16384, $r = 8, $p = 1) 125 { 126 if ($salt === false) 127 $salt = self::generateSalt(); 128 129 if (function_exists( 'scrypt' )) { 130 if ($N == 0 || ($N & ($N - 1)) != 0) { 131 throw new \InvalidArgumentException("N must be > 0 and a power of 2"); 132 } 133 134 if ($N > PHP_INT_MAX / 128 / $r) { 135 throw new \InvalidArgumentException("Parameter N is too large"); 136 } 137 138 if ($r > PHP_INT_MAX / 128 / $p) { 139 throw new \InvalidArgumentException("Parameter r is too large"); 140 } 141 142 if ($salt !== false) { 143 // Remove dollar signs from the salt, as we use that as a separator. 144 $salt = str_replace(array('+', '$'), array('.', ''), base64_encode($salt)); 145 } 146 147 $hash = scrypt($password, $salt, $N, $r, $p, self::$_keyLength); 148 149 return $N . '$' . $r . '$' . $p . '$' . $salt . '$' . $hash; 150 } 151 152 if (function_exists( 'password_hash' )) { 153 return password_hash($password, PASSWORD_DEFAULT); 154 } 155 156 return crypt($password,$salt); 157 158 } 159 160 /** 161 * Check a clear text password against a hash 162 * 163 * @param string $password The clear text password 164 * @param string $hash The hashed password 165 * 166 * @return boolean If the clear text matches 167 */ 168 public static function check($password, $hash) 169 { 170 // Is there actually a hash? 171 if (!$hash) { 172 return false; 173 } 174 175 $hcheck = false; 176 177 if (function_exists( 'scrypt' )) { 178 list ($N, $r, $p, $salt, $hash) = explode('$', $hash); 179 180 // No empty fields? 181 if (empty($N) or empty($r) or empty($p) or empty($salt) or empty($hash)) { 182 return false; 183 } 184 185 // Are numeric values numeric? 186 if (!is_numeric($N) or !is_numeric($r) or !is_numeric($p)) { 187 return false; 188 } 189 190 $calculated = scrypt($password, $salt, $N, $r, $p, self::$_keyLength); 191 192 // Use compareStrings to avoid timeing attacks 193 $hcheck = self::compareStrings($hash, $calculated); 194 } 195 196 if (!$hcheck && function_exists('password_verify')) { 197 $hcheck = password_verify($password, $hash); 198 } 199 200 if (!$hcheck && function_exists( 'crypt' )) { 201 $hcheck = (hash_equals($hash, crypt($password, $hash))); 202 } 203 204 return $hcheck; 205 } 206 207 /** 208 * Zend Framework (http://framework.zend.com/) 209 * 210 * @link http://github.com/zendframework/zf2 for the canonical source repository 211 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) 212 * @license http://framework.zend.com/license/new-bsd New BSD License 213 * 214 * Compare two strings to avoid timing attacks 215 * 216 * C function memcmp() internally used by PHP, exits as soon as a difference 217 * is found in the two buffers. That makes possible of leaking 218 * timing information useful to an attacker attempting to iteratively guess 219 * the unknown string (e.g. password). 220 * 221 * @param string $expected 222 * @param string $actual 223 * 224 * @return boolean If the two strings match. 225 */ 226 public static function compareStrings($expected, $actual) 227 { 228 $expected = (string) $expected; 229 $actual = (string) $actual; 230 $lenExpected = static::strlen($expected); 231 $lenActual = static::strlen($actual); 232 $len = min($lenExpected, $lenActual); 233 234 $result = 0; 235 for ($i = 0; $i < $len; $i ++) { 236 $result |= ord($expected[$i]) ^ ord($actual[$i]); 237 } 238 $result |= $lenExpected ^ $lenActual; 239 240 return ($result === 0); 241 } 242} 243