From 76fbc0589c814fdd09b60dcd7326c64e883cd9d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rouven=20We=C3=9Fling?= Date: Sun, 10 Nov 2013 22:37:10 +0100 Subject: [PATCH] Add hash_compare() to perform string comparisons that are not vulnerable to timing attacks. --- ext/hash/hash.c | 38 ++++++++++++++++++++++++++++++++ ext/hash/php_hash.h | 1 + ext/hash/tests/hash_compare.phpt | 31 ++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 ext/hash/tests/hash_compare.phpt diff --git a/ext/hash/hash.c b/ext/hash/hash.c index 928ec778dcdb1..7810cf7214c8b 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -37,6 +37,10 @@ HashTable php_hash_hashtable; # define DEFAULT_CONTEXT NULL #endif +#ifndef MAX +# define MAX(a,b) ((a)<(b)?(b):(a)) +#endif + #ifdef PHP_MHASH_BC struct mhash_bc_entry { char *mhash_name; @@ -729,6 +733,34 @@ PHP_FUNCTION(hash_pbkdf2) } /* }}} */ +/* {{{ proto bool hash_compare(string known_string, string user_string) + Compares two strings using the same time whether they're equal or not */ +PHP_FUNCTION(hash_compare) +{ + char *known_str, *user_str; + int known_len, user_len, mod_len; + int result, j; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &known_str, &known_len, &user_str, &user_len) == FAILURE) { + return; + } + + /** + * If known_string has a length of 0 we set the length to 1, + * this will cause us to compare all bytes of userString with the null byte which fails + */ + mod_len = MAX(known_len, 1); + + /* This is security sensitive code. Do not optimize this for speed. */ + result = known_len - user_len; + for (j = 0; j < user_len; j++) { + result |= known_str[j % mod_len] ^ user_str[j]; + } + + RETURN_BOOL(0 == result); +} +/* }}} */ + /* Module Housekeeping */ static void php_hash_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) /* {{{ */ @@ -1151,6 +1183,11 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_hash_pbkdf2, 0, 0, 4) ZEND_ARG_INFO(0, raw_output) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO(arginfo_hash_compare, 0) + ZEND_ARG_INFO(0, known_string) + ZEND_ARG_INFO(0, user_string) +ZEND_END_ARG_INFO() + /* BC Land */ #ifdef PHP_MHASH_BC ZEND_BEGIN_ARG_INFO(arginfo_mhash_get_block_size, 0) @@ -1198,6 +1235,7 @@ const zend_function_entry hash_functions[] = { PHP_FE(hash_algos, arginfo_hash_algos) PHP_FE(hash_pbkdf2, arginfo_hash_pbkdf2) + PHP_FE(hash_compare, arginfo_hash_compare) /* BC Land */ #ifdef PHP_HASH_MD5_NOT_IN_CORE diff --git a/ext/hash/php_hash.h b/ext/hash/php_hash.h index a104522c8888d..59221ac0489bf 100644 --- a/ext/hash/php_hash.h +++ b/ext/hash/php_hash.h @@ -134,6 +134,7 @@ PHP_FUNCTION(hash_update_file); PHP_FUNCTION(hash_final); PHP_FUNCTION(hash_algos); PHP_FUNCTION(hash_pbkdf2); +PHP_FUNCTION(hash_compare); 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); diff --git a/ext/hash/tests/hash_compare.phpt b/ext/hash/tests/hash_compare.phpt new file mode 100644 index 0000000000000..d98d541c140a5 --- /dev/null +++ b/ext/hash/tests/hash_compare.phpt @@ -0,0 +1,31 @@ +--TEST-- +hash_compare() function +--FILE-- +