diff --git a/NEWS b/NEWS index 91bd9e2da7c4..6886092c9245 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,9 @@ PHP NEWS . Fixed GHSA-62xp-839h-2637 (IPv6 ACL bypass in FastCGI listen.allowed_clients due to partial address comparison). (CVE-2026-91768) (Alexandre Daubois) +- Hash: + . Improved performance of hash_pbkdf2. (Sjoerd Langkemper) + - MySQLnd: . Fixed GHSA-r6x9-5r99-36j7 (Various packet overreads in mysqlnd wire protocol). (CVE-2025-1218) (Jakub Zelenka, Nora Dossche) diff --git a/UPGRADING b/UPGRADING index efa74cb42cf9..9954f2a561c2 100644 --- a/UPGRADING +++ b/UPGRADING @@ -592,3 +592,7 @@ PHP 8.2 UPGRADE NOTES ======================================== 14. Performance Improvements ======================================== + +- Hash: + . hash_pbkdf2() is now faster. Applications using PBKDF2 may consider whether + their iteration count can be increased. diff --git a/ext/hash/hash.c b/ext/hash/hash.c index 4fdecfca79fb..f22e13037002 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -497,6 +497,12 @@ static inline void php_hash_hmac_round(unsigned char *final, const php_hash_ops ops->hash_final(final, context); } +static inline void php_hash_hmac_round_with_copy(unsigned char *final, const php_hash_ops *ops, void *base_context, void *context, const unsigned char *data, const zend_long data_size) { + ops->hash_copy(ops, base_context, context); + ops->hash_update(context, data, data_size); + ops->hash_final(final, context); +} + static void php_hash_do_hash_hmac( zval *return_value, zend_string *algo, char *data, size_t data_len, char *key, size_t key_len, bool raw_output, bool isfilename ) /* {{{ */ { @@ -996,7 +1002,7 @@ PHP_FUNCTION(hash_pbkdf2) size_t pass_len, salt_len = 0; bool raw_output = 0; const php_hash_ops *ops; - void *context; + void *context, *inner_context, *outer_context; HashTable *args = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sssl|lbh", &algo, &pass, &pass_len, &salt, &salt_len, &iterations, &length, &raw_output, &args) == FAILURE) { @@ -1027,6 +1033,9 @@ PHP_FUNCTION(hash_pbkdf2) context = php_hash_alloc_context(ops); ops->hash_init(context, args); + inner_context = php_hash_alloc_context(ops); + outer_context = php_hash_alloc_context(ops); + K1 = emalloc(ops->block_size); K2 = emalloc(ops->block_size); digest = emalloc(ops->digest_size); @@ -1037,6 +1046,12 @@ PHP_FUNCTION(hash_pbkdf2) /* Convert K1 to opad -- 0x6A = 0x36 ^ 0x5C */ php_hash_string_xor_char(K2, K1, 0x6A, ops->block_size); + /* Precompute the hash states after absorbing the ipad and opad blocks */ + ops->hash_init(inner_context, NULL); + ops->hash_update(inner_context, K1, ops->block_size); + ops->hash_init(outer_context, NULL); + ops->hash_update(outer_context, K2, ops->block_size); + /* Setup Main Loop to build a long enough result */ if (length == 0) { length = ops->digest_size; @@ -1065,8 +1080,8 @@ PHP_FUNCTION(hash_pbkdf2) computed_salt[salt_len + 2] = (unsigned char) ((i & 0xFF00) >> 8); computed_salt[salt_len + 3] = (unsigned char) (i & 0xFF); - php_hash_hmac_round(digest, ops, context, K1, computed_salt, (zend_long) salt_len + 4); - php_hash_hmac_round(digest, ops, context, K2, digest, ops->digest_size); + php_hash_hmac_round_with_copy(digest, ops, inner_context, context, computed_salt, (zend_long) salt_len + 4); + php_hash_hmac_round_with_copy(digest, ops, outer_context, context, digest, ops->digest_size); /* } */ /* temp = digest */ @@ -1078,8 +1093,8 @@ PHP_FUNCTION(hash_pbkdf2) */ for (j = 1; j < iterations; j++) { /* digest = hash_hmac(digest, password) { */ - php_hash_hmac_round(digest, ops, context, K1, digest, ops->digest_size); - php_hash_hmac_round(digest, ops, context, K2, digest, ops->digest_size); + php_hash_hmac_round_with_copy(digest, ops, inner_context, context, digest, ops->digest_size); + php_hash_hmac_round_with_copy(digest, ops, outer_context, context, digest, ops->digest_size); /* } */ /* temp ^= digest */ php_hash_string_xor(temp, temp, digest, ops->digest_size); @@ -1095,6 +1110,10 @@ PHP_FUNCTION(hash_pbkdf2) efree(K2); efree(computed_salt); efree(context); + ZEND_SECURE_ZERO(inner_context, ops->context_size); + ZEND_SECURE_ZERO(outer_context, ops->context_size); + efree(inner_context); + efree(outer_context); efree(digest); efree(temp);