root/ext/standard/password.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. PHP_MINIT_FUNCTION
  2. php_password_get_algo_name
  3. php_password_determine_algo
  4. php_password_salt_is_alphabet
  5. php_password_salt_to64
  6. php_password_make_salt
  7. PHP_FUNCTION
  8. PHP_FUNCTION
  9. PHP_FUNCTION
  10. PHP_FUNCTION

   1 /*
   2    +----------------------------------------------------------------------+
   3    | PHP Version 5                                                        |
   4    +----------------------------------------------------------------------+
   5    | Copyright (c) 1997-2016 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 
  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 
  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 
  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 
  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 
  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 
 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 
 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) {
 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 
 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() */
 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 */
 308 PHP_FUNCTION(password_hash)
 309 {
 310         char hash_format[8], *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                         sprintf(hash_format, "$2y$%02ld$", cost);
 345                         hash_format_len = 7;
 346                 }
 347                 break;
 348                 case PHP_PASSWORD_UNKNOWN:
 349                 default:
 350                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown password hashing algorithm: %ld", algo);
 351                         RETURN_NULL();
 352         }
 353 
 354         if (options && zend_symtable_find(options, "salt", 5, (void**) &option_buffer) == SUCCESS) {
 355                 char *buffer;
 356                 int buffer_len_int = 0;
 357                 size_t buffer_len;
 358                 zval cast_option_buffer = zval_used_for_init;
 359                 switch (Z_TYPE_PP(option_buffer)) {
 360                         case IS_STRING:
 361                                 buffer         = Z_STRVAL_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                                 MAKE_COPY_ZVAL(option_buffer, &cast_option_buffer);
 368                                 convert_to_string(&cast_option_buffer);
 369                                 if (Z_TYPE(cast_option_buffer) == IS_STRING) {
 370                                         buffer         = Z_STRVAL(cast_option_buffer);
 371                                         buffer_len_int = Z_STRLEN(cast_option_buffer);
 372                                         break;
 373                                 }
 374                         case IS_BOOL:
 375                         case IS_NULL:
 376                         case IS_RESOURCE:
 377                         case IS_ARRAY:
 378                         default:
 379                                 if (Z_TYPE(cast_option_buffer) != IS_NULL) {
 380                                         zval_dtor(&cast_option_buffer);
 381                                 }
 382                                 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Non-string salt parameter supplied");
 383                                 RETURN_NULL();
 384                 }
 385                 if (buffer_len_int < 0) {
 386                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Supplied salt is too long");
 387                         if (Z_TYPE(cast_option_buffer) != IS_NULL) {
 388                                 zval_dtor(&cast_option_buffer);
 389                         }
 390                         RETURN_NULL();
 391                 }
 392                 buffer_len = (size_t) buffer_len_int;
 393                 if (buffer_len < required_salt_len) {
 394                         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);
 395                         if (Z_TYPE(cast_option_buffer) != IS_NULL) {
 396                                 zval_dtor(&cast_option_buffer);
 397                         }
 398                         RETURN_NULL();
 399                 } else if (php_password_salt_is_alphabet(buffer, buffer_len) == FAILURE) {
 400                         salt = safe_emalloc(required_salt_len, 1, 1);
 401                         if (php_password_salt_to64(buffer, buffer_len, required_salt_len, salt) == FAILURE) {
 402                                 efree(salt);
 403                                 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Provided salt is too short: %lu", (unsigned long) buffer_len);
 404                                 if (Z_TYPE(cast_option_buffer) != IS_NULL) {
 405                                         zval_dtor(&cast_option_buffer);
 406                                 }
 407                                 RETURN_NULL();
 408                         }
 409                         salt_len = required_salt_len;
 410                 } else {
 411                         salt = safe_emalloc(required_salt_len, 1, 1);
 412                         memcpy(salt, buffer, (int) required_salt_len);
 413                         salt_len = required_salt_len;
 414                 }
 415                 if (Z_TYPE(cast_option_buffer) != IS_NULL) {
 416                         zval_dtor(&cast_option_buffer);
 417                 }
 418         } else {
 419                 salt = safe_emalloc(required_salt_len, 1, 1);
 420                 if (php_password_make_salt(required_salt_len, salt TSRMLS_CC) == FAILURE) {
 421                         efree(salt);
 422                         RETURN_FALSE;
 423                 }
 424                 salt_len = required_salt_len;
 425         }
 426         
 427         salt[salt_len] = 0;
 428 
 429         hash = safe_emalloc(salt_len + hash_format_len, 1, 1);
 430         sprintf(hash, "%s%s", hash_format, salt);
 431         hash[hash_format_len + salt_len] = 0;
 432 
 433         efree(salt);
 434 
 435         /* This cast is safe, since both values are defined here in code and cannot overflow */
 436         hash_len = (int) (hash_format_len + salt_len);
 437 
 438         if (php_crypt(password, password_len, hash, hash_len, &result) == FAILURE) {
 439                 efree(hash);
 440                 RETURN_FALSE;
 441         }
 442 
 443         efree(hash);
 444 
 445         if (strlen(result) < 13) {
 446                 efree(result);
 447                 RETURN_FALSE;
 448         }
 449 
 450         RETURN_STRING(result, 0);
 451 }
 452 /* }}} */
 453 
 454 #endif /* HAVE_CRYPT */
 455 /*
 456  * Local variables:
 457  * tab-width: 4
 458  * c-basic-offset: 4
 459  * End:
 460  * vim600: sw=4 ts=4 fdm=marker
 461  * vim<600: sw=4 ts=4
 462  */

/* [<][>][^][v][top][bottom][index][help] */