php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login

Patch hash_pbkdf2 for hash related Bug #60813

Patch version 2012-01-19 22:00 UTC

Return to Bug #60813 | Download this patch
Patch Revisions:

Developer: ircmaxell

Index: ext/hash/php_hash.h
===================================================================
--- ext/hash/php_hash.h	(revision 322459)
+++ ext/hash/php_hash.h	(working copy)
@@ -127,6 +127,7 @@
 PHP_FUNCTION(hash_update_file);
 PHP_FUNCTION(hash_final);
 PHP_FUNCTION(hash_algos);
+PHP_FUNCTION(hash_pbkdf2);
 
 PHP_HASH_API const php_hash_ops *php_hash_fetch_ops(const char *algo, int algo_len);
 PHP_HASH_API void php_hash_register_algo(const char *algo, const php_hash_ops *ops);
Index: ext/hash/tests/hash_pbkdf2_basic.phpt
===================================================================
--- ext/hash/tests/hash_pbkdf2_basic.phpt	(revision 0)
+++ ext/hash/tests/hash_pbkdf2_basic.phpt	(revision 0)
@@ -0,0 +1,37 @@
+--TEST--
+Test hash_pbkdf2() function : basic functionality
+--SKIPIF--
+<?php extension_loaded('hash') or die('skip: hash extension not loaded.'); ?>
+--FILE--
+<?php
+
+/* Prototype  : string hash_hmac  ( string $algo  , string $data  , string $key  [, bool $raw_output  ] )
+ * Description: Generate a keyed hash value using the HMAC method
+ * Source code: ext/hash/hash.c
+ * Alias to functions:
+*/
+
+echo "*** Testing hash_pbkdf2() : basic functionality ***\n";
+
+echo "sha1: " . hash_pbkdf2('sha1', 'password', 'salt', 1, 20)."\n";
+echo "sha1(raw): " . bin2hex(hash_pbkdf2('sha1', 'password', 'salt', 1, 20, TRUE))."\n";
+echo "sha1(rounds): " . hash_pbkdf2('sha1', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 25)."\n";
+echo "sha1(rounds)(raw): " . bin2hex(hash_pbkdf2('sha1', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 25, TRUE))."\n";
+echo "sha256: " . hash_pbkdf2('sha256', 'password', 'salt', 1, 20)."\n";
+echo "sha256(raw): " . bin2hex(hash_pbkdf2('sha256', 'password', 'salt', 1, 20, TRUE))."\n";
+echo "sha256(rounds): " . hash_pbkdf2('sha256', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 40)."\n";
+echo "sha256(rounds)(raw): " . bin2hex(hash_pbkdf2('sha256', 'passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 40, TRUE))."\n";
+
+?>
+===Done===
+--EXPECT--
+*** Testing hash_pbkdf2() : basic functionality ***
+sha1: 0c60c80f961f0e71f3a9
+sha1(raw): 0c60c80f961f0e71f3a9b524af6012062fe037a6
+sha1(rounds): 3d2eec4fe41c849b80c8d8366
+sha1(rounds)(raw): 3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038
+sha256: 120fb6cffcf8b32c43e7
+sha256(raw): 120fb6cffcf8b32c43e7225256c4f837a86548c9
+sha256(rounds): 348c89dbcbd32b2f32d814b8116e84cf2b17347e
+sha256(rounds)(raw): 348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9
+===Done===
Index: ext/hash/tests/hash_pbkdf2_error.phpt
===================================================================
--- ext/hash/tests/hash_pbkdf2_error.phpt	(revision 0)
+++ ext/hash/tests/hash_pbkdf2_error.phpt	(revision 0)
@@ -0,0 +1,78 @@
+--TEST--
+Test hash_pbkdf2() function : error functionality
+--SKIPIF--
+<?php extension_loaded('hash') or die('skip: hash extension not loaded.'); ?>
+--FILE--
+<?php
+
+/* {{{ proto string hash_pbkdf2(string algo, string password, string salt, int iterations [, int length = 0, bool raw_output = false])
+Generate a PBKDF2 hash of the given password and salt
+Returns lowercase hexbits by default */
+
+echo "*** Testing hash_pbkdf2() : error conditions ***\n";
+
+$password = 'password';
+$salt = 'salt';
+
+echo "\n-- Testing hash_pbkdf2() function with less than expected no. of arguments --\n";
+var_dump(@hash_pbkdf2());
+echo $php_errormsg . "\n";
+var_dump(@hash_pbkdf2('crc32'));
+echo $php_errormsg . "\n";
+var_dump(@hash_pbkdf2('crc32', $password));
+echo $php_errormsg . "\n";
+var_dump(@hash_pbkdf2('crc32', $password, $salt));
+echo $php_errormsg . "\n";
+
+echo "\n-- Testing hash_pbkdf2() function with more than expected no. of arguments --\n";
+var_dump(@hash_pbkdf2('crc32', $password, $salt, 10, 10, true, 'extra arg'));
+echo $php_errormsg . "\n";
+
+echo "\n-- Testing hash_pbkdf2() function with invalid hash algorithm --\n";
+var_dump(@hash_pbkdf2('foo', $password, $salt, 1));
+echo $php_errormsg . "\n";
+
+echo "\n-- Testing hash_pbkdf2() function with invalid iterations --\n";
+var_dump(@hash_pbkdf2('md5', $password, $salt, 0));
+echo $php_errormsg . "\n";
+var_dump(@hash_pbkdf2('md5', $password, $salt, -1));
+echo $php_errormsg . "\n";
+
+echo "\n-- Testing hash_pbkdf2() function with invalid length --\n";
+var_dump(@hash_pbkdf2('md5', $password, $salt, 1, -1));
+echo $php_errormsg . "\n\n";
+
+?>
+===Done===
+--EXPECT--
+*** Testing hash_pbkdf2() : error conditions ***
+
+-- Testing hash_pbkdf2() function with less than expected no. of arguments --
+NULL
+hash_pbkdf2() expects at least 4 parameters, 0 given
+NULL
+hash_pbkdf2() expects at least 4 parameters, 1 given
+NULL
+hash_pbkdf2() expects at least 4 parameters, 2 given
+NULL
+hash_pbkdf2() expects at least 4 parameters, 3 given
+
+-- Testing hash_pbkdf2() function with more than expected no. of arguments --
+NULL
+hash_pbkdf2() expects at most 6 parameters, 7 given
+
+-- Testing hash_pbkdf2() function with invalid hash algorithm --
+bool(false)
+hash_pbkdf2(): Unknown hashing algorithm: foo
+
+-- Testing hash_pbkdf2() function with invalid iterations --
+bool(false)
+hash_pbkdf2(): Iterations Must Be A Positive Integer: 0
+bool(false)
+hash_pbkdf2(): Iterations Must Be A Positive Integer: -1
+
+-- Testing hash_pbkdf2() function with invalid length --
+bool(false)
+hash_pbkdf2(): Length Must Be Greater Than Or Equal To 0: -1
+
+===Done===
Index: ext/hash/hash.c
===================================================================
--- ext/hash/hash.c	(revision 322459)
+++ ext/hash/hash.c	(working copy)
@@ -23,6 +23,7 @@
 #include "config.h"
 #endif
 
+#include <math.h>
 #include "php_hash.h"
 #include "ext/standard/info.h"
 #include "ext/standard/file.h"
@@ -202,10 +203,45 @@
 }
 /* }}} */
 
+static inline void php_hash_string_xor_char(unsigned char *out, const unsigned char *in, const unsigned char xor_with, const int length) {
+	int i;
+	for(i=0; i < length; i++) {
+		out[i] = in[i] ^ xor_with;
+	}
+}
+
+static inline void php_hash_string_xor(unsigned char *out, const unsigned char *in, const unsigned char *xor_with, const int length) {
+	int i;
+	for(i=0; i < length; i++) {
+		out[i] = in[i] ^ xor_with[i];
+	}
+}
+
+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) {
+	memset(K, 0, ops->block_size);
+	if (key_len > ops->block_size) {
+		/* Reduce the key first */
+		ops->hash_init(context);
+		ops->hash_update(context, (unsigned char *) key, key_len);
+		ops->hash_final((unsigned char *) K, context);
+	} else {
+		memcpy(K, key, key_len);
+	}
+	/* XOR the key with 0x36 to get the ipad) */
+	php_hash_string_xor_char(K, K, 0x36, ops->block_size);
+}
+
+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) {
+	ops->hash_init(context);
+	ops->hash_update(context, key, ops->block_size);
+	ops->hash_update(context, data, data_size);
+	ops->hash_final(final, context);
+}
+
 static void php_hash_do_hash_hmac(INTERNAL_FUNCTION_PARAMETERS, int isfilename, zend_bool raw_output_default) /* {{{ */
 {
 	char *algo, *data, *digest, *key, *K;
-	int algo_len, data_len, key_len, i;
+	int algo_len, data_len, key_len;
 	zend_bool raw_output = raw_output_default;
 	const php_hash_ops *ops;
 	void *context;
@@ -230,53 +266,30 @@
 	}
 
 	context = emalloc(ops->context_size);
