Add CI_Security::get_random_bytes() for CSRF & XSS token generation
diff --git a/system/core/Security.php b/system/core/Security.php
index bb06705..bc224e7 100755
--- a/system/core/Security.php
+++ b/system/core/Security.php
@@ -77,7 +77,7 @@
 	 *
 	 * @var	string
 	 */
-	protected $_xss_hash =	'';
+	protected $_xss_hash;
 
 	/**
 	 * CSRF Hash
@@ -86,7 +86,7 @@
 	 *
 	 * @var	string
 	 */
-	protected $_csrf_hash =	'';
+	protected $_csrf_hash;
 
 	/**
 	 * CSRF Expire time
@@ -227,7 +227,7 @@
 		{
 			// Nothing should last forever
 			unset($_COOKIE[$this->_csrf_cookie_name]);
-			$this->_csrf_hash = '';
+			$this->_csrf_hash = NULL;
 		}
 
 		$this->_csrf_set_hash();
@@ -538,9 +538,12 @@
 	 */
 	public function xss_hash()
 	{
-		if ($this->_xss_hash === '')
+		if ($this->_xss_hash === NULL)
 		{
-			$this->_xss_hash = md5(uniqid(mt_rand()));
+			$rand = $this->get_random_bytes(16);
+			$this->_xss_hash = ($rand === FALSE)
+				? md5(uniqid(mt_rand(), TRUE))
+				: bin2hex($rand);
 		}
 
 		return $this->_xss_hash;
@@ -549,6 +552,46 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * Get random bytes
+	 *
+	 * @param	int	$length	Output length
+	 * @return	string
+	 */
+	public function get_random_bytes($length)
+	{
+		if (empty($length) OR ! ctype_digit($length))
+		{
+			return FALSE;
+		}
+
+		// Unfortunately, none of the following PRNGs is guaranteed to exist ...
+		if (defined(MCRYPT_DEV_URANDOM) && ($output = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)) !== FALSE)
+		{
+			return $output;
+		}
+
+
+		if (is_readable('/dev/urandom') && ($fp = fopen('/dev/urandom', 'rb')) !== FALSE)
+		{
+			$output = fread($fp, $length);
+			fclose($fp);
+			if ($output !== FALSE)
+			{
+				return $output;
+			}
+		}
+
+		if (function_exists('openssl_random_pseudo_bytes'))
+		{
+			return openssl_random_pseudo_bytes($length);
+		}
+
+		return FALSE;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * HTML Entities Decode
 	 *
 	 * A replacement for html_entity_decode()
@@ -915,7 +958,7 @@
 	 */
 	protected function _csrf_set_hash()
 	{
-		if ($this->_csrf_hash === '')
+		if ($this->_csrf_hash === NULL)
 		{
 			// If the cookie exists we will use its value.
 			// We don't necessarily want to regenerate it with
@@ -927,7 +970,11 @@
 				return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name];
 			}
 
-			$this->_csrf_hash = md5(uniqid(mt_rand(), TRUE));
+			$rand = $this->get_random_bytes(16);
+			$this->_csrf_hash = ($rand === FALSE)
+				? md5(uniqid(mt_rand(), TRUE))
+				: bin2hex($rand);
+
 			$this->csrf_set_cookie();
 		}
 
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 5c233ef..64a7689 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -508,9 +508,10 @@
 
    -  :doc:`Security Library <libraries/security>` changes include:
 
-      -  Added method ``strip_image_tags()``.
       -  Added ``$config['csrf_regeneration']``, which makes CSRF token regeneration optional.
       -  Added ``$config['csrf_exclude_uris']``, allowing for exclusion of URIs from the CSRF protection (regular expressions are supported).
+      -  Added method ``strip_image_tags()``.
+      -  Added method ``get_random_bytes()`` and switched CSRF & XSS token generation to use it.
       -  Modified method ``sanitize_filename()`` to read a public ``$filename_bad_chars`` property for getting the invalid characters list.
       -  Return status code of 403 instead of a 500 if CSRF protection is enabled but a token is missing from a request.
 
diff --git a/user_guide_src/source/libraries/security.rst b/user_guide_src/source/libraries/security.rst
index c8d69d1..0c51e34 100644
--- a/user_guide_src/source/libraries/security.rst
+++ b/user_guide_src/source/libraries/security.rst
@@ -163,4 +163,19 @@
 		This method acts a lot like PHP's own native ``html_entity_decode()`` function in ENT_COMPAT mode, only
 		it tries to detect HTML entities that don't end in a semicolon because some browsers allow that.
 
-		If the ``$charset`` parameter is left empty, then your configured ``$config['charset']`` value will be used.
\ No newline at end of file
+		If the ``$charset`` parameter is left empty, then your configured ``$config['charset']`` value will be used.
+
+	.. method:: get_random_bytes($length)
+
+		:param	int	$length: Output length
+		:returns:	A binary stream of random bytes or FALSE on failure
+		:rtype:	string
+
+		A convenience method for getting proper random bytes via ``mcrypt_create_iv()``,
+		``/dev/urandom`` or ``openssl_random_pseudo_bytes()`` (in that order), if one
+		of them is available.
+
+		Used for generating CSRF and XSS tokens.
+
+		.. note:: The output is NOT guaranteed to be cryptographically secure,
+			just the best attempt at that.
\ No newline at end of file