|
|
|
@ -58,28 +58,30 @@ PHP_MINIT_FUNCTION(password) /* {{{ */ |
|
|
|
} |
|
|
|
/* }}} */ |
|
|
|
|
|
|
|
static char* php_password_get_algo_name(const php_password_algo algo) |
|
|
|
static zend_string* php_password_get_algo_name(const php_password_algo algo) |
|
|
|
{ |
|
|
|
switch (algo) { |
|
|
|
case PHP_PASSWORD_BCRYPT: |
|
|
|
return "bcrypt"; |
|
|
|
return zend_string_init("bcrypt", sizeof("bcrypt") - 1, 0); |
|
|
|
#if HAVE_ARGON2LIB |
|
|
|
case PHP_PASSWORD_ARGON2I: |
|
|
|
return "argon2i"; |
|
|
|
return zend_string_init("argon2i", sizeof("argon2i") - 1, 0); |
|
|
|
#endif |
|
|
|
case PHP_PASSWORD_UNKNOWN: |
|
|
|
default: |
|
|
|
return "unknown"; |
|
|
|
return zend_string_init("unknown", sizeof("unknown") - 1, 0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static php_password_algo php_password_determine_algo(const char *hash, const size_t len) |
|
|
|
static php_password_algo php_password_determine_algo(const zend_string *hash) |
|
|
|
{ |
|
|
|
if (len > 3 && hash[0] == '$' && hash[1] == '2' && hash[2] == 'y' && len == 60) { |
|
|
|
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("$argon2i$")-1 && !memcmp(hash, "$argon2i$", sizeof("$argon2i$")-1)) { |
|
|
|
if (len >= sizeof("$argon2i$")-1 && !memcmp(h, "$argon2i$", sizeof("$argon2i$")-1)) { |
|
|
|
return PHP_PASSWORD_ARGON2I; |
|
|
|
} |
|
|
|
#endif |
|
|
|
@ -128,39 +130,32 @@ static int php_password_salt_to64(const char *str, const size_t str_len, const s |
|
|
|
} |
|
|
|
/* }}} */ |
|
|
|
|
|
|
|
static int php_password_make_salt(size_t length, char *ret) /* {{{ */ |
|
|
|
static zend_string* php_password_make_salt(size_t length) /* {{{ */ |
|
|
|
{ |
|
|
|
size_t raw_length; |
|
|
|
char *buffer; |
|
|
|
char *result; |
|
|
|
zend_string *ret, *buffer; |
|
|
|
|
|
|
|
if (length > (INT_MAX / 3)) { |
|
|
|
php_error_docref(NULL, E_WARNING, "Length is too large to safely generate"); |
|
|
|
return FAILURE; |
|
|
|
php_error(E_WARNING, "Length is too large to safely generate"); |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
|
|
|
|
raw_length = length * 3 / 4 + 1; |
|
|
|
|
|
|
|
buffer = (char *) safe_emalloc(raw_length, 1, 1); |
|
|
|
|
|
|
|
if (FAILURE == php_random_bytes_silent(buffer, raw_length)) { |
|
|
|
php_error_docref(NULL, E_WARNING, "Unable to generate salt"); |
|
|
|
efree(buffer); |
|
|
|
return FAILURE; |
|
|
|
buffer = zend_string_alloc(length * 3 / 4 + 1, 0); |
|
|
|
if (FAILURE == php_random_bytes_silent(ZSTR_VAL(buffer), ZSTR_LEN(buffer))) { |
|
|
|
php_error(E_WARNING, "Unable to generate salt"); |
|
|
|
zend_string_release(buffer); |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
|
|
|
|
result = safe_emalloc(length, 1, 1); |
|
|
|
if (php_password_salt_to64(buffer, raw_length, length, result) == FAILURE) { |
|
|
|
php_error_docref(NULL, E_WARNING, "Generated salt too short"); |
|
|
|
efree(buffer); |
|
|
|
efree(result); |
|
|
|
return FAILURE; |
|
|
|
ret = zend_string_alloc(length, 0); |
|
|
|
if (php_password_salt_to64(ZSTR_VAL(buffer), ZSTR_LEN(buffer), length, ZSTR_VAL(ret)) == FAILURE) { |
|
|
|
php_error(E_WARNING, "Generated salt too short"); |
|
|
|
zend_string_release(buffer); |
|
|
|
zend_string_release(ret); |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
memcpy(ret, result, length); |
|
|
|
efree(result); |
|
|
|
efree(buffer); |
|
|
|
ret[length] = 0; |
|
|
|
return SUCCESS; |
|
|
|
zend_string_release(buffer); |
|
|
|
ZSTR_VAL(ret)[length] = 0; |
|
|
|
return ret; |
|
|
|
} |
|
|
|
/* }}} */ |
|
|
|
|
|
|
|
@ -169,24 +164,23 @@ Retrieves information about a given hash */ |
|
|
|
PHP_FUNCTION(password_get_info) |
|
|
|
{ |
|
|
|
php_password_algo algo; |
|
|
|
size_t hash_len; |
|
|
|
char *hash, *algo_name; |
|
|
|
zend_string *hash, *algo_name; |
|
|
|
zval options; |
|
|
|
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1) |
|
|
|
Z_PARAM_STRING(hash, hash_len) |
|
|
|
Z_PARAM_STR(hash) |
|
|
|
ZEND_PARSE_PARAMETERS_END(); |
|
|
|
|
|
|
|
array_init(&options); |
|
|
|
|
|
|
|
algo = php_password_determine_algo(hash, (size_t) hash_len); |
|
|
|
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(hash, "$2y$" ZEND_LONG_FMT "$", &cost); |
|
|
|
sscanf(ZSTR_VAL(hash), "$2y$" ZEND_LONG_FMT "$", &cost); |
|
|
|
add_assoc_long(&options, "cost", cost); |
|
|
|
} |
|
|
|
break; |
|
|
|
@ -198,7 +192,7 @@ PHP_FUNCTION(password_get_info) |
|
|
|
zend_long time_cost = PHP_PASSWORD_ARGON2_TIME_COST; |
|
|
|
zend_long threads = PHP_PASSWORD_ARGON2_THREADS; |
|
|
|
|
|
|
|
sscanf(hash, "$%*[argon2i]$v=" ZEND_LONG_FMT "$m=" ZEND_LONG_FMT ",t=" ZEND_LONG_FMT ",p=" ZEND_LONG_FMT, &v, &memory_cost, &time_cost, &threads); |
|
|
|
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); |
|
|
|
add_assoc_long(&options, "memory_cost", memory_cost); |
|
|
|
add_assoc_long(&options, "time_cost", time_cost); |
|
|
|
add_assoc_long(&options, "threads", threads); |
|
|
|
@ -213,7 +207,7 @@ PHP_FUNCTION(password_get_info) |
|
|
|
array_init(return_value); |
|
|
|
|
|
|
|
add_assoc_long(return_value, "algo", algo); |
|
|
|
add_assoc_string(return_value, "algoName", algo_name); |
|
|
|
add_assoc_str(return_value, "algoName", algo_name); |
|
|
|
add_assoc_zval(return_value, "options", &options); |
|
|
|
} |
|
|
|
/** }}} */ |
|
|
|
@ -224,19 +218,18 @@ PHP_FUNCTION(password_needs_rehash) |
|
|
|
{ |
|
|
|
zend_long new_algo = 0; |
|
|
|
php_password_algo algo; |
|
|
|
size_t hash_len; |
|
|
|
char *hash; |
|
|
|
zend_string *hash; |
|
|
|
HashTable *options = 0; |
|
|
|
zval *option_buffer; |
|
|
|
|
|
|
|
ZEND_PARSE_PARAMETERS_START(2, 3) |
|
|
|
Z_PARAM_STRING(hash, hash_len) |
|
|
|
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, (size_t) hash_len); |
|
|
|
algo = php_password_determine_algo(hash); |
|
|
|
|
|
|
|
if ((zend_long)algo != new_algo) { |
|
|
|
RETURN_TRUE; |
|
|
|
@ -251,7 +244,7 @@ PHP_FUNCTION(password_needs_rehash) |
|
|
|
new_cost = zval_get_long(option_buffer); |
|
|
|
} |
|
|
|
|
|
|
|
sscanf(hash, "$2y$" ZEND_LONG_FMT "$", &cost); |
|
|
|
sscanf(ZSTR_VAL(hash), "$2y$" ZEND_LONG_FMT "$", &cost); |
|
|
|
if (cost != new_cost) { |
|
|
|
RETURN_TRUE; |
|
|
|
} |
|
|
|
@ -277,7 +270,7 @@ PHP_FUNCTION(password_needs_rehash) |
|
|
|
new_threads = zval_get_long(option_buffer); |
|
|
|
} |
|
|
|
|
|
|
|
sscanf(hash, "$%*[argon2i]$v=" ZEND_LONG_FMT "$m=" ZEND_LONG_FMT ",t=" ZEND_LONG_FMT ",p=" ZEND_LONG_FMT, &v, &memory_cost, &time_cost, &threads); |
|
|
|
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); |
|
|
|
|
|
|
|
if (new_time_cost != time_cost || new_memory_cost != memory_cost || new_threads != threads) { |
|
|
|
RETURN_TRUE; |
|
|
|
@ -297,44 +290,35 @@ PHP_FUNCTION(password_needs_rehash) |
|
|
|
Verify a hash created using crypt() or password_hash() */ |
|
|
|
PHP_FUNCTION(password_verify) |
|
|
|
{ |
|
|
|
int status = 0; |
|
|
|
size_t i, password_len, hash_len; |
|
|
|
char *password, *hash; |
|
|
|
zend_string *ret; |
|
|
|
zend_string *password, *hash; |
|
|
|
php_password_algo algo; |
|
|
|
|
|
|
|
ZEND_PARSE_PARAMETERS_START(2, 2) |
|
|
|
Z_PARAM_STRING(password, password_len) |
|
|
|
Z_PARAM_STRING(hash, hash_len) |
|
|
|
Z_PARAM_STR(password) |
|
|
|
Z_PARAM_STR(hash) |
|
|
|
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); |
|
|
|
|
|
|
|
algo = php_password_determine_algo(hash, (size_t) hash_len); |
|
|
|
algo = php_password_determine_algo(hash); |
|
|
|
|
|
|
|
switch(algo) { |
|
|
|
#if HAVE_ARGON2LIB |
|
|
|
case PHP_PASSWORD_ARGON2I: |
|
|
|
{ |
|
|
|
argon2_type type = Argon2_i; |
|
|
|
|
|
|
|
status = argon2_verify(hash, password, password_len, type); |
|
|
|
|
|
|
|
if (status == ARGON2_OK) { |
|
|
|
RETURN_TRUE; |
|
|
|
} |
|
|
|
|
|
|
|
RETURN_FALSE; |
|
|
|
} |
|
|
|
RETURN_BOOL(ARGON2_OK == argon2_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password), Argon2_i)); |
|
|
|
break; |
|
|
|
#endif |
|
|
|
case PHP_PASSWORD_BCRYPT: |
|
|
|
case PHP_PASSWORD_UNKNOWN: |
|
|
|
default: |
|
|
|
{ |
|
|
|
if ((ret = php_crypt(password, (int)password_len, hash, (int)hash_len, 1)) == NULL) { |
|
|
|
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) != hash_len || hash_len < 13) { |
|
|
|
if (ZSTR_LEN(ret) != ZSTR_LEN(hash) || ZSTR_LEN(hash) < 13) { |
|
|
|
zend_string_free(ret); |
|
|
|
RETURN_FALSE; |
|
|
|
} |
|
|
|
@ -343,8 +327,8 @@ PHP_FUNCTION(password_verify) |
|
|
|
* resistance towards timing attacks. This is a constant time |
|
|
|
* equality check that will always check every byte of both |
|
|
|
* values. */ |
|
|
|
for (i = 0; i < hash_len; i++) { |
|
|
|
status |= (ZSTR_VAL(ret)[i] ^ hash[i]); |
|
|
|
for (i = 0; i < ZSTR_LEN(hash); i++) { |
|
|
|
status |= (ZSTR_VAL(ret)[i] ^ ZSTR_VAL(hash)[i]); |
|
|
|
} |
|
|
|
|
|
|
|
zend_string_free(ret); |
|
|
|
@ -361,11 +345,10 @@ PHP_FUNCTION(password_verify) |
|
|
|
Hash a password */ |
|
|
|
PHP_FUNCTION(password_hash) |
|
|
|
{ |
|
|
|
char hash_format[8], *hash, *salt, *password; |
|
|
|
char hash_format[8]; |
|
|
|
zend_long algo = 0; |
|
|
|
size_t password_len = 0; |
|
|
|
int hash_len; |
|
|
|
size_t salt_len = 0, required_salt_len = 0, hash_format_len; |
|
|
|
zend_string *password, *salt; |
|
|
|
size_t required_salt_len = 0, hash_format_len; |
|
|
|
HashTable *options = 0; |
|
|
|
zval *option_buffer; |
|
|
|
|
|
|
|
@ -377,7 +360,7 @@ PHP_FUNCTION(password_hash) |
|
|
|
#endif |
|
|
|
|
|
|
|
ZEND_PARSE_PARAMETERS_START(2, 3) |
|
|
|
Z_PARAM_STRING(password, password_len) |
|
|
|
Z_PARAM_STR(password) |
|
|
|
Z_PARAM_LONG(algo) |
|
|
|
Z_PARAM_OPTIONAL |
|
|
|
Z_PARAM_ARRAY_OR_OBJECT_HT(options) |
|
|
|
@ -477,51 +460,45 @@ PHP_FUNCTION(password_hash) |
|
|
|
zend_string_release(buffer); |
|
|
|
RETURN_NULL(); |
|
|
|
} else if (php_password_salt_is_alphabet(ZSTR_VAL(buffer), ZSTR_LEN(buffer)) == FAILURE) { |
|
|
|
salt = safe_emalloc(required_salt_len, 1, 1); |
|
|
|
if (php_password_salt_to64(ZSTR_VAL(buffer), ZSTR_LEN(buffer), required_salt_len, salt) == FAILURE) { |
|
|
|
efree(salt); |
|
|
|
salt = zend_string_alloc(required_salt_len, 0); |
|
|
|
if (php_password_salt_to64(ZSTR_VAL(buffer), ZSTR_LEN(buffer), required_salt_len, ZSTR_VAL(salt)) == FAILURE) { |
|
|
|
zend_string_release(salt); |
|
|
|
php_error_docref(NULL, E_WARNING, "Provided salt is too short: %zd", ZSTR_LEN(buffer)); |
|
|
|
zend_string_release(buffer); |
|
|
|
RETURN_NULL(); |
|
|
|
} |
|
|
|
salt_len = required_salt_len; |
|
|
|
} else { |
|
|
|
salt = safe_emalloc(required_salt_len, 1, 1); |
|
|
|
memcpy(salt, ZSTR_VAL(buffer), required_salt_len); |
|
|
|
salt_len = required_salt_len; |
|
|
|
salt = zend_string_alloc(required_salt_len, 0); |
|
|
|
memcpy(ZSTR_VAL(salt), ZSTR_VAL(buffer), required_salt_len); |
|
|
|
} |
|
|
|
zend_string_release(buffer); |
|
|
|
} else { |
|
|
|
salt = safe_emalloc(required_salt_len, 1, 1); |
|
|
|
if (php_password_make_salt(required_salt_len, salt) == FAILURE) { |
|
|
|
efree(salt); |
|
|
|
salt = php_password_make_salt(required_salt_len); |
|
|
|
if (!salt) { |
|
|
|
RETURN_FALSE; |
|
|
|
} |
|
|
|
salt_len = required_salt_len; |
|
|
|
} |
|
|
|
|
|
|
|
switch (algo) { |
|
|
|
case PHP_PASSWORD_BCRYPT: |
|
|
|
{ |
|
|
|
zend_string *result; |
|
|
|
salt[salt_len] = 0; |
|
|
|
zend_string *result, *hash; |
|
|
|
ZSTR_VAL(salt)[ZSTR_LEN(salt)] = 0; |
|
|
|
|
|
|
|
hash = safe_emalloc(salt_len + hash_format_len, 1, 1); |
|
|
|
sprintf(hash, "%s%s", hash_format, salt); |
|
|
|
hash[hash_format_len + salt_len] = 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; |
|
|
|
|
|
|
|
efree(salt); |
|
|
|
zend_string_release(salt); |
|
|
|
|
|
|
|
/* This cast is safe, since both values are defined here in code and cannot overflow */ |
|
|
|
hash_len = (int) (hash_format_len + salt_len); |
|
|
|
result = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1); |
|
|
|
zend_string_release(hash); |
|
|
|
|
|
|
|
if ((result = php_crypt(password, (int)password_len, hash, hash_len, 1)) == NULL) { |
|
|
|
efree(hash); |
|
|
|
if (!result) { |
|
|
|
RETURN_FALSE; |
|
|
|
} |
|
|
|
|
|
|
|
efree(hash); |
|
|
|
|
|
|
|
if (ZSTR_LEN(result) < 13) { |
|
|
|
zend_string_free(result); |
|
|
|
RETURN_FALSE; |
|
|
|
@ -533,51 +510,50 @@ PHP_FUNCTION(password_hash) |
|
|
|
#if HAVE_ARGON2LIB |
|
|
|
case PHP_PASSWORD_ARGON2I: |
|
|
|
{ |
|
|
|
size_t out_len = 32; |
|
|
|
size_t encoded_len; |
|
|
|
int status = 0; |
|
|
|
char *out; |
|
|
|
zend_string *out = zend_string_alloc(32, 0); |
|
|
|
zend_string *encoded; |
|
|
|
|
|
|
|
encoded_len = argon2_encodedlen( |
|
|
|
time_cost, |
|
|
|
memory_cost, |
|
|
|
threads, |
|
|
|
(uint32_t)salt_len, |
|
|
|
out_len |
|
|
|
(uint32_t)ZSTR_LEN(salt), |
|
|
|
ZSTR_LEN(out) |
|
|
|
#if HAVE_ARGON2ID |
|
|
|
, type |
|
|
|
#endif |
|
|
|
); |
|
|
|
|
|
|
|
out = emalloc(out_len + 1); |
|
|
|
encoded = zend_string_alloc(encoded_len, 0); |
|
|
|
|
|
|
|
status = argon2_hash( |
|
|
|
time_cost, |
|
|
|
memory_cost, |
|
|
|
threads, |
|
|
|
password, |
|
|
|
password_len, |
|
|
|
salt, |
|
|
|
salt_len, |
|
|
|
out, |
|
|
|
out_len, |
|
|
|
ZSTR_VAL(password), |
|
|
|
ZSTR_LEN(password), |
|
|
|
ZSTR_VAL(salt), |
|
|
|
ZSTR_LEN(salt), |
|
|
|
ZSTR_VAL(out), |
|
|
|
ZSTR_LEN(out), |
|
|
|
ZSTR_VAL(encoded), |
|
|
|
encoded_len, |
|
|
|
ZSTR_LEN(encoded), |
|
|
|
type, |
|
|
|
ARGON2_VERSION_NUMBER |
|
|
|
); |
|
|
|
|
|
|
|
efree(out); |
|
|
|
efree(salt); |
|
|
|
zend_string_release(out); |
|
|
|
zend_string_release(salt); |
|
|
|
|
|
|
|
if (status != ARGON2_OK) { |
|
|
|
zend_string_free(encoded); |
|
|
|
php_error_docref(NULL, E_WARNING, argon2_error_message(status)); |
|
|
|
php_error(E_WARNING, "%s", argon2_error_message(status)); |
|
|
|
RETURN_FALSE; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
ZSTR_VAL(encoded)[ZSTR_LEN(encoded)] = 0; |
|
|
|
RETURN_STR(encoded); |
|
|
|
} |
|
|
|
break; |
|
|
|
|