Created
January 19, 2012 18:30
-
-
Save ircmaxell/1641725 to your computer and use it in GitHub Desktop.
hash_pbkdf2() addition patch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
*/ | |
+ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment