summaryrefslogtreecommitdiff
path: root/ext/hash
diff options
context:
space:
mode:
authorRouven Weßling <me@rouvenwessling.de>2013-11-10 22:37:10 +0100
committerRouven Weßling <me@rouvenwessling.de>2014-03-17 09:37:28 +0100
commit2148a88b3d6d8b25151e2da168c37f5e3bdfd22b (patch)
treeea74209958d0b06f6376e6319c1f4e5db433dc70 /ext/hash
parent2499b6518c4b0112c79295145d5f09d3658c31f4 (diff)
downloadphp-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.c46
-rw-r--r--ext/hash/php_hash.h1
-rw-r--r--ext/hash/tests/hash_equals.phpt43
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)