-	ops->hash_init(context);
 
 	K = emalloc(ops->block_size);
-	memset(K, 0, ops->block_size);
+	digest = emalloc(ops->digest_size + 1);
 
-	if (key_len > ops->block_size) {
-		/* Reduce the key first */
-		ops->hash_update(context, (unsigned char *) key, key_len);
-		ops->hash_final((unsigned char *) K, context);
-		/* Make the context ready to start over */
-		ops->hash_init(context);
-	} else {
-		memcpy(K, key, key_len);
-	}
-			
-	/* XOR ipad */
-	for(i=0; i < ops->block_size; i++) {
-		K[i] ^= 0x36;
-	}
-	ops->hash_update(context, (unsigned char *) K, ops->block_size);
+	php_hash_hmac_prep_key((unsigned char *) K, ops, context, (unsigned char *) key, key_len);		
 
 	if (isfilename) {
 		char buf[1024];
 		int n;
-
+		ops->hash_init(context);
+		ops->hash_update(context, (unsigned char *) K, ops->block_size);
 		while ((n = php_stream_read(stream, buf, sizeof(buf))) > 0) {
 			ops->hash_update(context, (unsigned char *) buf, n);
 		}
 		php_stream_close(stream);
+		ops->hash_final((unsigned char *) digest, context);
 	} else {
-		ops->hash_update(context, (unsigned char *) data, data_len);
+		php_hash_hmac_round((unsigned char *) digest, ops, context, (unsigned char *) K, (unsigned char *) data, data_len);
 	}
 
