xref: /PHP-5.5/ext/standard/password.c (revision 73c1be26)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2015 The PHP Group                                |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Authors: Anthony Ferrara <ircmaxell@php.net>                         |
16    +----------------------------------------------------------------------+
17 */
18 
19 /* $Id$ */
20 
21 #include <stdlib.h>
22 
23 #include "php.h"
24 #if HAVE_CRYPT
25 
26 #include "fcntl.h"
27 #include "php_password.h"
28 #include "php_rand.h"
29 #include "php_crypt.h"
30 #include "base64.h"
31 #include "zend_interfaces.h"
32 #include "info.h"
33 
34 #if PHP_WIN32
35 #include "win32/winutil.h"
36 #endif
37 
PHP_MINIT_FUNCTION(password)38 PHP_MINIT_FUNCTION(password) /* {{{ */
39 {
40 	REGISTER_LONG_CONSTANT("PASSWORD_DEFAULT", PHP_PASSWORD_DEFAULT, CONST_CS | CONST_PERSISTENT);
41 	REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT", PHP_PASSWORD_BCRYPT, CONST_CS | CONST_PERSISTENT);
42 
43 	REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT_DEFAULT_COST", PHP_PASSWORD_BCRYPT_COST, CONST_CS | CONST_PERSISTENT);
44 
45 	return SUCCESS;
46 }
47 /* }}} */
48 
php_password_get_algo_name(const php_password_algo algo)49 static char* php_password_get_algo_name(const php_password_algo algo)
50 {
51 	switch (algo) {
52 		case PHP_PASSWORD_BCRYPT:
53 			return "bcrypt";
54 		case PHP_PASSWORD_UNKNOWN:
55 		default:
56 			return "unknown";
57 	}
58 }
59 
php_password_determine_algo(const char * hash,const size_t len)60 static php_password_algo php_password_determine_algo(const char *hash, const size_t len)
61 {
62 	if (len > 3 && hash[0] == '$' && hash[1] == '2' && hash[2] == 'y' && len == 60) {
63 		return PHP_PASSWORD_BCRYPT;
64 	}
65 
66 	return PHP_PASSWORD_UNKNOWN;
67 }
68 
php_password_salt_is_alphabet(const char * str,const size_t len)69 static int php_password_salt_is_alphabet(const char *str, const size_t len) /* {{{ */
70 {
71 	size_t i = 0;
72 
73 	for (i = 0; i < len; i++) {
74 		if (!((str[i] >= 'A' && str[i] <= 'Z') || (str[i] >= 'a' && str[i] <= 'z') || (str[i] >= '0' && str[i] <= '9') || str[i] == '.' || str[i] == '/')) {
75 			return FAILURE;
76 		}
77 	}
78 	return SUCCESS;
79 }
80 /* }}} */
81 
php_password_salt_to64(const char * str,const size_t str_len,const size_t out_len,char * ret)82 static int php_password_salt_to64(const char *str, const size_t str_len, const size_t out_len, char *ret) /* {{{ */
83 {
84 	size_t pos = 0;
85 	size_t ret_len = 0;
86 	unsigned char *buffer;
87 	if ((int) str_len < 0) {
88 		return FAILURE;
89 	}
90 	buffer = php_base64_encode((unsigned char*) str, (int) str_len, (int*) &ret_len);
91 	if (ret_len < out_len) {
92 		/* Too short of an encoded string generated */
93 		efree(buffer);
94 		return FAILURE;
95 	}
96 	for (pos = 0; pos < out_len; pos++) {
97 		if (buffer[pos] == '+') {
98 			ret[pos] = '.';
99 		} else if (buffer[pos] == '=') {
100 			efree(buffer);
101 			return FAILURE;
102 		} else {
103 			ret[pos] = buffer[pos];
104 		}
105 	}
106 	efree(buffer);
107 	return SUCCESS;
108 }
109 /* }}} */
110 
php_password_make_salt(size_t length,char * ret TSRMLS_DC)111 static int php_password_make_salt(size_t length, char *ret TSRMLS_DC) /* {{{ */
112 {
113 	int buffer_valid = 0;
114 	size_t i, raw_length;
115 	char *buffer;
116 	char *result;
117 
118 	if (length > (INT_MAX / 3)) {
119 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Length is too large to safely generate");
120 		return FAILURE;
121 	}
122 
123 	raw_length = length * 3 / 4 + 1;
124 
125 	buffer = (char *) safe_emalloc(raw_length, 1, 1);
126 
127 #if PHP_WIN32
128 	{
129 		BYTE *iv_b = (BYTE *) buffer;
130 		if (php_win32_get_random_bytes(iv_b, raw_length) == SUCCESS) {
131 			buffer_valid = 1;
132 		}
133 	}
134 #else
135 	{
136 		int fd, n;
137 		size_t read_bytes = 0;
138 		fd = open("/dev/urandom", O_RDONLY);
139 		if (fd >= 0) {
140 			while (read_bytes < raw_length) {
141 				n = read(fd, buffer + read_bytes, raw_length - read_bytes);
142 				if (n < 0) {
143 					break;
144 				}
145 				read_bytes += (size_t) n;
146 			}
147 			close(fd);
148 		}
149 		if (read_bytes >= raw_length) {
150 			buffer_valid = 1;
151 		}
152 	}
153 #endif
154 	if (!buffer_valid) {
155 		for (i = 0; i < raw_length; i++) {
156 			buffer[i] ^= (char) (255.0 * php_rand(TSRMLS_C) / RAND_MAX);
157 		}
158 	}
159 
160 	result = safe_emalloc(length, 1, 1);
161 	if (php_password_salt_to64(buffer, raw_length, length, result) == FAILURE) {
162 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Generated salt too short");
163 		efree(buffer);
164 		efree(result);
165 		return FAILURE;
166 	}
167 	memcpy(ret, result, (int) length);
168 	efree(result);
169 	efree(buffer);
170 	ret[length] = 0;
171 	return SUCCESS;
172 }
173 /* }}} */
174 
PHP_FUNCTION(password_get_info)175 PHP_FUNCTION(password_get_info)
176 {
177 	php_password_algo algo;
178 	int hash_len;
179 	char *hash, *algo_name;
180 	zval *options;
181 
182 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &hash, &hash_len) == FAILURE) {
183 		return;
184 	}
185 
186 	if (hash_len < 0 || (size_t) hash_len < 0) {
187 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Supplied password hash too long to safely identify");
188 		RETURN_FALSE;
189 	}
190 
191 	ALLOC_INIT_ZVAL(options);
192 	array_init(options);
193 
194 	algo = php_password_determine_algo(hash, (size_t) hash_len);
195 	algo_name = php_password_get_algo_name(algo);
196 
197 	switch (algo) {
198 		case PHP_PASSWORD_BCRYPT:
199 			{
200 				long cost = PHP_PASSWORD_BCRYPT_COST;
201 				sscanf(hash, "$2y$%ld$", &cost);
202 				add_assoc_long(options, "cost", cost);
203 			}
204 			break;
205 		case PHP_PASSWORD_UNKNOWN:
206 		default:
207 			break;
208 	}
209 
210 	array_init(return_value);
211 
212 	add_assoc_long(return_value, "algo", algo);
213 	add_assoc_string(return_value, "algoName", algo_name, 1);
214 	add_assoc_zval(return_value, "options", options);
215 }
216 
PHP_FUNCTION(password_needs_rehash)217 PHP_FUNCTION(password_needs_rehash)
218 {
219 	long new_algo = 0;
220 	php_password_algo algo;
221 	int hash_len;
222 	char *hash;
223 	HashTable *options = 0;
224 	zval **option_buffer;
225 
226 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl|H", &hash, &hash_len, &new_algo, &options) == FAILURE) {
227 		return;
228 	}
229 
230 	if (hash_len < 0) {
231 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Supplied password hash too long to safely identify");
232 		RETURN_FALSE;
233 	}
234 
235 	algo = php_password_determine_algo(hash, (size_t) hash_len);
236 
237 	if (algo != new_algo) {
238 		RETURN_TRUE;
239 	}
240 
241 	switch (algo) {
242 		case PHP_PASSWORD_BCRYPT:
243 			{
244 				long new_cost = PHP_PASSWORD_BCRYPT_COST, cost = 0;
245 
246 				if (options && zend_symtable_find(options, "cost", sizeof("cost"), (void **) &option_buffer) == SUCCESS) {
247 					if (Z_TYPE_PP(option_buffer) != IS_LONG) {
248 						zval cast_option_buffer;
249 						MAKE_COPY_ZVAL(option_buffer, &cast_option_buffer);
250 						convert_to_long(&cast_option_buffer);
251 						new_cost = Z_LVAL(cast_option_buffer);
252 						zval_dtor(&cast_option_buffer);
253 					} else {
254 						new_cost = Z_LVAL_PP(option_buffer);
255 					}
256 				}
257 
258 				sscanf(hash, "$2y$%ld$", &cost);
259 				if (cost != new_cost) {
260 					RETURN_TRUE;
261 				}
262 			}
263 			break;
264 		case PHP_PASSWORD_UNKNOWN:
265 		default:
266 			break;
267 	}
268 	RETURN_FALSE;
269 }
270 
271 /* {{{ proto boolean password_make_salt(string password, string hash)
272 Verify a hash created using crypt() or password_hash() */
PHP_FUNCTION(password_verify)273 PHP_FUNCTION(password_verify)
274 {
275 	int status = 0, i;
276 	int password_len, hash_len;
277 	char *ret, *password, *hash;
278 
279 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &password, &password_len, &hash, &hash_len) == FAILURE) {
280 		RETURN_FALSE;
281 	}
282 	if (php_crypt(password, password_len, hash, hash_len, &ret) == FAILURE) {
283 		RETURN_FALSE;
284 	}
285 
286 	if (strlen(ret) != hash_len || hash_len < 13) {
287 		efree(ret);
288 		RETURN_FALSE;
289 	}
290 
291 	/* We're using this method instead of == in order to provide
292 	 * resistence towards timing attacks. This is a constant time
293 	 * equality check that will always check every byte of both
294 	 * values. */
295 	for (i = 0; i < hash_len; i++) {
296 		status |= (ret[i] ^ hash[i]);
297 	}
298 
299 	efree(ret);
300 
301 	RETURN_BOOL(status == 0);
302 
303 }
304 /* }}} */
305 
306 /* {{{ proto string password_hash(string password, int algo, array options = array())
307 Hash a password */
PHP_FUNCTION(password_hash)308 PHP_FUNCTION(password_hash)
309 {
310 	char *hash_format, *hash, *salt, *password, *result;
311 	long algo = 0;
312 	int password_len = 0, hash_len;
313 	size_t salt_len = 0, required_salt_len = 0, hash_format_len;
314 	HashTable *options = 0;
315 	zval **option_buffer;
316 
317 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl|H", &password, &password_len, &algo, &options) == FAILURE) {
318 		return;
319 	}
320 
321 	switch (algo) {
322 		case PHP_PASSWORD_BCRYPT:
323 		{
324 			long cost = PHP_PASSWORD_BCRYPT_COST;
325 
326 			if (options && zend_symtable_find(options, "cost", 5, (void **) &option_buffer) == SUCCESS) {
327 				if (Z_TYPE_PP(option_buffer) != IS_LONG) {
328 					zval cast_option_buffer;
329 					MAKE_COPY_ZVAL(option_buffer, &cast_option_buffer);
330 					convert_to_long(&cast_option_buffer);
331 					cost = Z_LVAL(cast_option_buffer);
332 					zval_dtor(&cast_option_buffer);
333 				} else {
334 					cost = Z_LVAL_PP(option_buffer);
335 				}
336 			}
337 
338 			if (cost < 4 || cost > 31) {
339 				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid bcrypt cost parameter specified: %ld", cost);
340 				RETURN_NULL();
341 			}
342 
343 			required_salt_len = 22;
344 			hash_format = emalloc(8);
345 			sprintf(hash_format, "$2y$%02ld$", cost);
346 			hash_format_len = 7;
347 		}
348 		break;
349 		case PHP_PASSWORD_UNKNOWN:
350 		default:
351 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown password hashing algorithm: %ld", algo);
352 			RETURN_NULL();
353 	}
354 
355 	if (options && zend_symtable_find(options, "salt", 5, (void**) &option_buffer) == SUCCESS) {
356 		char *buffer;
357 		int buffer_len_int = 0;
358 		size_t buffer_len;
359 		switch (Z_TYPE_PP(option_buffer)) {
360 			case IS_STRING:
361 				buffer = estrndup(Z_STRVAL_PP(option_buffer), Z_STRLEN_PP(option_buffer));
362 				buffer_len_int = Z_STRLEN_PP(option_buffer);
363 				break;
364 			case IS_LONG:
365 			case IS_DOUBLE:
366 			case IS_OBJECT: {
367 				zval cast_option_buffer;
368 				MAKE_COPY_ZVAL(option_buffer, &cast_option_buffer);
369 				convert_to_string(&cast_option_buffer);
370 				if (Z_TYPE(cast_option_buffer) == IS_STRING) {
371 					buffer = estrndup(Z_STRVAL(cast_option_buffer), Z_STRLEN(cast_option_buffer));
372 					buffer_len_int = Z_STRLEN(cast_option_buffer);
373 					zval_dtor(&cast_option_buffer);
374 					break;
375 				}
376 				zval_dtor(&cast_option_buffer);
377 			}
378 			case IS_BOOL:
379 			case IS_NULL:
380 			case IS_RESOURCE:
381 			case IS_ARRAY:
382 			default:
383 				efree(hash_format);
384 				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Non-string salt parameter supplied");
385 				RETURN_NULL();
386 		}
387 		if (buffer_len_int < 0) {
388 			efree(hash_format);
389 			efree(buffer);
390 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Supplied salt is too long");
391 		}
392 		buffer_len = (size_t) buffer_len_int;
393 		if (buffer_len < required_salt_len) {
394 			efree(hash_format);
395 			efree(buffer);
396 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Provided salt is too short: %lu expecting %lu", (unsigned long) buffer_len, (unsigned long) required_salt_len);
397 			RETURN_NULL();
398 		} else if (php_password_salt_is_alphabet(buffer, buffer_len) == FAILURE) {
399 			salt = safe_emalloc(required_salt_len, 1, 1);
400 			if (php_password_salt_to64(buffer, buffer_len, required_salt_len, salt) == FAILURE) {
401 				efree(hash_format);
402 				efree(buffer);
403 				efree(salt);
404 				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Provided salt is too short: %lu", (unsigned long) buffer_len);
405 				RETURN_NULL();
406 			}
407 			salt_len = required_salt_len;
408 		} else {
409 			salt = safe_emalloc(required_salt_len, 1, 1);
410 			memcpy(salt, buffer, (int) required_salt_len);
411 			salt_len = required_salt_len;
412 		}
413 		efree(buffer);
414 	} else {
415 		salt = safe_emalloc(required_salt_len, 1, 1);
416 		if (php_password_make_salt(required_salt_len, salt TSRMLS_CC) == FAILURE) {
417 			efree(hash_format);
418 			efree(salt);
419 			RETURN_FALSE;
420 		}
421 		salt_len = required_salt_len;
422 	}
423 
424 	salt[salt_len] = 0;
425 
426 	hash = safe_emalloc(salt_len + hash_format_len, 1, 1);
427 	sprintf(hash, "%s%s", hash_format, salt);
428 	hash[hash_format_len + salt_len] = 0;
429 
430 	efree(hash_format);
431 	efree(salt);
432 
433 	/* This cast is safe, since both values are defined here in code and cannot overflow */
434 	hash_len = (int) (hash_format_len + salt_len);
435 
436 	if (php_crypt(password, password_len, hash, hash_len, &result) == FAILURE) {
437 		efree(hash);
438 		RETURN_FALSE;
439 	}
440 
441 	efree(hash);
442 
443 	if (strlen(result) < 13) {
444 		efree(result);
445 		RETURN_FALSE;
446 	}
447 
448 	RETURN_STRING(result, 0);
449 }
450 /* }}} */
451 
452 #endif /* HAVE_CRYPT */
453 /*
454  * Local variables:
455  * tab-width: 4
456  * c-basic-offset: 4
457  * End:
458  * vim600: sw=4 ts=4 fdm=marker
459  * vim<600: sw=4 ts=4
460  */
461