Add hash_equals() to ext/hash compat layer

Introduced in PHP 5.6 Beta 1 (unfortunately, still undocumented).
RFC: https://wiki.php.net/rfc/timing_attack

(Yes, I am aware that the RFC talks about hash_compare(), the function was later renamed in the implementation.)
diff --git a/system/core/compat/hash.php b/system/core/compat/hash.php
index a9f59f1..b217010 100644
--- a/system/core/compat/hash.php
+++ b/system/core/compat/hash.php
@@ -39,6 +39,52 @@
 
 // ------------------------------------------------------------------------
 
+if (is_php('5.6'))
+{
+	return;
+}
+
+// ------------------------------------------------------------------------
+
+if ( ! function_exists('hash_equals'))
+{
+	/**
+	 * hash_equals()
+	 *
+	 * @link	http://php.net/hash_equals
+	 * @param	string	$known_string
+	 * @param	string	$user_string
+	 * @return	bool
+	 */
+	function hash_equals($known_string, $user_string)
+	{
+		if ( ! is_string($known_string))
+		{
+			trigger_error('hash_equals(): Expected known_string to be a string, '.strtolower(gettype($known_string)).' given', E_USER_WARNING);
+			return FALSE;
+		}
+		elseif ( ! is_string($user_string))
+		{
+			trigger_error('hash_equals(): Expected user_string to be a string, '.strtolower(gettype($user_string)).' given', E_USER_WARNING);
+			return FALSE;
+		}
+		elseif (($length = strlen($known_string)) !== strlen($user_string))
+		{
+			return FALSE;
+		}
+
+		$diff = 0;
+		for ($i = 0; $i < $length; $i++)
+		{
+			$diff |= ord($known_string[$i]) ^ ord($user_string[$i]);
+		}
+
+		return ($diff === 0);
+	}
+}
+
+// ------------------------------------------------------------------------
+
 if (is_php('5.5'))
 {
 	return;
diff --git a/tests/codeigniter/core/compat/hash_test.php b/tests/codeigniter/core/compat/hash_test.php
index 45a5b39..d8cd0bb 100644
--- a/tests/codeigniter/core/compat/hash_test.php
+++ b/tests/codeigniter/core/compat/hash_test.php
@@ -4,12 +4,33 @@
 
 	public function test_bootstrap()
 	{
-		if (is_php('5.5'))
+		if (is_php('5.6'))
 		{
-			return $this->markTestSkipped('ext/hash is available on PHP 5.5');
+			return $this->markTestSkipped('ext/hash is available on PHP 5.6');
 		}
 
-		$this->assertTrue(function_exists('hash_pbkdf2'));
+		$this->assertTrue(function_exists('hash_equals'));
+		is_php('5.5') OR $this->assertTrue(function_exists('hash_pbkdf2'));
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * hash_equals() test
+	 *
+	 * Borrowed from PHP's own tests
+	 *
+	 * @depends	test_bootstrap
+	 */
+	public function test_hash_equals()
+	{
+		$this->assertTrue(hash_equals('same', 'same'));
+		$this->assertFalse(hash_equals('not1same', 'not2same'));
+		$this->assertFalse(hash_equals('short', 'longer'));
+		$this->assertFalse(hash_equals('longer', 'short'));
+		$this->assertFalse(hash_equals('', 'notempty'));
+		$this->assertFalse(hash_equals('notempty', ''));
+		$this->assertTrue(hash_equals('', ''));
 	}
 
 	// ------------------------------------------------------------------------
@@ -23,6 +44,11 @@
 	 */
 	public function test_hash_pbkdf2()
 	{
+		if (is_php('5.5'))
+		{
+			return $this->markTestSkipped('hash_pbkdf2() is available on PHP 5.5');
+		}
+
 		$this->assertEquals('0c60c80f961f0e71f3a9', hash_pbkdf2('sha1', 'password', 'salt', 1, 20));
 		$this->assertEquals(
 			"\x0c\x60\xc8\x0f\x96\x1f\x0e\x71\xf3\xa9\xb5\x24\xaf\x60\x12\x06\x2f\xe0\x37\xa6",
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index edf0a49..db8f7d2 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -525,7 +525,7 @@
    -  Added `compatibility layers <general/compatibility_functions>` for:
 
       - `Multibyte String <http://php.net/mbstring>`_ (limited support).
-      - `Hash <http://php.net/hash>`_ (just ``hash_pbkdf2()``).
+      - `Hash <http://php.net/hash>`_ (``hash_equals()``, ``hash_pbkdf2()``).
       - `Password Hashing <http://php.net/password>`_.
       - `Array Functions <http://php.net/book.array>`_ (``array_column()``, ``array_replace()``, ``array_replace_recursive()``).
 
diff --git a/user_guide_src/source/general/compatibility_functions.rst b/user_guide_src/source/general/compatibility_functions.rst
index 398403e..e685073 100644
--- a/user_guide_src/source/general/compatibility_functions.rst
+++ b/user_guide_src/source/general/compatibility_functions.rst
@@ -97,8 +97,9 @@
 Hash (Message Digest)
 *********************
 
-This compatibility layer contains only a single function at
-this time - ``hash_pbkdf2()``, which otherwise requires PHP 5.5.
+This compatibility layer contains backports for the ``hash_equals()``
+and ``hash_pbkdf2()`` functions, which otherwise require PHP 5.6 and/or
+PHP 5.5 respectively.
 
 Dependancies
 ============
@@ -108,6 +109,16 @@
 Function reference
 ==================
 
+.. function:: hash_equals($known_string, $user_string)
+
+	:param	string	$known_string: Known string
+	:param	string	$user_string: User-supplied string
+	:returns:	TRUE if the strings match, FALSE otherwise
+	:rtype:	string
+
+	For more information, please refer to the `PHP manual for
+	hash_equals() <http://php.net/hash_equals>`_.
+
 .. function:: hash_pbkdf2($algo, $password, $salt, $iterations[, $length = 0[, $raw_output = FALSE]])
 
 	:param	string	$algo: Hashing algorithm