From: Rouven Weßling Date: Sun, 10 Nov 2013 21:37:10 +0000 (+0100) Subject: Add hash_equals() to perform string comparisons that are not vulnerable to timing... X-Git-Tag: php-5.6.0beta1~3^2~68^2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=2148a88b3d6d8b25151e2da168c37f5e3bdfd22b;p=php Add hash_equals() to perform string comparisons that are not vulnerable to timing attacks. --- diff --git a/NEWS b/NEWS index c5fbb6cd99..a2f563b960 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,8 @@ PHP NEWS - Hash: . Fixed bug #66698 (Missing FNV1a32 and FNV1a64 hash functions). (Michael M Slusarz). + . Implemented timing attack safe string comparison function + (RFC: https://wiki.php.net/rfc/timing_attack). (Rouven Weßling) - Intl: . Fixed bug #66873 (A reproductible crash in UConverter when given invalid diff --git a/UPGRADING b/UPGRADING index 9cfc0a642d..8b20f512f2 100755 --- a/UPGRADING +++ b/UPGRADING @@ -71,6 +71,9 @@ PHP 5.6 UPGRADE NOTES - Added use function and use const. (https://wiki.php.net/rfc/use_function) +- Added a function for timing attack safe string comparison + (https://wiki.php.net/rfc/timing_attack) + - Added gost-crypto (CryptoPro S-box) hash algorithm. - Stream wrappers verify peer certificates and host names by default in @@ -182,6 +185,9 @@ PHP 5.6 UPGRADE NOTES 5. New Functions ======================================== +- Hash + Added hash_equals($known_string, $user_string) + - GMP: Added gmp_root($a, $nth) and gmp_rootrem($a, $nth) for calculating nth roots. diff --git a/ext/hash/hash.c b/ext/hash/hash.c index 41c7a70d2b..f14437d96f 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -728,6 +728,46 @@ PHP_FUNCTION(hash_pbkdf2) } /* }}} */ +/* {{{ proto bool hash_equals(string known_string, string user_string) + Compares two strings using the same time whether they're equal or not. + A difference in length will leak */ +PHP_FUNCTION(hash_equals) +{ + zval *known_zval, *user_zval; + char *known_str, *user_str; + int result = 0, j; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &known_zval, &user_zval) == FAILURE) { + return; + } + + /* We only allow comparing string to prevent unexpected results. */ + if (Z_TYPE_P(known_zval) != IS_STRING) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expected known_string to be a string, %s given", zend_zval_type_name(known_zval)); + RETURN_FALSE; + } + + if (Z_TYPE_P(user_zval) != IS_STRING) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expected user_string to be a string, %s given", zend_zval_type_name(user_zval)); + RETURN_FALSE; + } + + if (Z_STRLEN_P(known_zval) != Z_STRLEN_P(user_zval)) { + RETURN_FALSE; + } + + known_str = Z_STRVAL_P(known_zval); + user_str = Z_STRVAL_P(user_zval); + + /* This is security sensitive code. Do not optimize this for speed. */ + for (j = 0; j < Z_STRLEN_P(known_zval); j++) { + result |= known_str[j] ^ user_str[j]; + } + + RETURN_BOOL(0 == result); +} +/* }}} */ + /* Module Housekeeping */ static void php_hash_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) /* {{{ */ @@ -1152,6 +1192,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_equals, 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) @@ -1199,6 +1244,7 @@ const zend_function_entry hash_functions[] = { PHP_FE(hash_algos, arginfo_hash_algos) PHP_FE(hash_pbkdf2, arginfo_hash_pbkdf2) + PHP_FE(hash_equals, arginfo_hash_equals) /* BC Land */ #ifdef PHP_HASH_MD5_NOT_IN_CORE diff --git a/ext/hash/php_hash.h b/ext/hash/php_hash.h index c5cb6604db..c75b930936 100644 --- a/ext/hash/php_hash.h +++ b/ext/hash/php_hash.h @@ -136,6 +136,7 @@ PHP_FUNCTION(hash_update_file); PHP_FUNCTION(hash_final); PHP_FUNCTION(hash_algos); PHP_FUNCTION(hash_pbkdf2); +PHP_FUNCTION(hash_equals); 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_equals.phpt b/ext/hash/tests/hash_equals.phpt new file mode 100644 index 0000000000..8f87985f47 --- /dev/null +++ b/ext/hash/tests/hash_equals.phpt @@ -0,0 +1,43 @@ +--TEST-- +hash_equals() function +--FILE-- +