Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to add a UPGRADING entry.


- MySQLnd:
. Fixed GHSA-r6x9-5r99-36j7 (Various packet overreads in mysqlnd wire
protocol). (CVE-2025-1218) (Jakub Zelenka, Nora Dossche)
Expand Down
4 changes: 4 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -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.
29 changes: 24 additions & 5 deletions ext/hash/hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
) /* {{{ */ {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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 */
Expand All @@ -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);
Expand All @@ -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);

Expand Down
Loading