Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Create hash_pbkdf2 function addition #105

Merged
merged 8 commits into from almost 2 years ago

4 participants

Anthony Ferrara Nikita Popov Scott MacVicar Account for PHP Pull Requests
Anthony Ferrara

This pull request adds a new function to the hash package: hash_pbkdf2(), providing the ability to natively hash content using the PKCS5 approved PBKDF2 algorithm. See Wikipedia and RSA for more information about the algorithm.

This patch refactors the internal implementation of hash_hmac() to allow code reuse between it and the new hash_pbkdf2() function. No internal APIs were changed, and the only API addition is the PHP function hash_pbkdf2(). A few static inline functions were either added, or extracted from the inside of hash_hmac and its implementation. These refactorings should have no public impact, since they are static to the extension.

NEWS
@@ -42,6 +42,9 @@ PHP                                                                        NEWS
42 42
 	still exists for backward compatibility but is doing nothing). (Pierrick)
43 43
   . Fixed bug #54995 (Missing CURLINFO_RESPONSE_CODE support). (Pierrick)
44 44
 
  45
+- Hash
  46
+  . Added support for PBKDF2 via hash_pbkdf2()F
1
Nikita Popov
nikic added a note June 12, 2012

The F looks strange :) Also I'm missing your name there :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Scott MacVicar

Should we make this more generic so that other derived key hash functions can be added? Like scrypt too?

ext/hash/hash.c
((9 lines not shown))
  211
+}
  212
+
  213
+static inline void php_hash_string_xor(unsigned char *out, const unsigned char *in, const unsigned char *xor_with, const int length) {
  214
+	int i;
  215
+	for(i=0; i < length; i++) {
  216
+		out[i] = in[i] ^ xor_with[i];
  217
+	}
  218
+}
  219
+
  220
