diff options
author | Rouven Weßling <me@rouvenwessling.de> | 2013-11-10 22:37:10 +0100 |
---|---|---|
committer | Rouven Weßling <me@rouvenwessling.de> | 2014-03-17 09:37:28 +0100 |
commit | 2148a88b3d6d8b25151e2da168c37f5e3bdfd22b (patch) | |
tree | ea74209958d0b06f6376e6319c1f4e5db433dc70 /ext/hash | |
parent | 2499b6518c4b0112c79295145d5f09d3658c31f4 (diff) | |
download | php-git-2148a88b3d6d8b25151e2da168c37f5e3bdfd22b.tar.gz |
Add hash_equals() to perform string comparisons that are not vulnerable to timing attacks.
Diffstat (limited to 'ext/hash')
-rw-r--r-- | ext/hash/hash.c | 46 | ||||
-rw-r--r-- | ext/hash/php_hash.h | 1 | ||||
-rw-r--r-- | ext/hash/tests/hash_equals.phpt | 43 |
3 files changed, 90 insertions, 0 deletions
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-- +<?php +var_dump(hash_equals("same", "same")); +var_dump(hash_equals("not1same", "not2same")); +var_dump(hash_equals("short", "longer")); +var_dump(hash_equals("longer", "short")); +var_dump(hash_equals("", "notempty")); +var_dump(hash_equals("notempty", "")); +var_dump(hash_equals("", "")); +var_dump(hash_equals(123, "NaN")); +var_dump(hash_equals("NaN", 123)); +var_dump(hash_equals(123, 123)); +var_dump(hash_equals(null, "")); +var_dump(hash_equals(null, 123)); +var_dump(hash_equals(null, null)); +--EXPECTF-- +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(true) + +Warning: hash_equals(): Expected known_string to be a string, integer given in %s on line %d +bool(false) + +Warning: hash_equals(): Expected user_string to be a string, integer given in %s on line %d +bool(false) + +Warning: hash_equals(): Expected known_string to be a string, integer given in %s on line %d +bool(false) + +Warning: hash_equals(): Expected known_string to be a string, null given in %s on line %d +bool(false) + +Warning: hash_equals(): Expected known_string to be a string, null given in %s on line %d +bool(false) + +Warning: hash_equals(): Expected known_string to be a string, null given in %s on line %d +bool(false) |