jelix /lib/jelix-legacy/auth/password.php

Language PHP Lines 268
MD5 Hash 45bae19a16461ef1e3b71a858cf18f8b
Repository https://github.com/gmarrot/jelix.git View Raw File
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
<?php
/**
 * A Compatibility library with PHP 5.5's simplified password hashing API.
 *
 * @author Anthony Ferrara <ircmaxell@php.net>
 * @contributor Laurent Jouanneau <laurent@jelix.org>
 * @license http://www.opensource.org/licenses/mit-license.html MIT License
 * @copyright 2012 The Authors
 */

 /**
  * function to check if the password API can be used
  * In some PHP version ( <5.3.7), crypt() with blowfish is vulnerable.
  * But this issue has been fixed on some older PHP version (php 5.3.3 for most of them) in some
  * distro, like Debian squeeze.
  * @see http://www.php.net/security/crypt_blowfish.php
  */
function can_use_password_API () {
    if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
        if (!defined('_PASSWORD_CRYPT_HASH_FORMAT'))
            define('_PASSWORD_CRYPT_HASH_FORMAT', '$2y$%02d$');
        if (!defined('_PASSWORD_CRYPT_PROLOG'))
            define('_PASSWORD_CRYPT_PROLOG', '$2y$');
        return true;
    }
    if (version_compare(PHP_VERSION, '5.3.3', '<')) {
        return false;
    }
    // On debian squeeze, crypt() has been fixed in PHP 5.3.3
    // http://security-tracker.debian.org/tracker/CVE-2011-2483
    // so we can use crypt() securely with $2a$ ($2y$ is not available)
    if (preg_match('/squeeze(\d+)$/', PHP_VERSION, $m)) {
        if (intval($m[1]) >= 4) {
            if (!defined('_PASSWORD_CRYPT_HASH_FORMAT'))
                define('_PASSWORD_CRYPT_HASH_FORMAT', '$2a$%02d$');
            if (!defined('_PASSWORD_CRYPT_PROLOG'))
                define('_PASSWORD_CRYPT_PROLOG', '$2a$');
            return true;
        }
    }
    //FIXME crypt() in PHP 5.3.3 is fixed also on other distro like RedHat.
    // however I don't know if it supports 2y, and how does PHP_VERSION look like
    return false;
}



if (!can_use_password_API()) {
	trigger_error("The Password Compatibility Library requires PHP >= 5.3.7 or PHP >= 5.3.3-7+squeeze4 on debian", E_USER_WARNING);
	// Prevent defining the functions
	return;
}

