summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSara Golemon <pollita@php.net>2018-10-15 14:58:34 -0400
committerSara Golemon <pollita@php.net>2018-11-20 17:26:55 -0500
commit534df87c9e3c28001986e70844e0ad04e5708d3d (patch)
tree0d0175681d4c00ca3493e9cda3711544ab324da5
parentf4faa69b575bd70aa6877d7c295834f5be1f133c (diff)
downloadphp-git-534df87c9e3c28001986e70844e0ad04e5708d3d.tar.gz
Implement password mechanism registry
RFC: https://wiki.php.net/rfc/password_registry
-rw-r--r--ext/standard/basic_functions.c3
-rw-r--r--ext/standard/password.c961
-rw-r--r--ext/standard/php_password.h31
-rw-r--r--ext/standard/tests/password/password_get_info.phpt8
-rw-r--r--ext/standard/tests/password/password_get_info_argon2.phpt4
-rw-r--r--ext/standard/tests/password/password_hash.phpt13
-rw-r--r--ext/standard/tests/password/password_hash_argon2.phpt28
-rw-r--r--ext/standard/tests/password/password_hash_error.phpt4
-rw-r--r--ext/standard/tests/password/password_needs_rehash.phpt8
-rw-r--r--ext/standard/tests/password/password_needs_rehash_error.phpt10
10 files changed, 633 insertions, 437 deletions
diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
index 171ab79c8b..9aeb2fb3a9 100644
--- a/ext/standard/basic_functions.c
+++ b/ext/standard/basic_functions.c
@@ -1860,6 +1860,8 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_password_verify, 0, 0, 2)
ZEND_ARG_INFO(0, password)
ZEND_ARG_INFO(0, hash)
ZEND_END_ARG_INFO()
+ZEND_BEGIN_ARG_INFO(arginfo_password_algos, 0)
+ZEND_END_ARG_INFO();
/* }}} */
/* {{{ proc_open.c */
#ifdef PHP_CAN_SUPPORT_PROC_OPEN
@@ -2926,6 +2928,7 @@ static const zend_function_entry basic_functions[] = { /* {{{ */
PHP_FE(password_get_info, arginfo_password_get_info)
PHP_FE(password_needs_rehash, arginfo_password_needs_rehash)
PHP_FE(password_verify, arginfo_password_verify)
+ PHP_FE(password_algos, arginfo_password_algos)
PHP_FE(convert_uuencode, arginfo_convert_uuencode)
PHP_FE(convert_uudecode, arginfo_convert_uudecode)
diff --git a/ext/standard/password.c b/ext/standard/password.c
index 5cf0d397f5..36c14b8ef2 100644
--- a/ext/standard/password.c
+++ b/ext/standard/password.c
@@ -37,61 +37,19 @@
#include "win32/winutil.h"
#endif
-PHP_MINIT_FUNCTION(password) /* {{{ */
-{
- REGISTER_LONG_CONSTANT("PASSWORD_DEFAULT", PHP_PASSWORD_DEFAULT, CONST_CS | CONST_PERSISTENT);
- REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT", PHP_PASSWORD_BCRYPT, CONST_CS | CONST_PERSISTENT);
-#if HAVE_ARGON2LIB
- REGISTER_LONG_CONSTANT("PASSWORD_ARGON2I", PHP_PASSWORD_ARGON2I, CONST_CS | CONST_PERSISTENT);
- REGISTER_LONG_CONSTANT("PASSWORD_ARGON2ID", PHP_PASSWORD_ARGON2ID, CONST_CS | CONST_PERSISTENT);
-#endif
-
- REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT_DEFAULT_COST", PHP_PASSWORD_BCRYPT_COST, CONST_CS | CONST_PERSISTENT);
-#if HAVE_ARGON2LIB
- REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_MEMORY_COST", PHP_PASSWORD_ARGON2_MEMORY_COST, CONST_CS | CONST_PERSISTENT);
- REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_TIME_COST", PHP_PASSWORD_ARGON2_TIME_COST, CONST_CS | CONST_PERSISTENT);
- REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_THREADS", PHP_PASSWORD_ARGON2_THREADS, CONST_CS | CONST_PERSISTENT);
-#endif
-
- return SUCCESS;
-}
-/* }}} */
+static zend_array php_password_algos;
-static zend_string* php_password_get_algo_name(const php_password_algo algo)
-{
- switch (algo) {
- case PHP_PASSWORD_BCRYPT:
- return zend_string_init("bcrypt", sizeof("bcrypt") - 1, 0);
-#if HAVE_ARGON2LIB
- case PHP_PASSWORD_ARGON2I:
- return zend_string_init("argon2i", sizeof("argon2i") - 1, 0);
- case PHP_PASSWORD_ARGON2ID:
- return zend_string_init("argon2id", sizeof("argon2id") - 1, 0);
-#endif
- case PHP_PASSWORD_UNKNOWN:
- default:
- return zend_string_init("unknown", sizeof("unknown") - 1, 0);
+int php_password_algo_register(const char *ident, const php_password_algo *algo) {
+ zval zalgo;
+ ZVAL_PTR(&zalgo, (php_password_algo*)algo);
+ if (zend_hash_str_add(&php_password_algos, ident, strlen(ident), &zalgo)) {
+ return SUCCESS;
}
+ return FAILURE;
}
-static php_password_algo php_password_determine_algo(const zend_string *hash)
-{
- const char *h = ZSTR_VAL(hash);
- const size_t len = ZSTR_LEN(hash);
- if (len == 60 && h[0] == '$' && h[1] == '2' && h[2] == 'y') {
- return PHP_PASSWORD_BCRYPT;
- }
-#if HAVE_ARGON2LIB
- if (len >= sizeof("$argon2id$")-1 && !memcmp(h, "$argon2id$", sizeof("$argon2id$")-1)) {
- return PHP_PASSWORD_ARGON2ID;
- }
-
- if (len >= sizeof("$argon2i$")-1 && !memcmp(h, "$argon2i$", sizeof("$argon2i$")-1)) {
- return PHP_PASSWORD_ARGON2I;
- }
-#endif
-
- return PHP_PASSWORD_UNKNOWN;
+void php_password_algo_unregister(const char *ident) {
+ zend_hash_str_del(&php_password_algos, ident, strlen(ident));
}
static int php_password_salt_is_alphabet(const char *str, const size_t len) /* {{{ */
@@ -164,225 +122,12 @@ static zend_string* php_password_make_salt(size_t length) /* {{{ */
}
/* }}} */
-#if HAVE_ARGON2LIB
-static void extract_argon2_parameters(const php_password_algo algo, const zend_string *hash,
- zend_long *v, zend_long *memory_cost,
- zend_long *time_cost, zend_long *threads) /* {{{ */
-{
- if (algo == PHP_PASSWORD_ARGON2ID) {
- sscanf(ZSTR_VAL(hash), "$%*[argon2id]$v=" ZEND_LONG_FMT "$m=" ZEND_LONG_FMT ",t=" ZEND_LONG_FMT ",p=" ZEND_LONG_FMT, v, memory_cost, time_cost, threads);
- } else if (algo == PHP_PASSWORD_ARGON2I) {
- sscanf(ZSTR_VAL(hash), "$%*[argon2i]$v=" ZEND_LONG_FMT "$m=" ZEND_LONG_FMT ",t=" ZEND_LONG_FMT ",p=" ZEND_LONG_FMT, v, memory_cost, time_cost, threads);
- }
-
- return;
-}
-#endif
-
-/* {{{ proto array password_get_info(string $hash)
-Retrieves information about a given hash */
-PHP_FUNCTION(password_get_info)
-{
- php_password_algo algo;
- zend_string *hash, *algo_name;
- zval options;
-
- ZEND_PARSE_PARAMETERS_START(1, 1)
- Z_PARAM_STR(hash)
- ZEND_PARSE_PARAMETERS_END();
-
- array_init(&options);
-
- algo = php_password_determine_algo(hash);
- algo_name = php_password_get_algo_name(algo);
-
- switch (algo) {
- case PHP_PASSWORD_BCRYPT:
- {
- zend_long cost = PHP_PASSWORD_BCRYPT_COST;
- sscanf(ZSTR_VAL(hash), "$2y$" ZEND_LONG_FMT "$", &cost);
- add_assoc_long(&options, "cost", cost);
- }
- break;
-#if HAVE_ARGON2LIB
- case PHP_PASSWORD_ARGON2I:
- case PHP_PASSWORD_ARGON2ID:
- {
- zend_long v = 0;
- zend_long memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST;
- zend_long time_cost = PHP_PASSWORD_ARGON2_TIME_COST;
- zend_long threads = PHP_PASSWORD_ARGON2_THREADS;
-
- extract_argon2_parameters(algo, hash, &v, &memory_cost, &time_cost, &threads);
-
- add_assoc_long(&options, "memory_cost", memory_cost);
- add_assoc_long(&options, "time_cost", time_cost);
- add_assoc_long(&options, "threads", threads);
- }
- break;
-#endif
- case PHP_PASSWORD_UNKNOWN:
- default:
- break;
- }
-
- array_init(return_value);
-
- add_assoc_long(return_value, "algo", algo);
- add_assoc_str(return_value, "algoName", algo_name);
- add_assoc_zval(return_value, "options", &options);
-}
-/** }}} */
-
-/* {{{ proto bool password_needs_rehash(string $hash, int $algo[, array $options])
-Determines if a given hash requires re-hashing based upon parameters */
-PHP_FUNCTION(password_needs_rehash)
-{
- zend_long new_algo = 0;
- php_password_algo algo;
- zend_string *hash;
- HashTable *options = 0;
- zval *option_buffer;
-
- ZEND_PARSE_PARAMETERS_START(2, 3)
- Z_PARAM_STR(hash)
- Z_PARAM_LONG(new_algo)
- Z_PARAM_OPTIONAL
- Z_PARAM_ARRAY_OR_OBJECT_HT(options)
- ZEND_PARSE_PARAMETERS_END();
-
- algo = php_password_determine_algo(hash);
-
- if ((zend_long)algo != new_algo) {
- RETURN_TRUE;
- }
-
- switch (algo) {
- case PHP_PASSWORD_BCRYPT:
- {
- zend_long new_cost = PHP_PASSWORD_BCRYPT_COST, cost = 0;
-
- if (options && (option_buffer = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
- new_cost = zval_get_long(option_buffer);
- }
-
- sscanf(ZSTR_VAL(hash), "$2y$" ZEND_LONG_FMT "$", &cost);
- if (cost != new_cost) {
- RETURN_TRUE;
- }
- }
- break;
-#if HAVE_ARGON2LIB
- case PHP_PASSWORD_ARGON2I:
- case PHP_PASSWORD_ARGON2ID:
- {
- zend_long v = 0;
- zend_long new_memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST, memory_cost = 0;
- zend_long new_time_cost = PHP_PASSWORD_ARGON2_TIME_COST, time_cost = 0;
- zend_long new_threads = PHP_PASSWORD_ARGON2_THREADS, threads = 0;
-
- if (options && (option_buffer = zend_hash_str_find(options, "memory_cost", sizeof("memory_cost")-1)) != NULL) {
- new_memory_cost = zval_get_long(option_buffer);
- }
-
- if (options && (option_buffer = zend_hash_str_find(options, "time_cost", sizeof("time_cost")-1)) != NULL) {
- new_time_cost = zval_get_long(option_buffer);
- }
-
- if (options && (option_buffer = zend_hash_str_find(options, "threads", sizeof("threads")-1)) != NULL) {
- new_threads = zval_get_long(option_buffer);
- }
-
- extract_argon2_parameters(algo, hash, &v, &memory_cost, &time_cost, &threads);
-
- if (new_time_cost != time_cost || new_memory_cost != memory_cost || new_threads != threads) {
- RETURN_TRUE;
- }
- }
- break;
-#endif
- case PHP_PASSWORD_UNKNOWN:
- default:
- break;
- }
- RETURN_FALSE;
-}
-/* }}} */
-
-/* {{{ proto bool password_verify(string password, string hash)
-Verify a hash created using crypt() or password_hash() */
-PHP_FUNCTION(password_verify)
-{
- zend_string *password, *hash;
- php_password_algo algo;
-
- ZEND_PARSE_PARAMETERS_START(2, 2)
- Z_PARAM_STR(password)
- Z_PARAM_STR(hash)
- ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
-
- algo = php_password_determine_algo(hash);
-
- switch(algo) {
-#if HAVE_ARGON2LIB
- case PHP_PASSWORD_ARGON2I:
- case PHP_PASSWORD_ARGON2ID:
- {
- argon2_type type;
- if (algo == PHP_PASSWORD_ARGON2ID) {
- type = Argon2_id;
- } else if (algo == PHP_PASSWORD_ARGON2I) {
- type = Argon2_i;
- }
- RETURN_BOOL(ARGON2_OK == argon2_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password), type));
- }
- break;
-#endif
- case PHP_PASSWORD_BCRYPT:
- case PHP_PASSWORD_UNKNOWN:
- default:
- {
- size_t i;
- int status = 0;
- zend_string *ret = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);
-
- if (!ret) {
- RETURN_FALSE;
- }
-
- if (ZSTR_LEN(ret) != ZSTR_LEN(hash) || ZSTR_LEN(hash) < 13) {
- zend_string_free(ret);
- RETURN_FALSE;
- }
-
- /* We're using this method instead of == in order to provide
- * resistance towards timing attacks. This is a constant time
- * equality check that will always check every byte of both
- * values. */
- for (i = 0; i < ZSTR_LEN(hash); i++) {
- status |= (ZSTR_VAL(ret)[i] ^ ZSTR_VAL(hash)[i]);
- }
-
- zend_string_free(ret);
-
- RETURN_BOOL(status == 0);
- }
- }
-
- RETURN_FALSE;
-}
-/* }}} */
-
-static zend_string* php_password_get_salt(zval *return_value, size_t required_salt_len, HashTable *options) {
+static zend_string* php_password_get_salt(zval *unused_, size_t required_salt_len, HashTable *options) {
zend_string *buffer;
zval *option_buffer;
if (!options || !(option_buffer = zend_hash_str_find(options, "salt", sizeof("salt") - 1))) {
- buffer = php_password_make_salt(required_salt_len);
- if (!buffer) {
- RETVAL_FALSE;
- }
- return buffer;
+ return php_password_make_salt(required_salt_len);
}
php_error_docref(NULL, E_DEPRECATED, "Use of the 'salt' option to password_hash is deprecated");
@@ -439,162 +184,556 @@ static zend_string* php_password_get_salt(zval *return_value, size_t required_sa
}
}
-/* {{{ proto string password_hash(string password, int algo[, array options = array()])
+/* bcrypt implementation */
+
+static zend_bool php_password_bcrypt_valid(const zend_string *hash) {
+ const char *h = ZSTR_VAL(hash);
+ return (ZSTR_LEN(hash) == 60) &&
+ (h[0] == '$') && (h[1] == '2') && (h[2] == 'y');
+}
+
+static int php_password_bcrypt_get_info(zval *return_value, const zend_string *hash) {
+ zend_long cost = PHP_PASSWORD_BCRYPT_COST;
+
+ if (!php_password_bcrypt_valid(hash)) {
+ /* Should never get called this way. */
+ return FAILURE;
+ }
+
+ sscanf(ZSTR_VAL(hash), "$2y$" ZEND_LONG_FMT "$", &cost);
+ add_assoc_long(return_value, "cost", cost);
+
+ return SUCCESS;
+}
+
+static zend_bool php_password_bcrypt_needs_rehash(const zend_string *hash, zend_array *options) {
+ zval *znew_cost;
+ zend_long old_cost = PHP_PASSWORD_BCRYPT_COST;
+ zend_long new_cost = PHP_PASSWORD_BCRYPT_COST;
+
+ if (!php_password_bcrypt_valid(hash)) {
+ /* Should never get called this way. */
+ return 1;
+ }
+
+ sscanf(ZSTR_VAL(hash), "$2y$" ZEND_LONG_FMT "$", &old_cost);
+ if (options && (znew_cost = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
+ new_cost = zval_get_long(znew_cost);
+ }
+
+ return old_cost != new_cost;
+}
+
+static zend_bool php_password_bcrypt_verify(const zend_string *password, const zend_string *hash) {
+ size_t i;
+ int status = 0;
+ zend_string *ret = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);
+
+ if (!ret) {
+ return 0;
+ }
+
+ if (ZSTR_LEN(ret) != ZSTR_LEN(hash) || ZSTR_LEN(hash) < 13) {
+ zend_string_free(ret);
+ return 0;
+ }
+
+ /* We're using this method instead of == in order to provide
+ * resistance towards timing attacks. This is a constant time
+ * equality check that will always check every byte of both
+ * values. */
+ for (i = 0; i < ZSTR_LEN(hash); i++) {
+ status |= (ZSTR_VAL(ret)[i] ^ ZSTR_VAL(hash)[i]);
+ }
+
+ zend_string_free(ret);
+ return status == 0;
+}
+
+static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_array *options) {
+ char hash_format[10];
+ size_t hash_format_len;
+ zend_string *result, *hash, *salt;
+ zval *zcost;
+ zend_long cost = PHP_PASSWORD_BCRYPT_COST;
+
+ if (options && (zcost = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
+ cost = zval_get_long(zcost);
+ }
+
+ if (cost < 4 || cost > 31) {
+ php_error_docref(NULL, E_WARNING, "Invalid bcrypt cost parameter specified: " ZEND_LONG_FMT, cost);
+ return NULL;
+ }
+
+ hash_format_len = snprintf(hash_format, sizeof(hash_format), "$2y$%02" ZEND_LONG_FMT_SPEC "$", cost);
+ if (!(salt = php_password_get_salt(NULL, Z_UL(22), options))) {
+ return NULL;
+ }
+ ZSTR_VAL(salt)[ZSTR_LEN(salt)] = 0;
+
+ hash = zend_string_alloc(ZSTR_LEN(salt) + hash_format_len, 0);
+ sprintf(ZSTR_VAL(hash), "%s%s", hash_format, ZSTR_VAL(salt));
+ ZSTR_VAL(hash)[hash_format_len + ZSTR_LEN(salt)] = 0;
+
+ zend_string_release_ex(salt, 0);
+
+ /* This cast is safe, since both values are defined here in code and cannot overflow */
+ result = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);
+ zend_string_release_ex(hash, 0);
+
+ if (!result) {
+ return NULL;
+ }
+
+ if (ZSTR_LEN(result) < 13) {
+ zend_string_free(result);
+ return NULL;
+ }
+
+ return result;
+}
+
+const php_password_algo php_password_algo_bcrypt = {
+ "bcrypt",
+ php_password_bcrypt_hash,
+ php_password_bcrypt_verify,
+ php_password_bcrypt_needs_rehash,
+ php_password_bcrypt_get_info,
+ php_password_bcrypt_valid,
+};
+
+
+#if HAVE_ARGON2LIB
+/* argon2i/argon2id shared implementation */
+
+static int extract_argon2_parameters(const zend_string *hash,
+ zend_long *v, zend_long *memory_cost,
+ zend_long *time_cost, zend_long *threads) /* {{{ */
+{
+ const char *p = ZSTR_VAL(hash);
+ if (!hash || (ZSTR_LEN(hash) < sizeof("$argon2id$"))) {
+ return FAILURE;
+ }
+ if (!memcmp(p, "$argon2i$", sizeof("$argon2i$") - 1)) {
+ p += sizeof("$argon2i$") - 1;
+ } else if (!memcmp(p, "$argon2id$", sizeof("$argon2id$") - 1)) {
+ p += sizeof("$argon2id$") - 1;
+ } else {
+ return FAILURE;
+ }
+
+ sscanf(p, "v=" ZEND_LONG_FMT "$m=" ZEND_LONG_FMT ",t=" ZEND_LONG_FMT ",p=" ZEND_LONG_FMT,
+ v, memory_cost, time_cost, threads);
+
+ return SUCCESS;
+}
+/* }}} */
+
+static int php_password_argon2_get_info(zval *return_value, const zend_string *hash) {
+ zend_long v = 0;
+ zend_long memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST;
+ zend_long time_cost = PHP_PASSWORD_ARGON2_TIME_COST;
+ zend_long threads = PHP_PASSWORD_ARGON2_THREADS;
+
+ extract_argon2_parameters(hash, &v, &memory_cost, &time_cost, &threads);
+
+ add_assoc_long(return_value, "memory_cost", memory_cost);
+ add_assoc_long(return_value, "time_cost", time_cost);
+ add_assoc_long(return_value, "threads", threads);
+
+ return SUCCESS;
+}
+
+static zend_bool php_password_argon2_needs_rehash(const zend_string *hash, zend_array *options) {
+ zend_long v = 0;
+ zend_long new_memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST, memory_cost = 0;
+ zend_long new_time_cost = PHP_PASSWORD_ARGON2_TIME_COST, time_cost = 0;
+ zend_long new_threads = PHP_PASSWORD_ARGON2_THREADS, threads = 0;
+ zval *option_buffer;
+
+ if (options && (option_buffer = zend_hash_str_find(options, "memory_cost", sizeof("memory_cost")-1)) != NULL) {
+ new_memory_cost = zval_get_long(option_buffer);
+ }
+
+ if (options && (option_buffer = zend_hash_str_find(options, "time_cost", sizeof("time_cost")-1)) != NULL) {
+ new_time_cost = zval_get_long(option_buffer);
+ }
+
+ if (options && (option_buffer = zend_hash_str_find(options, "threads", sizeof("threads")-1)) != NULL) {
+ new_threads = zval_get_long(option_buffer);
+ }
+
+ extract_argon2_parameters(hash, &v, &memory_cost, &time_cost, &threads);
+
+ return (new_time_cost != time_cost) ||
+ (new_memory_cost != memory_cost) ||
+ (new_threads != threads);
+}
+
+static zend_string *php_password_argon2_hash(const zend_string *password, zend_array *options, argon2_type type) {
+ zval *option_buffer;
+ zend_string *salt, *out, *encoded;
+ size_t time_cost = PHP_PASSWORD_ARGON2_TIME_COST;
+ size_t memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST;
+ size_t threads = PHP_PASSWORD_ARGON2_THREADS;
+ size_t encoded_len;
+ int status = 0;
+
+ if (options && (option_buffer = zend_hash_str_find(options, "memory_cost", sizeof("memory_cost")-1)) != NULL) {
+ memory_cost = zval_get_long(option_buffer);
+ }
+
+ if (memory_cost > ARGON2_MAX_MEMORY || memory_cost < ARGON2_MIN_MEMORY) {
+ php_error_docref(NULL, E_WARNING, "Memory cost is outside of allowed memory range");
+ return NULL;
+ }
+
+ if (options && (option_buffer = zend_hash_str_find(options, "time_cost", sizeof("time_cost")-1)) != NULL) {
+ time_cost = zval_get_long(option_buffer);
+ }
+
+ if (time_cost > ARGON2_MAX_TIME || time_cost < ARGON2_MIN_TIME) {
+ php_error_docref(NULL, E_WARNING, "Time cost is outside of allowed time range");
+ return NULL;
+ }
+
+ if (options && (option_buffer = zend_hash_str_find(options, "threads", sizeof("threads")-1)) != NULL) {
+ threads = zval_get_long(option_buffer);
+ }
+
+ if (threads > ARGON2_MAX_LANES || threads == 0) {
+ php_error_docref(NULL, E_WARNING, "Invalid number of threads");
+ return NULL;
+ }
+
+ if (!(salt = php_password_get_salt(NULL, Z_UL(16), options))) {
+ return NULL;
+ }
+
+ out = zend_string_alloc(32, 0);
+ encoded_len = argon2_encodedlen(
+ time_cost,
+ memory_cost,
+ threads,
+ (uint32_t)ZSTR_LEN(salt),
+ ZSTR_LEN(out),
+ type
+ );
+
+ encoded = zend_string_alloc(encoded_len - 1, 0);
+ status = argon2_hash(
+ time_cost,
+ memory_cost,
+ threads,
+ ZSTR_VAL(password),
+ ZSTR_LEN(password),
+ ZSTR_VAL(salt),
+ ZSTR_LEN(salt),
+ ZSTR_VAL(out),
+ ZSTR_LEN(out),
+ ZSTR_VAL(encoded),
+ encoded_len,
+ type,
+ ARGON2_VERSION_NUMBER
+ );
+
+ zend_string_release_ex(out, 0);
+ zend_string_release_ex(salt, 0);
+
+ if (status != ARGON2_OK) {
+ zend_string_efree(encoded);
+ php_error_docref(NULL, E_WARNING, "%s", argon2_error_message(status));
+ return NULL;
+ }
+
+ ZSTR_VAL(encoded)[ZSTR_LEN(encoded)] = 0;
+ return encoded;
+}
+
+/* argon2i specific methods */
+
+static zend_bool php_password_argon2i_verify(const zend_string *password, const zend_string *hash) {
+ return ARGON2_OK == argon2_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password), Argon2_i);
+}
+
+static zend_string *php_password_argon2i_hash(const zend_string *password, zend_array *options) {
+ return php_password_argon2_hash(password, options, Argon2_i);
+}
+
+const php_password_algo php_password_algo_argon2i = {
+ "argon2i",
+ php_password_argon2i_hash,
+ php_password_argon2i_verify,
+ php_password_argon2_needs_rehash,
+ php_password_argon2_get_info,
+ NULL,
+};
+
+/* argon2id specific methods */
+
+static zend_bool php_password_argon2id_verify(const zend_string *password, const zend_string *hash) {
+ return ARGON2_OK == argon2_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password), Argon2_id);
+}
+
+static zend_string *php_password_argon2id_hash(const zend_string *password, zend_array *options) {
+ return php_password_argon2_hash(password, options, Argon2_id);
+}
+
+const php_password_algo php_password_algo_argon2id = {
+ "argon2id",
+ php_password_argon2id_hash,
+ php_password_argon2id_verify,
+ php_password_argon2_needs_rehash,
+ php_password_argon2_get_info,
+ NULL,
+};
+#endif
+
+PHP_MINIT_FUNCTION(password) /* {{{ */
+{
+ zend_hash_init(&php_password_algos, 4, NULL, ZVAL_PTR_DTOR, 1);
+ REGISTER_NULL_CONSTANT("PASSWORD_DEFAULT", CONST_CS | CONST_PERSISTENT);
+
+ if (FAILURE == php_password_algo_register("2y", &php_password_algo_bcrypt)) {
+ return FAILURE;
+ }
+ REGISTER_STRING_CONSTANT("PASSWORD_BCRYPT", "2y", CONST_CS | CONST_PERSISTENT);
+
+#if HAVE_ARGON2LIB
+ if (FAILURE == php_password_algo_register("argon2i", &php_password_algo_argon2i)) {
+ return FAILURE;
+ }
+ REGISTER_STRING_CONSTANT("PASSWORD_ARGON2I", "argon2i", CONST_CS | CONST_PERSISTENT);
+
+ if (FAILURE == php_password_algo_register("argon2id", &php_password_algo_argon2id)) {
+ return FAILURE;
+ }
+ REGISTER_STRING_CONSTANT("PASSWORD_ARGON2ID", "argon2id", CONST_CS | CONST_PERSISTENT);
+#endif
+
+ REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT_DEFAULT_COST", PHP_PASSWORD_BCRYPT_COST, CONST_CS | CONST_PERSISTENT);
+#if HAVE_ARGON2LIB
+ REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_MEMORY_COST", PHP_PASSWORD_ARGON2_MEMORY_COST, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_TIME_COST", PHP_PASSWORD_ARGON2_TIME_COST, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_THREADS", PHP_PASSWORD_ARGON2_THREADS, CONST_CS | CONST_PERSISTENT);
+#endif
+
+ return SUCCESS;
+}
+/* }}} */
+
+const php_password_algo* php_password_algo_default() {
+ return &php_password_algo_bcrypt;
+}
+
+const php_password_algo* php_password_algo_find(const zend_string *ident) {
+ zval *tmp;
+
+ if (!ident) {
+ return NULL;
+ }
+
+ tmp = zend_hash_find(&php_password_algos, (zend_string*)ident);
+ if (!tmp || (Z_TYPE_P(tmp) != IS_PTR)) {
+ return NULL;
+ }
+
+ return Z_PTR_P(tmp);
+}
+
+static const php_password_algo* php_password_algo_find_zval_ex(zval *arg, const php_password_algo* default_algo) {
+ if (!arg || (Z_TYPE_P(arg) == IS_NULL)) {
+ return default_algo;
+ }
+
+ if (Z_TYPE_P(arg) == IS_LONG) {
+ switch (Z_LVAL_P(arg)) {
+ case 0: return default_algo;
+ case 1: return &php_password_algo_bcrypt;
+#if HAVE_ARGON2LIB
+ case 2: return &php_password_algo_argon2i;
+ case 3: return &php_password_algo_argon2id;
+#endif
+ }
+ return NULL;
+ }
+
+ if (Z_TYPE_P(arg) != IS_STRING) {
+ return NULL;
+ }
+
+ return php_password_algo_find(Z_STR_P(arg));
+}
+static const php_password_algo* php_password_algo_find_zval(zval *arg) {
+ return php_password_algo_find_zval_ex(arg, php_password_algo_default());
+}
+
+zend_string *php_password_algo_extract_ident(const zend_string* hash) {
+ const char *ident, *ident_end;
+
+ if (!hash || ZSTR_LEN(hash) < 3) {
+ /* Minimum prefix: "$x$" */
+ return NULL;
+ }
+
+ ident = ZSTR_VAL(hash) + 1;
+ ident_end = strchr(ident, '$');
+ if (!ident_end) {
+ /* No terminating '$' */
+ return NULL;
+ }
+
+ return zend_string_init(ident, ident_end - ident, 0);
+}
+
+const php_password_algo* php_password_algo_identify_ex(const zend_string* hash, const php_password_algo *default_algo) {
+ const php_password_algo *algo;
+ zend_string *ident = php_password_algo_extract_ident(hash);
+
+ if (!ident) {
+ return default_algo;
+ }
+
+ algo = php_password_algo_find(ident);
+ zend_string_release(ident);
+ return (!algo || (algo->valid && !algo->valid(hash))) ? default_algo : algo;
+}
+
+/* {{{ proto array password_get_info(string $hash)
+Retrieves information about a given hash */
+PHP_FUNCTION(password_get_info)
+{
+ const php_password_algo *algo;
+ zend_string *hash, *ident;
+ zval options;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_STR(hash)
+ ZEND_PARSE_PARAMETERS_END();
+
+ array_init(return_value);
+ array_init(&options);
+
+ ident = php_password_algo_extract_ident(hash);
+ algo = php_password_algo_find(ident);
+ if (!algo || (algo->valid && !algo->valid(hash))) {
+ if (ident) {
+ zend_string_release(ident);
+ }
+ add_assoc_null(return_value, "algo");
+ add_assoc_string(return_value, "algoName", "unknown");
+ add_assoc_zval(return_value, "options", &options);
+ return;
+ }
+
+ add_assoc_str(return_value, "algo", php_password_algo_extract_ident(hash));
+ zend_string_release(ident);
+
+ add_assoc_string(return_value, "algoName", algo->name);
+ if (algo->get_info &&
+ (FAILURE == algo->get_info(&options, hash))) {
+ zval_dtor(&options);
+ zval_dtor(return_value);
+ RETURN_NULL();
+ }
+ add_assoc_zval(return_value, "options", &options);
+}
+/** }}} */
+
+/* {{{ proto bool password_needs_rehash(string $hash, mixed $algo[, array $options])
+Determines if a given hash requires re-hashing based upon parameters */
+PHP_FUNCTION(password_needs_rehash)
+{
+ const php_password_algo *old_algo, *new_algo;
+ zend_string *hash;
+ zval *znew_algo;
+ zend_array *options = 0;
+
+ ZEND_PARSE_PARAMETERS_START(2, 3)
+ Z_PARAM_STR(hash)
+ Z_PARAM_ZVAL_DEREF(znew_algo)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_ARRAY_OR_OBJECT_HT(options)
+ ZEND_PARSE_PARAMETERS_END();
+
+ new_algo = php_password_algo_find_zval_ex(znew_algo, NULL);
+ if (!new_algo) {
+ /* Unknown new algorithm, never prompt to rehash. */
+ RETURN_FALSE;
+ }
+
+ old_algo = php_password_algo_identify_ex(hash, NULL);
+ if (old_algo != new_algo) {
+ /* Different algorithm preferred, always rehash. */
+ RETURN_TRUE;
+ }
+
+ RETURN_BOOL(old_algo->needs_rehash(hash, options));
+}
+/* }}} */
+
+/* {{{ proto bool password_verify(string password, string hash)
+Verify a hash created using crypt() or password_hash() */
+PHP_FUNCTION(password_verify)
+{
+ zend_string *password, *hash;
+ const php_password_algo *algo;
+
+ ZEND_PARSE_PARAMETERS_START(2, 2)
+ Z_PARAM_STR(password)
+ Z_PARAM_STR(hash)
+ ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
+
+ algo = php_password_algo_identify(hash);
+ RETURN_BOOL(algo && (!algo->verify || algo->verify(password, hash)));
+}
+/* }}} */
+
+/* {{{ proto string password_hash(string password, mixed algo[, array options = array()])
Hash a password */
PHP_FUNCTION(password_hash)
{
- zend_string *password;
- zend_long algo = PHP_PASSWORD_DEFAULT;
- HashTable *options = NULL;
+ zend_string *password, *digest = NULL;
+ zval *zalgo;
+ const php_password_algo *algo;
+ zend_array *options = NULL;
ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STR(password)
- Z_PARAM_LONG(algo)
+ Z_PARAM_ZVAL_DEREF(zalgo)
Z_PARAM_OPTIONAL
Z_PARAM_ARRAY_OR_OBJECT_HT(options)
ZEND_PARSE_PARAMETERS_END();
- switch (algo) {
- case PHP_PASSWORD_BCRYPT:
- {
- char hash_format[10];
- size_t hash_format_len;
- zend_string *result, *hash, *salt;
- zval *option_buffer;
- zend_long cost = PHP_PASSWORD_BCRYPT_COST;
-
- if (options && (option_buffer = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
- cost = zval_get_long(option_buffer);
- }
-
- if (cost < 4 || cost > 31) {
- php_error_docref(NULL, E_WARNING, "Invalid bcrypt cost parameter specified: " ZEND_LONG_FMT, cost);
- RETURN_NULL();
- }
-
- hash_format_len = snprintf(hash_format, sizeof(hash_format), "$2y$%02" ZEND_LONG_FMT_SPEC "$", cost);
- if (!(salt = php_password_get_salt(return_value, Z_UL(22), options))) {
- return;
- }
- ZSTR_VAL(salt)[ZSTR_LEN(salt)] = 0;
-
- hash = zend_string_alloc(ZSTR_LEN(salt) + hash_format_len, 0);
- sprintf(ZSTR_VAL(hash), "%s%s", hash_format, ZSTR_VAL(salt));
- ZSTR_VAL(hash)[hash_format_len + ZSTR_LEN(salt)] = 0;
-
- zend_string_release_ex(salt, 0);
-
- /* This cast is safe, since both values are defined here in code and cannot overflow */
- result = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);
- zend_string_release_ex(hash, 0);
-
- if (!result) {
- RETURN_FALSE;
- }
-
- if (ZSTR_LEN(result) < 13) {
- zend_string_free(result);
- RETURN_FALSE;
- }
-
- RETURN_STR(result);
- }
- break;
-#if HAVE_ARGON2LIB
- case PHP_PASSWORD_ARGON2I:
- case PHP_PASSWORD_ARGON2ID:
- {
- zval *option_buffer;
- zend_string *salt, *out, *encoded;
- size_t time_cost = PHP_PASSWORD_ARGON2_TIME_COST;
- size_t memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST;
- size_t threads = PHP_PASSWORD_ARGON2_THREADS;
- argon2_type type;
- if (algo == PHP_PASSWORD_ARGON2ID) {
- type = Argon2_id;
- } else if (algo == PHP_PASSWORD_ARGON2I) {
- type = Argon2_i;
- }
- size_t encoded_len;
- int status = 0;
-
- if (options && (option_buffer = zend_hash_str_find(options, "memory_cost", sizeof("memory_cost")-1)) != NULL) {
- memory_cost = zval_get_long(option_buffer);
- }
-
- if (memory_cost > ARGON2_MAX_MEMORY || memory_cost < ARGON2_MIN_MEMORY) {
- php_error_docref(NULL, E_WARNING, "Memory cost is outside of allowed memory range");
- RETURN_NULL();
- }
-
- if (options && (option_buffer = zend_hash_str_find(options, "time_cost", sizeof("time_cost")-1)) != NULL) {
- time_cost = zval_get_long(option_buffer);
- }
-
- if (time_cost > ARGON2_MAX_TIME || time_cost < ARGON2_MIN_TIME) {
- php_error_docref(NULL, E_WARNING, "Time cost is outside of allowed time range");
- RETURN_NULL();
- }
-
- if (options && (option_buffer = zend_hash_str_find(options, "threads", sizeof("threads")-1)) != NULL) {
- threads = zval_get_long(option_buffer);
- }
-
- if (threads > ARGON2_MAX_LANES || threads == 0) {
- php_error_docref(NULL, E_WARNING, "Invalid number of threads");
- RETURN_NULL();
- }
-
- if (!(salt = php_password_get_salt(return_value, Z_UL(16), options))) {
- return;
- }
-
- out = zend_string_alloc(32, 0);
- encoded_len = argon2_encodedlen(
- time_cost,
- memory_cost,
- threads,
- (uint32_t)ZSTR_LEN(salt),
- ZSTR_LEN(out),
- type
- );
-
- encoded = zend_string_alloc(encoded_len - 1, 0);
- status = argon2_hash(
- time_cost,
- memory_cost,
- threads,
- ZSTR_VAL(password),
- ZSTR_LEN(password),
- ZSTR_VAL(salt),
- ZSTR_LEN(salt),
- ZSTR_VAL(out),
- ZSTR_LEN(out),
- ZSTR_VAL(encoded),
- encoded_len,
- type,
- ARGON2_VERSION_NUMBER
- );
-
- zend_string_release_ex(out, 0);
- zend_string_release_ex(salt, 0);
-
- if (status != ARGON2_OK) {
- zend_string_efree(encoded);
- php_error_docref(NULL, E_WARNING, "%s", argon2_error_message(status));
- RETURN_FALSE;
- }
-
- ZSTR_VAL(encoded)[ZSTR_LEN(encoded)] = 0;
- RETURN_NEW_STR(encoded);
- }
- break;
-#endif
- case PHP_PASSWORD_UNKNOWN:
- default:
- php_error_docref(NULL, E_WARNING, "Unknown password hashing algorithm: " ZEND_LONG_FMT, algo);
- RETURN_NULL();
+ algo = php_password_algo_find_zval(zalgo);
+ if (!algo) {
+ zend_string *algostr = zval_get_string(zalgo);
+ php_error_docref(NULL, E_WARNING, "Unknown password hashing algorithm: %s", ZSTR_VAL(algostr));
+ zend_string_release(algostr);
+ RETURN_NULL();
+ }
+
+ digest = algo->hash(password, options);
+ if (!digest) {
+ /* algo->hash should have raised an error. */
+ RETURN_NULL();
}
+
+ RETURN_NEW_STR(digest);
+}
+/* }}} */
+
+/* {{{ proto array password_algos() */
+PHP_FUNCTION(password_algos) {
+ zend_string *algo;
+
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ array_init(return_value);
+ ZEND_HASH_FOREACH_STR_KEY(&php_password_algos, algo) {
+ add_next_index_str(return_value, zend_string_copy(algo));
+ } ZEND_HASH_FOREACH_END();
}
/* }}} */
diff --git a/ext/standard/php_password.h b/ext/standard/php_password.h
index 0c2f83c650..4d9d6b46f1 100644
--- a/ext/standard/php_password.h
+++ b/ext/standard/php_password.h
@@ -24,6 +24,7 @@ PHP_FUNCTION(password_hash);
PHP_FUNCTION(password_verify);
PHP_FUNCTION(password_needs_rehash);
PHP_FUNCTION(password_get_info);
+PHP_FUNCTION(password_algos);
PHP_MINIT_FUNCTION(password);
@@ -36,14 +37,32 @@ PHP_MINIT_FUNCTION(password);
#define PHP_PASSWORD_ARGON2_THREADS 2
#endif
-typedef enum {
- PHP_PASSWORD_UNKNOWN,
- PHP_PASSWORD_BCRYPT,
+typedef struct _php_password_algo {
+ const char *name;
+ zend_string *(*hash)(const zend_string *password, zend_array *options);
+ zend_bool (*verify)(const zend_string *password, const zend_string *hash);
+ zend_bool (*needs_rehash)(const zend_string *password, zend_array *options);
+ int (*get_info)(zval *return_value, const zend_string *hash);
+ zend_bool (*valid)(const zend_string *hash);
+} php_password_algo;
+
+extern const php_password_algo php_password_algo_bcrypt;
#if HAVE_ARGON2LIB
- PHP_PASSWORD_ARGON2I,
- PHP_PASSWORD_ARGON2ID,
+extern const php_password_algo php_password_algo_argon2i;
+extern const php_password_algo php_password_algo_argon2id;
#endif
-} php_password_algo;
+
+PHPAPI int php_password_algo_register(const char*, const php_password_algo*);
+PHPAPI void php_password_algo_unregister(const char*);
+PHPAPI const php_password_algo* php_password_algo_default();
+PHPAPI zend_string *php_password_algo_extract_ident(const zend_string*);
+PHPAPI const php_password_algo* php_password_algo_find(const zend_string*);
+
+PHPAPI const php_password_algo* php_password_algo_identify_ex(const zend_string*, const php_password_algo*);
+static inline const php_password_algo* php_password_algo_identify(const zend_string *hash) {
+ return php_password_algo_identify_ex(hash, php_password_algo_default());
+}
+
#endif
diff --git a/ext/standard/tests/password/password_get_info.phpt b/ext/standard/tests/password/password_get_info.phpt
index 4c8dc04ff8..22c4ce4c52 100644
--- a/ext/standard/tests/password/password_get_info.phpt
+++ b/ext/standard/tests/password/password_get_info.phpt
@@ -17,7 +17,7 @@ echo "OK!";
--EXPECT--
array(3) {
["algo"]=>
- int(1)
+ string(2) "2y"
["algoName"]=>
string(6) "bcrypt"
["options"]=>
@@ -28,7 +28,7 @@ array(3) {
}
array(3) {
["algo"]=>
- int(1)
+ string(2) "2y"
["algoName"]=>
string(6) "bcrypt"
["options"]=>
@@ -39,7 +39,7 @@ array(3) {
}
array(3) {
["algo"]=>
- int(0)
+ NULL
["algoName"]=>
string(7) "unknown"
["options"]=>
@@ -48,7 +48,7 @@ array(3) {
}
array(3) {
["algo"]=>
- int(0)
+ NULL
["algoName"]=>
string(7) "unknown"
["options"]=>
diff --git a/ext/standard/tests/password/password_get_info_argon2.phpt b/ext/standard/tests/password/password_get_info_argon2.phpt
index b67fc5790e..1965d103db 100644
--- a/ext/standard/tests/password/password_get_info_argon2.phpt
+++ b/ext/standard/tests/password/password_get_info_argon2.phpt
@@ -15,7 +15,7 @@ echo "OK!";
--EXPECT--
array(3) {
["algo"]=>
- int(2)
+ string(7) "argon2i"
["algoName"]=>
string(7) "argon2i"
["options"]=>
@@ -30,7 +30,7 @@ array(3) {
}
array(3) {
["algo"]=>
- int(3)
+ string(8) "argon2id"
["algoName"]=>
string(8) "argon2id"
["options"]=>
diff --git a/ext/standard/tests/password/password_hash.phpt b/ext/standard/tests/password/password_hash.phpt
index 47335c376a..2ddfda32d1 100644
--- a/ext/standard/tests/password/password_hash.phpt
+++ b/ext/standard/tests/password/password_hash.phpt
@@ -6,13 +6,22 @@ Test normal operation of password_hash()
var_dump(strlen(password_hash("foo", PASSWORD_BCRYPT)));
-$hash = password_hash("foo", PASSWORD_BCRYPT);
+$algos = [
+ PASSWORD_BCRYPT,
+ '2y',
+ 1,
+];
-var_dump($hash === crypt("foo", $hash));
+foreach ($algos as $algo) {
+ $hash = password_hash("foo", $algo);
+ var_dump($hash === crypt("foo", $hash));
+}
echo "OK!";
?>
--EXPECT--
int(60)
bool(true)
+bool(true)
+bool(true)
OK!
diff --git a/ext/standard/tests/password/password_hash_argon2.phpt b/ext/standard/tests/password/password_hash_argon2.phpt
index 184bac4ac6..a7612effa9 100644
--- a/ext/standard/tests/password/password_hash_argon2.phpt
+++ b/ext/standard/tests/password/password_hash_argon2.phpt
@@ -9,15 +9,33 @@ if (!defined('PASSWORD_ARGON2ID')) die('skip password_hash not built with Argon2
$password = "the password for testing 12345!";
-$hash = password_hash($password, PASSWORD_ARGON2I);
-var_dump(password_verify($password, $hash));
-
-$hash = password_hash($password, PASSWORD_ARGON2ID);
-var_dump(password_verify($password, $hash));
+$algos = [
+ PASSWORD_ARGON2I,
+ 'argon2i',
+ 2,
+ PASSWORD_ARGON2ID,
+ 'argon2id',
+ 3,
+];
+foreach ($algos as $algo) {
+ $hash = password_hash($password, $algo);
+ var_dump(password_verify($password, $hash));
+ var_dump(password_get_info($hash)['algo']);
+}
echo "OK!";
?>
--EXPECT--
bool(true)
+string(7) "argon2i"
+bool(true)
+string(7) "argon2i"
+bool(true)
+string(7) "argon2i"
+bool(true)
+string(8) "argon2id"
+bool(true)
+string(8) "argon2id"
bool(true)
+string(8) "argon2id"
OK!
diff --git a/ext/standard/tests/password/password_hash_error.phpt b/ext/standard/tests/password/password_hash_error.phpt
index 5f576c3122..6416eca91b 100644
--- a/ext/standard/tests/password/password_hash_error.phpt
+++ b/ext/standard/tests/password/password_hash_error.phpt
@@ -29,7 +29,9 @@ NULL
Warning: password_hash() expects at least 2 parameters, 1 given in %s on line %d
NULL
-Warning: password_hash() expects parameter 2 to be int, array given in %s on line %d
+Notice: Array to string conversion in %s on line %d
+
+Warning: password_hash(): Unknown password hashing algorithm: Array in %s on line %d
NULL
Warning: password_hash(): Unknown password hashing algorithm: 19 in %s on line %d
diff --git a/ext/standard/tests/password/password_needs_rehash.phpt b/ext/standard/tests/password/password_needs_rehash.phpt
index 8efd0add8f..688d57ed32 100644
--- a/ext/standard/tests/password/password_needs_rehash.phpt
+++ b/ext/standard/tests/password/password_needs_rehash.phpt
@@ -6,9 +6,13 @@ Test normal operation of password_needs_rehash()
// Invalid Hash, always rehash
var_dump(password_needs_rehash('', PASSWORD_BCRYPT));
+var_dump(password_needs_rehash('', 1));
+var_dump(password_needs_rehash('', '2y'));
// Valid, as it's an unknown algorithm
+var_dump(password_needs_rehash('', PASSWORD_DEFAULT));
var_dump(password_needs_rehash('', 0));
+var_dump(password_needs_rehash('', NULL));
// Valid with cost the same
var_dump(password_needs_rehash('$2y$10$MTIzNDU2Nzg5MDEyMzQ1Nej0NmcAWSLR.oP7XOR9HD/vjUuOj100y', PASSWORD_BCRYPT, array('cost' => 10)));
@@ -35,6 +39,10 @@ echo "OK!";
?>
--EXPECT--
bool(true)
+bool(true)
+bool(true)
+bool(false)
+bool(false)
bool(false)
bool(false)
bool(false)
diff --git a/ext/standard/tests/password/password_needs_rehash_error.phpt b/ext/standard/tests/password/password_needs_rehash_error.phpt
index 65766cb3fe..7180d11de7 100644
--- a/ext/standard/tests/password/password_needs_rehash_error.phpt
+++ b/ext/standard/tests/password/password_needs_rehash_error.phpt
@@ -7,11 +7,11 @@ var_dump(password_needs_rehash());
var_dump(password_needs_rehash(''));
-var_dump(password_needs_rehash('', "foo"));
+var_dump(password_needs_rehash('', []));
-var_dump(password_needs_rehash(array(), 1));
+var_dump(password_needs_rehash(array(), PASSWORD_BCRYPT));
-var_dump(password_needs_rehash("", 1, "foo"));
+var_dump(password_needs_rehash("", PASSWORD_BCRYPT, "foo"));
echo "OK!";
?>
@@ -21,9 +21,7 @@ NULL
Warning: password_needs_rehash() expects at least 2 parameters, 1 given in %s on line %d
NULL
-
-Warning: password_needs_rehash() expects parameter 2 to be int, string given in %s on line %d
-NULL
+bool(false)
Warning: password_needs_rehash() expects parameter 1 to be string, array given in %s on line %d
NULL