+static inline void php_hash_hmac_prep_key(unsigned char *K, const php_hash_ops *ops, void *context, const unsigned char *key, const int key_len) {
  221
+	memset(K, 0, ops->block_size);
  222
+	if (key_len > ops->block_size) {
  223
+		/* Reduce the key first */
  224
+		ops->hash_init(context);
  225
+		ops->hash_update(context, (unsigned char *) key, key_len);
  226
+		ops->hash_final((unsigned char *) K, context);
1
Nikita Popov
nikic added a note June 12, 2012

Are the two unsigned char * cast necessary here? Look to me like they are already declared to be of that type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
ext/hash/hash.c
((13 lines not shown))
  215
+	for(i=0; i < length; i++) {
  216
+		out[i] = in[i] ^ xor_with[i];
  217
+	}
  218
+}
  219
+
  220
+static inline void php_hash_hmac_prep_key(unsigned char *K, const php_hash_ops *ops, void *context, const unsigned char *key, const int key_len) {
  221
+	memset(K, 0, ops->block_size);
  222
+	if (key_len > ops->block_size) {
  223
+		/* Reduce the key first */
  224
+		ops->hash_init(context);
  225
+		ops->hash_update(context, (unsigned char *) key, key_len);
  226
+		ops->hash_final((unsigned char *) K, context);
  227
+	} else {
  228
+		memcpy(K, key, key_len);
  229
+	}
  230
+	/* XOR the key with 0x36 to get the ipad) */
1
Nikita Popov
nikic added a note June 12, 2012

There's a leftover ) at the end of the comment :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Anthony Ferrara

@scottmac As far as that's concerned, I'm not sure how that would look. The API between PBKDF2 and SCrypt are very different. One takes a hash algorithm, a key (password), a salt, an iteration count, and a length. The other takes a key (password), a salt, and 3 integer parameters: N, p, r. So without having a generic "options" array (which I wouldn't care for).

Instead, why not add a second function: hash_scrypt()?

ext/hash/hash.c
((19 lines not shown))
  622
+		return;
  623
+	}
  624
+
  625
+	ops = php_hash_fetch_ops(algo, algo_len);
  626
+	if (!ops) {
  627
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown hashing algorithm: %s", algo);
  628
+		RETURN_FALSE;
  629
+	}
  630
+
  631
+	if (iterations <= 0) {
  632
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Iterations Must Be A Positive Integer: %ld", iterations);
  633
+		RETURN_FALSE;
  634
+	}
  635
+
  636
+	if (length < 0) {
  637
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Length Must Be Greater Than Or Equal To 0: %ld", length);
1
Nikita Popov
nikic added a note June 12, 2012

This and the previous error message Have All Words Capitalized. PHP doesn't usually make use of that scheme :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
ext/hash/hash.c
((37 lines not shown))
  640
+
  641
+	if (salt_len > INT_MAX - 4) {
  642
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Supplied salt is too long, max of INT_MAX - 4 bytes: %d supplied", salt_len);
  643
+		RETURN_FALSE;
  644
+	}
  645
+
  646
+	context = emalloc(ops->context_size);
  647
+	ops->hash_init(context);
  648
+
  649
+	K1 = emalloc(ops->block_size);
  650
+	K2 = emalloc(ops->block_size);
  651
+	digest = emalloc(ops->digest_size);
  652
+	temp = emalloc(ops->digest_size);
  653
+
  654
+	/* Setup Keys that will be used for all hmac rounds */
  655
+	memset(K2, 0, ops->block_size);
1
Nikita Popov
nikic added a note June 12, 2012

Not that it makes much of a difference, but is this memset needed? If I see it right then you are fully overwriting K2 anyways in the php_hash_string_xor_char call below.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
ext/hash/hash.c
((46 lines not shown))
  649
+	K1 = emalloc(ops->block_size);
  650
+	K2 = emalloc(ops->block_size);
  651
+	digest = emalloc(ops->digest_size);
  652
+	temp = emalloc(ops->digest_size);
  653
+
  654
+	/* Setup Keys that will be used for all hmac rounds */
  655
+	memset(K2, 0, ops->block_size);
  656
+	php_hash_hmac_prep_key(K1, ops, context, (unsigned char *) pass, pass_len);
  657
+	/* Convert K1 to opad -- 0x6A = 0x36 ^ 0x5C */
  658
+	php_hash_string_xor_char(K2, K1, 0x6A, ops->block_size);
  659
+
  660
+	/* Setup Main Loop to build a long enough result */
  661
+	if (length == 0) {
  662
+		length = ops->digest_size;
  663
+	}
  664
+        digest_length = length;
1
Nikita Popov
nikic added a note June 12, 2012

This line looks incorrectly indented. Maybe using spaces here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Anthony Ferrara

@nikic Thanks! I've updated all of the issues you've identified. The reason for the casts, was that before the refactor it was needed, but I never changed the argument after I extracted the method. You are correct that the second memset is not needed. I've removed that as well.

I'll push the changes once make test passes to ensure I didn't bork anything.

Thanks!

ext/hash/hash.c
((83 lines not shown))
  686
+		/* } */
  687
+
  688
+		/* temp = digest */
  689
+		memcpy(temp, digest, ops->digest_size);
  690
+		for (j = 1; j < iterations; j++) {
  691
+			/* digest = hash_hmac(digest, password) { */
  692
+			php_hash_hmac_round(digest, ops, context, K1, digest, ops->digest_size);
  693
+			php_hash_hmac_round(digest, ops, context, K2, digest, ops->digest_size);
  694
+			/* } */
  695
+			/* temp ^= digest */
  696
+			php_hash_string_xor(temp, temp, digest, ops->digest_size);
  697
+		}
  698
+		/* result += temp */
  699
+		memcpy(result + ((i - 1) * ops->digest_size), temp, ops->digest_size);
  700
+	}
  701
+	/* Zero potentiall sensitive variables */
1
Nikita Popov
nikic added a note June 12, 2012

nit: potentiall should probably be potentially

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
ext/hash/hash.c
@@ -202,10 +203,45 @@ static void php_hash_do_hash(INTERNAL_FUNCTION_PARAMETERS, int isfilename, zend_
202 203
 }
203 204
 /* }}} */
204 205
 
  206
+static inline void php_hash_string_xor_char(unsigned char *out, const unsigned char *in, const unsigned char xor_with, const int length) {
  207
+	int i;
  208
+	for(i=0; i < length; i++) {
1
Nikita Popov
nikic added a note June 12, 2012

nit: This for loop (and the one below it) need a space after the keyword :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
added some commits June 12, 2012
Anthony Ferrara Fix tests to use proper casing 2f1cd2c
Anthony Ferrara More cleanup of documentation and comments, as well as code formatting 03536e8
Anthony Ferrara Merge remote branch 'upstream/master' into hash_pbkdf2
* upstream/master: (101 commits)
  Fixed Bug #62500 (Segfault in DateInterval class when extended)
  Fixed test bug #62312 (warnings changed one more time)
  fix valgrind warning
  fix valgrind warning
  fixed #62433 test for win
  update NEWS
  Fixed bug #62499 (curl_setopt($ch, CURLOPT_COOKIEFILE, "") returns false)
  appease MSVC (doesnt like unary minus of unsigned ints)
  appease MSVC (doesnt like unary minus of unsigned ints)
  appease MSVC (doesnt like unary minus of unsigned ints)
  - Fixed bug #62507 (['REQUEST_TIME'] under mod_php5 returns miliseconds instead of seconds)
  Fixed Bug #62500 (Segfault in DateInterval class when extended)
  Added in NEWS and UPGRADING for feature 55218
  Fix two issues with run-tests.php
  Fix potential integer overflow in nl2br
  Fix potential integer overflow in bin2hex
  This wil be PHP 5.3.16
  Revert change 3f3ad30: There shouldn't be new features in 5.3, especially not if they aren't in 5.4, too.
  fix (signed) integer overflow (part of bug #52550
  fix (signed) integer overflow (part of bug #52550
  ...
731c6fd
Account for PHP Pull Requests php-pulls merged commit 731c6fd into from July 10, 2012
Account for PHP Pull Requests php-pulls closed this July 10, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 8 unique commits by 1 author.

Jun 12, 2012
Anthony Ferrara Create hash_pbkdf2 function addition 6387498
Anthony Ferrara Update NEWS to fix typo, add name 550253f
Anthony Ferrara refactor away un-necessary casts in hashing routines 4918acc
Anthony Ferrara Update error messages to be more inline with PHP standards df3d351
Anthony Ferrara Remove un-needed memset, and replacing stray spaces 43eb8dc
Anthony Ferrara Fix tests to use proper casing 2f1cd2c
Anthony Ferrara More cleanup of documentation and comments, as well as code formatting 03536e8
Jul 10, 2012
Anthony Ferrara Merge remote branch 'upstream/master' into hash_pbkdf2
* upstream/master: (101 commits)
  Fixed Bug #62500 (Segfault in DateInterval class when extended)
  Fixed test bug #62312 (warnings changed one more time)
  fix valgrind warning
  fix valgrind warning
  fixed #62433 test for win
  update NEWS
  Fixed bug #62499 (curl_setopt($ch, CURLOPT_COOKIEFILE, "") returns false)
  appease MSVC (doesnt like unary minus of unsigned ints)
  appease MSVC (doesnt like unary minus of unsigned ints)
  appease MSVC (doesnt like unary minus of unsigned ints)
  - Fixed bug #62507 (['REQUEST_TIME'] under mod_php5 returns miliseconds instead of seconds)
  Fixed Bug #62500 (Segfault in DateInterval class when extended)
  Added in NEWS and UPGRADING for feature 55218
  Fix two issues with run-tests.php
  Fix potential integer overflow in nl2br
  Fix potential integer overflow in bin2hex
  This wil be PHP 5.3.16
  Revert change 3f3ad30: There shouldn't be new features in 5.3, especially not if they aren't in 5.4, too.
  fix (signed) integer overflow (part of bug #52550
  fix (signed) integer overflow (part of bug #52550
  ...
731c6fd
This page is out of date. Refresh to see the latest.
5  NEWS
@@ -12,8 +12,6 @@ PHP                                                                        NEWS
12 12
     (Nikita Popov)
13 13
 
14 14
 - Core:
15  
-  . Fixed bug #62443 (Crypt SHA256/512 Segfaults With Malformed 
16  
-    Salt). (Anthony Ferrara)
17 15
   . Added boolval(). (Jille Timmermans).
18 16
   . Fixed bug #61681 (Malformed grammar). (Nikita Popov, Etienne, Laruence).
19 17
   . Fixed bug #61038 (unpack("a5", "str\0\0") does not work as expected).
@@ -44,6 +42,9 @@ PHP                                                                        NEWS
44 42
 	still exists for backward compatibility but is doing nothing). (Pierrick)
45 43
   . Fixed bug #54995 (Missing CURLINFO_RESPONSE_CODE support). (Pierrick)
46 44
 
  45
+- Hash
  46
+  . Added support for PBKDF2 via hash_pbkdf2(). (Anthony Ferrara)
  47
+
47 48
 - MySQLi
48 49
   . Dropped support for LOAD DATA LOCAL INFILE handlers when using libmysql.
49 50
     Known for stability problems. (Andrey)
210  ext/hash/hash.c
@@ -23,6 +23,7 @@
23 23
 #include "config.h"
24 24
 #endif
25 25
 
  26
+#include <math.h>
26 27
 #include "php_hash.h"
27 28
 #include "ext/standard/info.h"
28 29
 #include "ext/standard/file.h"
@@ -202,10 +203,45 @@ PHP_FUNCTION(hash_file)
202 203
 }
203 204
 /* }}} */
204 205
 
  206
+static inline void php_hash_string_xor_char(unsigned char *out, const unsigned char *in, const unsigned char xor_with, const int length) {
  207
+	int i;
  208
+	for (i=0; i < length; i++) {
  209
+		out[i] = in[i] ^ xor_with;
  210
+	}
  211
+}
  212
+
  213
+static inline void php_hash_string_xor(unsigned char *out, const unsigned char *in, const unsigned char *xor_with, const int length) {
  214
+	int i;
  215
+	for (i=0; i < length; i++) {
  216
+		out[i] = in[i] ^ xor_with[i];
  217
+	}
  218
+}
  219
+
  220
+static inline void php_hash_hmac_prep_key(unsigned char *K, const php_hash_ops *ops, void *context, const unsigned char *key, const int key_len) {
  221
+	memset(K, 0, ops->block_size);
  222
+	if (key_len > ops->block_size) {
  223
+		/* Reduce the key first */
  224
+		ops->hash_init(context);
  225
+		ops->hash_update(context, key, key_len);
  226
+		ops->hash_final(K, context);
  227
+	} else {
  228
+		memcpy(K, key, key_len);
  229
+	}
  230
+	/* XOR the key with 0x36 to get the ipad) */
  231
+	php_hash_string_xor_char(K, K, 0x36, ops->block_size);
  232
+}
  233
+
  234
+static inline void php_hash_hmac_round(unsigned char *final, const php_hash_ops *ops, void *context, const unsigned char *key, const unsigned char *data, const long data_size) {
  235
+	ops->hash_init(context);
  236
+	ops->hash_update(context, key, ops->block_size);
  237
+	ops->hash_update(context, data, data_size);
  238
+	ops->hash_final(final, context);
  239
+}
  240
+
205 241
 static void php_hash_do_hash_hmac(INTERNAL_FUNCTION_PARAMETERS, int isfilename, zend_bool raw_output_default) /* {{{ */
206 242
 {
207 243
 	char *algo, *data, *digest, *key, *K;
208  
-	int algo_len, data_len, key_len, i;
  244
+	int algo_len, data_len, key_len;
209 245
 	zend_bool raw_output = raw_output_default;
210 246
 	const php_hash_ops *ops;
211 247
 	void *context;
@@ -230,52 +266,29 @@ static void php_hash_do_hash_hmac(INTERNAL_FUNCTION_PARAMETERS, int isfilename,
230 266
 	}
231 267
 
232 268
 	context = emalloc(ops->context_size);
233  
-	ops->hash_init(context);
234 269
 
235 270
 	K = emalloc(ops->block_size);
236  
-	memset(K, 0, ops->block_size);
  271
+	digest = emalloc(ops->digest_size + 1);
237 272
 
238  
-	if (key_len > ops->block_size) {
239  
-		/* Reduce the key first */
240  
-		ops->hash_update(context, (unsigned char *) key, key_len);
241  
-		ops->hash_final((unsigned char *) K, context);
242  
-		/* Make the context ready to start over */
243  
-		ops->hash_init(context);
244  
-	} else {
245  
-		memcpy(K, key, key_len);
246  
-	}
247  
-			
248  
-	/* XOR ipad */
249  
-	for(i=0; i < ops->block_size; i++) {
250  
-		K[i] ^= 0x36;
251  
-	}
252  
-	ops->hash_update(context, (unsigned char *) K, ops->block_size);
  273
+	php_hash_hmac_prep_key((unsigned char *) K, ops, context, (unsigned char *) key, key_len);		
253 274
 
254 275
 	if (isfilename) {
255 276
 		char buf[1024];
256 277
 		int n;
257  
-
  278
+		ops->hash_init(context);
  279
+		ops->hash_update(context, (unsigned char *) K, ops->block_size);
258 280
 		while ((n = php_stream_read(stream, buf, sizeof(buf))) > 0) {
259 281
 			ops->hash_update(context, (unsigned char *) buf, n);
260 282
 		}
261 283
 		php_stream_close(stream);
  284
+		ops->hash_final((unsigned char *) digest, context);
262 285
 	} else {
263  
-		ops->hash_update(context, (unsigned char *) data, data_len);
  286
+		php_hash_hmac_round((unsigned char *) digest, ops, context, (unsigned char *) K, (unsigned char *) data, data_len);
264 287
 	}
265 288
 
266  
-	digest = emalloc(ops->digest_size + 1);
267  
-	ops->hash_final((unsigned char *) digest, context);
268  
-
269  
-	/* Convert K to opad -- 0x6A = 0x36 ^ 0x5C */
270  
-	for(i=0; i < ops->block_size; i++) {
271  
-		K[i] ^= 0x6A;
272  
-	}
  289
+	php_hash_string_xor_char((unsigned char *) K, (unsigned char *) K, 0x6A, ops->block_size);
273 290
 
274  
-	/* Feed this result into the outter hash */
275  
-	ops->hash_init(context);
276  
-	ops->hash_update(context, (unsigned char *) K, ops->block_size);
277  
-	ops->hash_update(context, (unsigned char *) digest, ops->digest_size);
278  
-	ops->hash_final((unsigned char *) digest, context);
  291
+	php_hash_hmac_round((unsigned char *) digest, ops, context, (unsigned char *) K, (unsigned char *) digest, ops->digest_size);
279 292
 
280 293
 	/* Zero the key */
281 294
 	memset(K, 0, ops->block_size);
@@ -591,6 +604,128 @@ PHP_FUNCTION(hash_algos)
591 604
 }
592 605
 /* }}} */
593 606
 
  607
+/* {{{ proto string hash_pbkdf2(string algo, string password, string salt, int iterations [, int length = 0, bool raw_output = false])
  608
+Generate a PBKDF2 hash of the given password and salt
  609
+Returns lowercase hexits by default */
  610
+PHP_FUNCTION(hash_pbkdf2)
  611
+{
  612
+	char *returnval, *algo, *salt, *pass = NULL;
  613
+	unsigned char *computed_salt, *digest, *temp, *result, *K1, *K2 = NULL;
  614
+	long loops, i, j, algo_len, pass_len, iterations, length, digest_length = 0;
  615
+	int argc, salt_len = 0;
  616
+	zend_bool raw_output = 0;
  617
+	const php_hash_ops *ops;
  618
+	void *context;
  619
+
  620
+	argc = ZEND_NUM_ARGS();
  621
+	if (zend_parse_parameters(argc TSRMLS_CC, "sssl|lb", &algo, &algo_len, &pass, &pass_len, &salt, &salt_len, &iterations, &length, &raw_output) == FAILURE) {
  622
+		return;
  623
+	}
  624
+
  625
+	ops = php_hash_fetch_ops(algo, algo_len);
  626
+	if (!ops) {
  627
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown hashing algorithm: %s", algo);
  628
+		RETURN_FALSE;
  629
+	}
  630
+
  631
+	if (iterations <= 0) {
  632
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Iterations must be a positive integer: %ld", iterations);
  633
+		RETURN_FALSE;
  634
+	}
  635
+
  636
+	if (length < 0) {
  637
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Length must be greater than or equal to 0: %ld", length);
  638
+		RETURN_FALSE;
  639
+	}
  640
+
  641
+	if (salt_len > INT_MAX - 4) {
  642
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Supplied salt is too long, max of INT_MAX - 4 bytes: %d supplied", salt_len);
  643
+		RETURN_FALSE;
  644
+	}
  645
+
  646
+	context = emalloc(ops->context_size);
  647
+	ops->hash_init(context);
  648
+
  649
+	K1 = emalloc(ops->block_size);
  650
+	K2 = emalloc(ops->block_size);
  651
+	digest = emalloc(ops->digest_size);
  652
+	temp = emalloc(ops->digest_size);
  653
+
  654
+	/* Setup Keys that will be used for all hmac rounds */
  655
+	php_hash_hmac_prep_key(K1, ops, context, (unsigned char *) pass, pass_len);
  656
+	/* Convert K1 to opad -- 0x6A = 0x36 ^ 0x5C */
  657
+	php_hash_string_xor_char(K2, K1, 0x6A, ops->block_size);
  658
+
  659
+	/* Setup Main Loop to build a long enough result */
  660
+	if (length == 0) {
  661
+		length = ops->digest_size;
  662
+	}
  663
+	digest_length = length;
  664
+	if (!raw_output) {
  665
+		digest_length = (long) ceil((float) length / 2.0);
  666
+	}
  667
+
  668
+	loops = (long) ceil((float) digest_length / (float) ops->digest_size);
  669
+
  670
+	result = safe_emalloc(loops, ops->digest_size, 0);
  671
+
  672
+	computed_salt = safe_emalloc(salt_len, 1, 4);
  673
+	memcpy(computed_salt, (unsigned char *) salt, salt_len);
  674
+
  675
+	for (i = 1; i <= loops; i++) {
  676
+		/* digest = hash_hmac(salt + pack('N', i), password) { */
  677
+
  678
+		/* pack("N", i) */
  679
+		computed_salt[salt_len] = (unsigned char) (i >> 24);
  680
+		computed_salt[salt_len + 1] = (unsigned char) ((i & 0xFF0000) >> 16);
  681
+		computed_salt[salt_len + 2] = (unsigned char) ((i & 0xFF00) >> 8);
  682
+		computed_salt[salt_len + 3] = (unsigned char) (i & 0xFF);
  683
+
  684
+		php_hash_hmac_round(digest, ops, context, K1, computed_salt, (long) salt_len + 4);
  685
+		php_hash_hmac_round(digest, ops, context, K2, digest, ops->digest_size);
  686
+		/* } */
  687
+
  688
+		/* temp = digest */
  689
+		memcpy(temp, digest, ops->digest_size);
  690
+
  691
+		/* 
  692
+		 * Note that the loop starting at 1 is intentional, since we've already done
  693
+		 * the first round of the algorithm.
  694
+		 */
  695
+		for (j = 1; j < iterations; j++) {
  696
+			/* digest = hash_hmac(digest, password) { */
  697
+			php_hash_hmac_round(digest, ops, context, K1, digest, ops->digest_size);
  698
+			php_hash_hmac_round(digest, ops, context, K2, digest, ops->digest_size);
  699
+			/* } */
  700
+			/* temp ^= digest */
  701
+			php_hash_string_xor(temp, temp, digest, ops->digest_size);
  702
+		}
  703
+		/* result += temp */
  704
+		memcpy(result + ((i - 1) * ops->digest_size), temp, ops->digest_size);
  705
+	}
  706
+	/* Zero potentially sensitive variables */
  707
+	memset(K1, 0, ops->block_size);
  708
+	memset(K2, 0, ops->block_size);
  709
+	memset(computed_salt, 0, salt_len + 4);
  710
+	efree(K1);
  711
+	efree(K2);
  712
+	efree(computed_salt);
  713
+	efree(context);
  714
+	efree(digest);
  715
+	efree(temp);
  716
+
  717
+	returnval = safe_emalloc(length, 1, 1);
  718
+	if (raw_output) {
  719
+		memcpy(returnval, result, length);
  720
+	} else {
  721
+		php_hash_bin2hex(returnval, result, digest_length);
  722
+	}
  723
+	returnval[length] = 0;
  724
+	efree(result);
  725
+	RETURN_STRINGL(returnval, length, 0);
  726
+}
  727
+/* }}} */
  728
+
594 729
 /* Module Housekeeping */
595 730
 
596 731
 static void php_hash_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) /* {{{ */
@@ -1003,6 +1138,15 @@ ZEND_END_ARG_INFO()
1003 1138
 ZEND_BEGIN_ARG_INFO(arginfo_hash_algos, 0)
1004 1139
 ZEND_END_ARG_INFO()
1005 1140
 
  1141
+ZEND_BEGIN_ARG_INFO_EX(arginfo_hash_pbkdf2, 0, 0, 4)
  1142
+	ZEND_ARG_INFO(0, algo)
  1143
+	ZEND_ARG_INFO(0, password)
  1144
+	ZEND_ARG_INFO(0, salt)
  1145
+	ZEND_ARG_INFO(0, iterations)
  1146
+	ZEND_ARG_INFO(0, length)
  1147
+	ZEND_ARG_INFO(0, raw_output)
  1148
+ZEND_END_ARG_INFO()
  1149
+
1006 1150
 /* BC Land */
1007 1151
 #ifdef PHP_MHASH_BC
1008 1152
 ZEND_BEGIN_ARG_INFO(arginfo_mhash_get_block_size, 0)
@@ -1049,6 +1193,7 @@ const zend_function_entry hash_functions[] = {
1049 1193
 	PHP_FE(hash_copy,								arginfo_hash_copy)
1050 1194
 
1051 1195
 	PHP_FE(hash_algos,								arginfo_hash_algos)
  1196
+	PHP_FE(hash_pbkdf2,								arginfo_hash_pbkdf2)
1052 1197
 
1053 1198
 	/* BC Land */
1054 1199
 #ifdef PHP_HASH_MD5_NOT_IN_CORE
@@ -1105,3 +1250,4 @@ ZEND_GET_MODULE(hash)
1105 1250
  * vim600: noet sw=4 ts=4 fdm=marker
1106 1251
  * vim<600: noet sw=4 ts=4
1107 1252
  */
  1253
+
1  ext/hash/php_hash.h
@@ -127,6 +127,7 @@ PHP_FUNCTION(hash_update_stream);
127 127
 PHP_FUNCTION(hash_update_file);
128 128
 PHP_FUNCTION(hash_final);
129 129
 PHP_FUNCTION(hash_algos);
  130
+PHP_FUNCTION(hash_pbkdf2);
130 131
 
131 132
 PHP_HASH_API const php_hash_ops *php_hash_fetch_ops(const char *algo, int algo_len);
132 133
 PHP_HASH_API void php_hash_register_algo(const char *algo, const php_hash_ops *ops);
37  ext/hash/tests/hash_pbkdf2_basic.phpt
... ...
@@ -0,0 +1,37 @@
  1
+--TEST--
  2
+Test hash_pbkdf2() function : basic functionality
  3
+--SKIPIF--
  4
+<?php extension_loaded('hash') or die('skip: hash extension not loaded.'); ?>
  5
+--FILE--
  6
+<?php
  7
+
  8
+/* Prototype  : string hash_hmac  ( string $algo  , string $data  , string $key  [, bool $raw_output  ] )
  9
+ * Description: Generate a keyed hash value using the HMAC method
  10
+ * Source code: ext/hash/hash.c
  11
+ * Alias to functions:
  12
+*/
  13
+
  14
+echo "*** Testing hash_pbkdf2() : basic functionality ***\n";
  15
+
  16
+echo "sha1: " . hash_pbkdf2('sha1', 'password', 'salt', 1, 20)."\n";
  17
+echo "sha1(raw): " . bin2hex(hash_pbkdf2('sha1', 'password', 'salt', 1, 20, TRUE))."\n";
  18
+echo "sha1(rounds): " . hash_pbkdf2('sha1', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 25)."\n";
  19
+echo "sha1(rounds)(raw): " . bin2hex(hash_pbkdf2('sha1', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 25, TRUE))."\n";
  20
+echo "sha256: " . hash_pbkdf2('sha256', 'password', 'salt', 1, 20)."\n";
  21
+echo "sha256(raw): " . bin2hex(hash_pbkdf2('sha256', 'password', 'salt', 1, 20, TRUE))."\n";
  22
+echo "sha256(rounds): " . hash_pbkdf2('sha256', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 40)."\n";
  23
+echo "sha256(rounds)(raw): " . bin2hex(hash_pbkdf2('sha256', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 40, TRUE))."\n";
  24
+
  25
+?>
  26
+===Done===
  27
+--EXPECT--
  28
+*** Testing hash_pbkdf2() : basic functionality ***
  29
+sha1: 0c60c80f961f0e71f3a9
  30
+sha1(raw): 0c60c80f961f0e71f3a9b524af6012062fe037a6
  31
+sha1(rounds): 3d2eec4fe41c849b80c8d8366
  32
+sha1(rounds)(raw): 3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038
  33
+sha256: 120fb6cffcf8b32c43e7
  34
+sha256(raw): 120fb6cffcf8b32c43e7225256c4f837a86548c9
  35
+sha256(rounds): 348c89dbcbd32b2f32d814b8116e84cf2b17347e
  36
+sha256(rounds)(raw): 348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9
  37
+===Done===
78  ext/hash/tests/hash_pbkdf2_error.phpt
... ...
@@ -0,0 +1,78 @@
  1
+--TEST--
  2
+Test hash_pbkdf2() function : error functionality
  3
+--SKIPIF--
  4
+<?php extension_loaded('hash') or die('skip: hash extension not loaded.'); ?>
  5
+--FILE--
  6
+<?php
  7
+
  8
+/* {{{ proto string hash_pbkdf2(string algo, string password, string salt, int iterations [, int length = 0, bool raw_output = false])
  9
+Generate a PBKDF2 hash of the given password and salt
  10
+Returns lowercase hexbits by default */
  11
+
  12
+echo "*** Testing hash_pbkdf2() : error conditions ***\n";
  13
+
  14
+$password = 'password';
  15
+$salt = 'salt';
  16
+
  17
+echo "\n-- Testing hash_pbkdf2() function with less than expected no. of arguments --\n";
  18
+var_dump(@hash_pbkdf2());
  19
+echo $php_errormsg . "\n";
  20
+var_dump(@hash_pbkdf2('crc32'));
  21
+echo $php_errormsg . "\n";
  22
+var_dump(@hash_pbkdf2('crc32', $password));
  23
+echo $php_errormsg . "\n";
  24
+var_dump(@hash_pbkdf2('crc32', $password, $salt));
  25
+echo $php_errormsg . "\n";
  26
+
  27
+echo "\n-- Testing hash_pbkdf2() function with more than expected no. of arguments --\n";
  28
+var_dump(@hash_pbkdf2('crc32', $password, $salt, 10, 10, true, 'extra arg'));
  29
+echo $php_errormsg . "\n";
  30
+
  31
+echo "\n-- Testing hash_pbkdf2() function with invalid hash algorithm --\n";
  32
+var_dump(@hash_pbkdf2('foo', $password, $salt, 1));
  33
+echo $php_errormsg . "\n";
  34
+
  35
+echo "\n-- Testing hash_pbkdf2() function with invalid iterations --\n";
  36
+var_dump(@hash_pbkdf2('md5', $password, $salt, 0));
  37
+echo $php_errormsg . "\n";
  38
+var_dump(@hash_pbkdf2('md5', $password, $salt, -1));
  39
+echo $php_errormsg . "\n";
  40
+
  41
+echo "\n-- Testing hash_pbkdf2() function with invalid length --\n";
  42
+var_dump(@hash_pbkdf2('md5', $password, $salt, 1, -1));
  43
+echo $php_errormsg . "\n\n";
  44
+
  45
+?>
  46
+===Done===
  47
+--EXPECT--
  48
+*** Testing hash_pbkdf2() : error conditions ***
  49
+
  50
+-- Testing hash_pbkdf2() function with less than expected no. of arguments --
  51
+NULL
  52
+hash_pbkdf2() expects at least 4 parameters, 0 given
  53
+NULL
  54
+hash_pbkdf2() expects at least 4 parameters, 1 given
  55
+NULL
  56
+hash_pbkdf2() expects at least 4 parameters, 2 given
  57
+NULL
  58
+hash_pbkdf2() expects at least 4 parameters, 3 given
  59
+
  60
+-- Testing hash_pbkdf2() function with more than expected no. of arguments --
  61
+NULL
  62
+hash_pbkdf2() expects at most 6 parameters, 7 given
  63
+
  64
+-- Testing hash_pbkdf2() function with invalid hash algorithm --
  65
+bool(false)
  66
+hash_pbkdf2(): Unknown hashing algorithm: foo
  67
+
  68
+-- Testing hash_pbkdf2() function with invalid iterations --
  69
+bool(false)
  70
+hash_pbkdf2(): Iterations must be a positive integer: 0
  71
+bool(false)
  72
+hash_pbkdf2(): Iterations must be a positive integer: -1
  73
+
  74
+-- Testing hash_pbkdf2() function with invalid length --
  75
+bool(false)
  76
+hash_pbkdf2(): Length must be greater than or equal to 0: -1
  77
+
  78
+===Done===
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.