if (!defined('PASSWORD_BCRYPT')) {

	define('PASSWORD_BCRYPT', 1);
	define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);

	/**
	 * Hash the password using the specified algorithm
	 *
	 * @param string $password The password to hash
	 * @param int    $algo     The algorithm to use (Defined by PASSWORD_* constants)
	 * @param array  $options  The options for the algorithm to use
	 *
	 * @returns string|false The hashed password, or false on error.
	 */
	function password_hash($password, $algo, array $options = array()) {
		if (!function_exists('crypt')) {
			trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
			return null;
		}
		if (!is_string($password)) {
			trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
			return null;
		}
		if (!is_int($algo)) {
			trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
			return null;
		}
		switch ($algo) {
			case PASSWORD_BCRYPT:
				// Note that this is a C constant, but not exposed to PHP, so we don't define it here.
				$cost = 10;
				if (isset($options['cost'])) {
					$cost = $options['cost'];
					if ($cost < 4 || $cost > 31) {
						trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
						return null;
					}
				}
				$required_salt_len = 22;
				$hash_format = sprintf(_PASSWORD_CRYPT_HASH_FORMAT, $cost);
				break;
			default:
				trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
				return null;
		}
		if (isset($options['salt'])) {
			switch (gettype($options['salt'])) {
				case 'NULL':
				case 'boolean':
				case 'integer':
				case 'double':
				case 'string':
					$salt = (string) $options['salt'];
					break;
				case 'object':
					if (method_exists($options['salt'], '__tostring')) {
						$salt = (string) $options['salt'];
						break;
					}
				case 'array':
				case 'resource':
				default:
					trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
					return null;
			}
			if (strlen($salt) < $required_salt_len) {
				trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
				return null;
			} elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
				$salt = str_replace('+', '.', base64_encode($salt));
			}
		} else {
			$buffer = '';
			$raw_length = (int) ($required_salt_len * 3 / 4 + 1);
			$buffer_valid = false;
			if (function_exists('mcrypt_create_iv')) {
				$buffer = mcrypt_create_iv($raw_length, MCRYPT_DEV_URANDOM);
				if ($buffer) {
					$buffer_valid = true;
				}
			}
			if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
				$buffer = openssl_random_pseudo_bytes($raw_length);
				if ($buffer) {
					$buffer_valid = true;
				}
			}
			if (!$buffer_valid && file_exists('/dev/urandom')) {
				$f = @fopen('/dev/urandom', 'r');
				if ($f) {
					$read = strlen($buffer);
					while ($read < $raw_length) {
						$buffer .= fread($f, $raw_length - $read);
						$read = strlen($buffer);
					}
					fclose($f);
					if ($read >= $raw_length) {
						$buffer_valid = true;
					}
				}
			}
			if (!$buffer_valid || strlen($buffer) < $raw_length) {
				$bl = strlen($buffer);
				for ($i = 0; $i < $raw_length; $i++) {
					if ($i < $bl) {
						$buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
					} else {
						$buffer .= chr(mt_rand(0, 255));
					}
				}
			}
			$salt = str_replace('+', '.', base64_encode($buffer));

		}
		$salt = substr($salt, 0, $required_salt_len);

		$hash = $hash_format . $salt;

		$ret = crypt($password, $hash);

		if (!is_string($ret) || strlen($ret) <= 13) {
			return false;
		}

		return $ret;
	}

	/**
	 * Get information about the password hash. Returns an array of the information
	 * that was used to generate the password hash.
	 *
	 * array(
	 *    'algo' => 1,
	 *    'algoName' => 'bcrypt',
	 *    'options' => array(
	 *        'cost' => 10,
	 *    ),
	 * )
	 *
	 * @param string $hash The password hash to extract info from
	 *
	 * @return array The array of information about the hash.
	 */
	function password_get_info($hash) {
		$return = array(
			'algo' => 0,
			'algoName' => 'unknown',
			'options' => array(),
		);
		if (substr($hash, 0, 4) == _PASSWORD_CRYPT_PROLOG && strlen($hash) == 60) {
			$return['algo'] = PASSWORD_BCRYPT;
			$return['algoName'] = 'bcrypt';
			list($cost) = sscanf($hash, _PASSWORD_CRYPT_HASH_FORMAT);
			$return['options']['cost'] = $cost;
		}
		return $return;
	}

	/**
	 * Determine if the password hash needs to be rehashed according to the options provided
	 *
	 * If the answer is true, after validating the password using password_verify, rehash it.
	 *
	 * @param string $hash    The hash to test
	 * @param int    $algo    The algorithm used for new password hashes
	 * @param array  $options The options array passed to password_hash
	 *
	 * @return boolean True if the password needs to be rehashed.
	 */
	function password_needs_rehash($hash, $algo, array $options = array()) {
		$info = password_get_info($hash);
		if ($info['algo'] != $algo) {
			return true;
		}
		switch ($algo) {
			case PASSWORD_BCRYPT:
				$cost = isset($options['cost']) ? $options['cost'] : 10;
				if ($cost != $info['options']['cost']) {
					return true;
				}
				break;
		}
		return false;
	}

	/**
	 * Verify a password against a hash using a timing attack resistant approach
	 *
	 * @param string $password The password to verify
	 * @param string $hash     The hash to verify against
	 *
	 * @return boolean If the password matches the hash
	 */
    function password_verify($password, $hash) {
		if (!function_exists('crypt')) {
			trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
			return false;
		}
		$ret = crypt($password, $hash);
		if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
			return false;
		}

		$status = 0;
		for ($i = 0; $i < strlen($ret); $i++) {
			$status |= (ord($ret[$i]) ^ ord($hash[$i]));
		}

		return $status === 0;
	}
}
Back to Top