Skip to content

Commit 8378d8e

Browse files
committed
Add SHA-256-specific PBKDF2 fast path
1 parent 0413024 commit 8378d8e

2 files changed

Lines changed: 152 additions & 0 deletions

File tree

ext/hash/hash.c

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@
2121

2222
#include <math.h>
2323
#include "php_hash.h"
24+
#include "php_hash_sha.h"
2425
#include "ext/standard/info.h"
2526
#include "ext/standard/file.h"
2627
#include "ext/standard/php_var.h"
2728

29+
/* Internal helper from hash_sha.c, not part of the public hash API. */
30+
void php_hash_sha256_final32_from_context(unsigned char digest[32], const PHP_SHA256_CTX *context, const unsigned char data[32]);
31+
2832
#include "zend_attributes.h"
2933
#include "zend_exceptions.h"
3034
#include "zend_interfaces.h"
@@ -502,6 +506,127 @@ static inline void php_hash_hmac_round_with_copy(unsigned char *final, const php
502506
ops->hash_final(final, context);
503507
}
504508

509+
static const unsigned char php_hash_sha_padding[128] = {
510+
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
511+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
512+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
513+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
514+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
515+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
516+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
517+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
518+
};
519+
520+
static inline void php_hash_sha256_encode(unsigned char digest[32], const uint32_t state[8]) {
521+
for (size_t i = 0; i < 8; i++) {
522+
digest[i * 4] = (unsigned char) ((state[i] >> 24) & 0xff);
523+
digest[(i * 4) + 1] = (unsigned char) ((state[i] >> 16) & 0xff);
524+
digest[(i * 4) + 2] = (unsigned char) ((state[i] >> 8) & 0xff);
525+
digest[(i * 4) + 3] = (unsigned char) (state[i] & 0xff);
526+
}
527+
}
528+
529+
static inline void php_hash_sha256_final_no_zero(unsigned char digest[32], PHP_SHA256_CTX *context) {
530+
unsigned char bits[8];
531+
unsigned int index, pad_len;
532+
533+
bits[7] = (unsigned char) (context->count[0] & 0xFF);
534+
bits[6] = (unsigned char) ((context->count[0] >> 8) & 0xFF);
535+
bits[5] = (unsigned char) ((context->count[0] >> 16) & 0xFF);
536+
bits[4] = (unsigned char) ((context->count[0] >> 24) & 0xFF);
537+
bits[3] = (unsigned char) (context->count[1] & 0xFF);
538+
bits[2] = (unsigned char) ((context->count[1] >> 8) & 0xFF);
539+
bits[1] = (unsigned char) ((context->count[1] >> 16) & 0xFF);
540+
bits[0] = (unsigned char) ((context->count[1] >> 24) & 0xFF);
541+
542+
index = (unsigned int) ((context->count[0] >> 3) & 0x3f);
543+
pad_len = (index < 56) ? (56 - index) : (120 - index);
544+
PHP_SHA256Update(context, php_hash_sha_padding, pad_len);
545+
PHP_SHA256Update(context, bits, 8);
546+
547+
php_hash_sha256_encode(digest, context->state);
548+
}
549+
550+
static zend_string *php_hash_pbkdf2_sha256(const char *pass, size_t pass_len, const char *salt, size_t salt_len, zend_long iterations, zend_long length, bool raw_output) {
551+
zend_string *returnval;
552+
unsigned char *digest, *result, *K = NULL, counter[4];
553+
zend_long loops, i, j, digest_length = 0;
554+
const size_t digest_size = 32;
555+
const size_t block_size = 64;
556+
PHP_SHA256_CTX inner_context, outer_context, context;
557+
558+
memset(&context, 0, sizeof(PHP_SHA256_CTX));
559+
K = emalloc(block_size);
560+
digest = emalloc(digest_size);
561+
562+
php_hash_hmac_prep_key(K, &php_hash_sha256_ops, &context, (const unsigned char *) pass, pass_len);
563+
564+
PHP_SHA256Init(&inner_context);
565+
PHP_SHA256Update(&inner_context, K, block_size);
566+
567+
php_hash_string_xor_char(K, K, 0x6A, block_size);
568+
PHP_SHA256Init(&outer_context);
569+
PHP_SHA256Update(&outer_context, K, block_size);
570+
571+
if (length == 0) {
572+
length = digest_size;
573+
if (!raw_output) {
574+
length = length * 2;
575+
}
576+
}
577+
digest_length = length;
578+
if (!raw_output) {
579+
digest_length = (zend_long) ceil((float) length / 2.0);
580+
}
581+
582+
loops = (zend_long) ceil((float) digest_length / (float) digest_size);
583+
584+
result = safe_emalloc(loops, digest_size, 0);
585+
586+
for (i = 1; i <= loops; i++) {
587+
unsigned char *result_block = result + ((i - 1) * digest_size);
588+
589+
counter[0] = (unsigned char) (i >> 24);
590+
counter[1] = (unsigned char) ((i & 0xFF0000) >> 16);
591+
counter[2] = (unsigned char) ((i & 0xFF00) >> 8);
592+
counter[3] = (unsigned char) (i & 0xFF);
593+
594+
context = inner_context;
595+
PHP_SHA256Update(&context, (const unsigned char *) salt, salt_len);
596+
PHP_SHA256Update(&context, counter, sizeof(counter));
597+
php_hash_sha256_final_no_zero(digest, &context);
598+
599+
php_hash_sha256_final32_from_context(digest, &outer_context, digest);
600+
memcpy(result_block, digest, digest_size);
601+
602+
for (j = 1; j < iterations; j++) {
603+
php_hash_sha256_final32_from_context(digest, &inner_context, digest);
604+
php_hash_sha256_final32_from_context(digest, &outer_context, digest);
605+
606+
php_hash_string_xor(result_block, result_block, digest, digest_size);
607+
}
608+
}
609+
610+
ZEND_SECURE_ZERO(K, block_size);
611+
ZEND_SECURE_ZERO(digest, digest_size);
612+
ZEND_SECURE_ZERO(&inner_context, sizeof(PHP_SHA256_CTX));
613+
ZEND_SECURE_ZERO(&outer_context, sizeof(PHP_SHA256_CTX));
614+
ZEND_SECURE_ZERO(&context, sizeof(PHP_SHA256_CTX));
615+
efree(K);
616+
efree(digest);
617+
618+
returnval = zend_string_alloc(length, 0);
619+
if (raw_output) {
620+
memcpy(ZSTR_VAL(returnval), result, length);
621+
} else {
622+
php_hash_bin2hex(ZSTR_VAL(returnval), result, digest_length);
623+
}
624+
ZSTR_VAL(returnval)[length] = 0;
625+
efree(result);
626+
627+
return returnval;
628+
}
629+
505630
static void php_hash_do_hash_hmac(
506631
zval *return_value, zend_string *algo, char *data, size_t data_len, char *key, size_t key_len, bool raw_output, bool isfilename
507632
) /* {{{ */ {
@@ -1029,6 +1154,10 @@ PHP_FUNCTION(hash_pbkdf2)
10291154
RETURN_THROWS();
10301155
}
10311156