-	digest = emalloc(ops->digest_size + 1);
-	ops->hash_final((unsigned char *) digest, context);
+	php_hash_string_xor_char((unsigned char *) K, (unsigned char *) K, 0x6A, ops->block_size);
 
-	/* Convert K to opad -- 0x6A = 0x36 ^ 0x5C */
-	for(i=0; i < ops->block_size; i++) {
-		K[i] ^= 0x6A;
-	}
+	php_hash_hmac_round((unsigned char *) digest, ops, context, (unsigned char *) K, (unsigned char *) digest, ops->digest_size);
 
-	/* Feed this result into the outter hash */
-	ops->hash_init(context);
-	ops->hash_update(context, (unsigned char *) K, ops->block_size);
-	ops->hash_update(context, (unsigned char *) digest, ops->digest_size);
-	ops->hash_final((unsigned char *) digest, context);
-
 	/* Zero the key */
 	memset(K, 0, ops->block_size);
 	efree(K);
@@ -591,6 +604,124 @@
 }
 /* }}} */
 
+/* {{{ proto string hash_pbkdf2(string algo, string password, string salt, int iterations [, int length = 0, bool raw_output = false])
+Generate a PBKDF2 hash of the given password and salt
+Returns lowercase hexits by default */
+PHP_FUNCTION(hash_pbkdf2)
+{
+	char *returnval, *algo, *salt, *pass = NULL;
+	unsigned char *computed_salt, *digest, *temp, *result, *K1, *K2 = NULL;
+	long loops, i, j, algo_len, pass_len, iterations, length, digest_length = 0;
+	int argc, salt_len = 0;
+	zend_bool raw_output = 0;
+	const php_hash_ops *ops;
+	void *context;
+
+	argc = ZEND_NUM_ARGS();
+	if (zend_parse_parameters(argc TSRMLS_CC, "sssl|lb", &algo, &algo_len, &pass, &pass_len, &salt, &salt_len, &iterations, &length, &raw_output) == FAILURE) {
+		return;
+	}
+
+	ops = php_hash_fetch_ops(algo, algo_len);
+	if (!ops) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown hashing algorithm: %s", algo);
+		RETURN_FALSE;
+	}
+
+	if (iterations <= 0) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Iterations Must Be A Positive Integer: %ld", iterations);
+		RETURN_FALSE;
+	}
+
+	if (length < 0) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Length Must Be Greater Than Or Equal To 0: %ld", length);
+		RETURN_FALSE;
+	}
+
+	if (salt_len > INT_MAX - 4) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Supplied salt is too long, max of INT_MAX - 4 bytes: %d supplied", salt_len);
+		RETURN_FALSE;
+	}
+
+	context = emalloc(ops->context_size);
+	ops->hash_init(context);
+
+	K1 = emalloc(ops->block_size);
+	K2 = emalloc(ops->block_size);
+	digest = emalloc(ops->digest_size);
+	temp = emalloc(ops->digest_size);
+
+	/* Setup Keys that will be used for all hmac rounds */
+	memset(K2, 0, ops->block_size);
+	php_hash_hmac_prep_key(K1, ops, context, (unsigned char *) pass, pass_len);
+	/* Convert K1 to opad -- 0x6A = 0x36 ^ 0x5C */
+	php_hash_string_xor_char(K2, K1, 0x6A, ops->block_size);
+
+	/* Setup Main Loop to build a long enough result */
+	if (length == 0) {
+		length = ops->digest_size;
+	}
+        digest_length = length;
+	if (!raw_output) {
+		digest_length = (long) ceil((float) length / 2.0);
+	}
+
+	loops = (long) ceil((float) digest_length / (float) ops->digest_size);
+
+	result = safe_emalloc(loops, ops->digest_size, 0);
+
+	computed_salt = safe_emalloc(salt_len, 1, 4);
+	memcpy(computed_salt, (unsigned char *) salt, salt_len);
+
+	for (i = 1; i <= loops; i++) {
+		/* digest = hash_hmac(salt + pack('N', i), password) { */
+
+		/* pack("N", i) */
+		computed_salt[salt_len] = (unsigned char) (i >> 24);
+		computed_salt[salt_len + 1] = (unsigned char) ((i & 0xFF0000) >> 16);
+		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, (long) salt_len + 4);
+		php_hash_hmac_round(digest, ops, context, K2, digest, ops->digest_size);
+		/* } */
+
+		/* temp = digest */
+		memcpy(temp, digest, ops->digest_size);
+		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);
+			/* } */
+			/* temp ^= digest */
+			php_hash_string_xor(temp, temp, digest, ops->digest_size);
+		}
+		/* result += temp */
+		memcpy(result + ((i - 1) * ops->digest_size), temp, ops->digest_size);
+	}
+	/* Zero potentiall sensitive variables */
+	memset(K1, 0, ops->block_size);
+	memset(K2, 0, ops->block_size);
+	memset(computed_salt, 0, salt_len + 4);
+	efree(K1);
+	efree(K2);
+	efree(computed_salt);
+	efree(context);
+	efree(digest);
+	efree(temp);
+
+	returnval = safe_emalloc(length, 1, 1);
+	if (raw_output) {
+		memcpy(returnval, result, length);
+	} else {
+		php_hash_bin2hex(returnval, result, digest_length);
+	}
+	returnval[length] = 0;
+	efree(result);
+	RETURN_STRINGL(returnval, length, 0);
+}
+/* }}} */
+
 /* Module Housekeeping */
 
 static void php_hash_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) /* {{{ */
