Browse Source

Add Security::random()

This method gives a safer alternative to Text::uuid() when making CSRF
tokens. This method does fallback to an insecure source of data when we
can't find a suitable one. This is only present to preserve backwards
compatbility for folks who can upgrade their servers or take on the
recommended polyfill library.

Refs #8282
Mark Story 10 years ago
parent
commit
b79d8131fc

+ 3 - 0
composer.json

@@ -25,6 +25,9 @@
         "aura/intl": "1.1.*",
         "psr/log": "1.0"
     },
+    "suggest": {
+      "ext-openssl": "To use Security::encrypt() or have secure CSRF token generation."
+    },
     "require-dev": {
         "phpunit/phpunit": "*",
         "cakephp/cakephp-codesniffer": "dev-master"

+ 1 - 1
src/Controller/Component/CsrfComponent.php

@@ -125,7 +125,7 @@ class CsrfComponent extends Component
     protected function _setCookie(Request $request, Response $response)
     {
         $expiry = new Time($this->_config['expiry']);
-        $value = Security::hash(Text::uuid(), 'sha1', true);
+        $value = hash('sha512', Security::randomBytes(16), false);
 
         $request->params['_csrfToken'] = $value;
         $response->cookie([

+ 27 - 0
src/Utility/Security.php

@@ -16,6 +16,7 @@ namespace Cake\Utility;
 
 use Cake\Utility\Crypto\Mcrypt;
 use Cake\Utility\Crypto\OpenSsl;
+use Cake\Utility\Text;
 use InvalidArgumentException;
 
 /**
@@ -90,6 +91,32 @@ class Security
     }
 
     /**
+     * Get random bytes from a secure source.
+     *
+     * This method will fall back to an insecure source an trigger a warning
+     * if it cannot find a secure source of random data.
+     *
+     * @param int $length The number of bytes you want.
+     * @return string Random bytes in binary.
+     */
+    public static function randomBytes($length)
+    {
+        if (function_exists('random_bytes')) {
+            return random_bytes($length);
+        }
+        if (function_exists('openssl_random_pseudo_bytes')) {
+            return openssl_random_pseudo_bytes($length);
+        }
+        trigger_error(
+            'You do not have a safe source of random data available. ' .
+            'Install either the openssl extension, or paragonie/random_compat. ' .
+            'Falling back to an insecure random source.',
+            E_USER_WARNING
+        );
+        return static::hash(Text::uuid() . uniqid(mt_rand(), true), 'sha512', true);
+    }
+
+    /**
      * Get the crypto implementation based on the loaded extensions.
      *
      * You can use this method to forcibly decide between mcrypt/openssl/custom implementations.

+ 14 - 0
tests/TestCase/Utility/SecurityTest.php

@@ -292,4 +292,18 @@ class SecurityTest extends TestCase
         Security::salt('foobarbaz');
         $this->assertEquals('foobarbaz', Security::salt());
     }
+
+    /**
+     * Test the random method.
+     *
+     * @return void
+     */
+    public function testRandomBytes()
+    {
+        $value = Security::randomBytes(16);
+        $this->assertSame(16, strlen($value));
+
+        $value = Security::randomBytes(64);
+        $this->assertSame(64, strlen($value));
+    }
 }