1157+
if (ops == &php_hash_sha256_ops) {
1158+
RETURN_NEW_STR(php_hash_pbkdf2_sha256(pass, pass_len, salt, salt_len, iterations, length, raw_output));
1159+
}
1160+
10321161
inner_context = php_hash_alloc_context(ops);
10331162
outer_context = php_hash_alloc_context(ops);
10341163
context = php_hash_alloc_context(ops);

ext/hash/hash_sha.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,29 @@ PHP_HASH_API void PHP_SHA256Final(unsigned char digest[32], PHP_SHA256_CTX * con
390390
}
391391
/* }}} */
392392

393+
/* {{{ php_hash_sha256_final32_from_context
394+
Compute SHA256(base_ctx || data[32]) where base_ctx already hashed one full 64-byte block.
395+
*/
396+
void php_hash_sha256_final32_from_context(unsigned char digest[32], const PHP_SHA256_CTX *context, const unsigned char data[32])
397+
{
398+
uint32_t state[8];
399+
unsigned char block[64];
400+
401+
ZEND_ASSERT(context->count[1] == 0);
402+
ZEND_ASSERT(context->count[0] == 512);
403+
404+
memcpy(state, context->state, sizeof(state));
405+
memcpy(block, data, 32);
406+
block[32] = 0x80;
407+
memset(block + 33, 0, 23);
408+
memset(block + 56, 0, 8);
409+
block[62] = 0x03;
410+
411+
SHA256Transform(state, block);
412+
SHAEncode32(digest, state, 32);
413+
}
414+
/* }}} */
415+
393416
/* sha384/sha512 */
394417

395418
/* Ch */

0 commit comments

Comments
 (0)