diff --git a/ext/standard/password.c b/ext/standard/password.c index a14dc8dff4eef..1cd97715ab9fe 100644 --- a/ext/standard/password.c +++ b/ext/standard/password.c @@ -164,6 +164,13 @@ static bool php_password_bcrypt_verify(const zend_string *password, const zend_s return 0; } + if (ZSTR_LEN(password) != strlen(ZSTR_VAL(password))) { + php_error_docref(NULL, E_WARNING, + "bcrypt based password hashing is not binary safe, but the provided password contains a null byte. " + "The portion of the password provided up to the null byte did validate, but this hash should be " + "regenerated using a password without null bytes, or using a binary safe algorithm such as argon2i/argon2id"); + } + if (ZSTR_LEN(ret) != ZSTR_LEN(hash) || ZSTR_LEN(hash) < 13) { zend_string_free(ret); return 0; @@ -188,6 +195,11 @@ static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_a zval *zcost; zend_long cost = PHP_PASSWORD_BCRYPT_COST; + if (ZSTR_LEN(password) != strlen(ZSTR_VAL(password))) { + zend_value_error("bcrypt based password hashing is not binary safe, but the provided password contains a null byte"); + return NULL; + } + if (options && (zcost = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) { cost = zval_get_long(zcost); } diff --git a/ext/standard/tests/password/null-argon2i.phpt b/ext/standard/tests/password/null-argon2i.phpt new file mode 100644 index 0000000000000..841410008ed94 --- /dev/null +++ b/ext/standard/tests/password/null-argon2i.phpt @@ -0,0 +1,24 @@ +--TEST-- +password_hash(argon2i) handles null bytes correctly +--SKIPIF-- +getMessage(), "\n"; +} + +--EXPECTF-- +string(60) "$2y$%s" +bool(true) + +Warning: password_verify(): bcrypt %s regenerated %s argon2i/argon2id in %s/null-bcrypt.php on line %d +bool(true) +Exception: bcrypt based password hashing is not binary safe, but the provided password contains a null byte