@@ -1003,6 +1134,15 @@
 ZEND_BEGIN_ARG_INFO(arginfo_hash_algos, 0)
 ZEND_END_ARG_INFO()
 
+ZEND_BEGIN_ARG_INFO_EX(arginfo_hash_pbkdf2, 0, 0, 4)
+	ZEND_ARG_INFO(0, algo)
+	ZEND_ARG_INFO(0, password)
+	ZEND_ARG_INFO(0, salt)
+	ZEND_ARG_INFO(0, iterations)
+	ZEND_ARG_INFO(0, length)
+	ZEND_ARG_INFO(0, raw_output)
+ZEND_END_ARG_INFO()
+
 /* BC Land */
 #ifdef PHP_MHASH_BC
 ZEND_BEGIN_ARG_INFO(arginfo_mhash_get_block_size, 0)
@@ -1049,6 +1189,7 @@
 	PHP_FE(hash_copy,								arginfo_hash_copy)
 
 	PHP_FE(hash_algos,								arginfo_hash_algos)
+	PHP_FE(hash_pbkdf2,								arginfo_hash_pbkdf2)
 
 	/* BC Land */
 #ifdef PHP_HASH_MD5_NOT_IN_CORE
@@ -1105,3 +1246,4 @@
  * vim600: noet sw=4 ts=4 fdm=marker
  * vim<600: noet sw=4 ts=4
  */
+
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sun Dec 22 16:01:31 2024 UTC