diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index ce067f4..da83a19 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -129,6 +129,7 @@ Yii Framework 2 Change Log - Added unit test for saving and loading data. - Enh #4080: Added proper handling and support of the symlinked directories in `FileHelper`, added $options parameter in `FileHelper::removeDirectory()` (resurtm) - Enh #4086: changedAttributes of afterSave Event now contain old values (dizews) +- Enh #4114: Added Security::generateRandomHexKey(), used it for various tokens and default key generation (samdark) - Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue) - Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue) - Enh: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue) diff --git a/framework/base/Security.php b/framework/base/Security.php index 07ad837..0df9c94 100644 --- a/framework/base/Security.php +++ b/framework/base/Security.php @@ -298,30 +298,46 @@ class Security extends Component } if (!isset($this->_keys[$name]) || $regenerate) { - $this->_keys[$name] = utf8_encode($this->generateRandomKey($length)); + $this->_keys[$name] = $this->generateRandomKey($length); file_put_contents($keyFile, json_encode($this->_keys)); } - return utf8_decode($this->_keys[$name]); + return $this->_keys[$name]; } /** - * Generates a random key. - * Note the generated key is a binary string with the specified number of bytes in it. - * @param integer $length the length of the key that should be generated + * Generates specified number of random bytes. + * Note that output may not be ASCII. + * @see generateRandomKey() if you need a string. + * + * @param integer $length the number of bytes to generate * @throws Exception on failure. - * @return string the generated random key + * @return string the generated random bytes */ - public function generateRandomKey($length = 32) + public function generateRandomBytes($length = 32) { if (!extension_loaded('mcrypt')) { throw new InvalidConfigException('The mcrypt PHP extension is not installed.'); } - $key = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); - if ($key === false) { - throw new Exception('Unable to generate random key.'); + $bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + if ($bytes === false) { + throw new Exception('Unable to generate random bytes.'); } - return $key; + return $bytes; + } + + /** + * Generates a random string of specified length. + * The string generated matches [A-Za-z0-9_.-]+ + * + * @param integer $length the length of the key in characters + * @throws Exception Exception on failure. + * @return string the generated random key + */ + public function generateRandomKey($length = 32) + { + $bytes = $this->generateRandomBytes($length); + return strtr(StringHelper::byteSubstr(base64_encode($bytes), 0, $length), '+/=', '_-.'); } /** @@ -456,7 +472,7 @@ class Security extends Component } // Get 20 * 8bits of random entropy - $rand = $this->generateRandomKey(20); + $rand = $this->generateRandomBytes(20); // Add the microtime for a little more entropy. $rand .= microtime(true); diff --git a/tests/unit/framework/base/SecurityTest.php b/tests/unit/framework/base/SecurityTest.php index 6fac532..ff23083 100644 --- a/tests/unit/framework/base/SecurityTest.php +++ b/tests/unit/framework/base/SecurityTest.php @@ -132,10 +132,18 @@ class SecurityTest extends TestCase $this->assertEquals($data, $decryptedData); } + public function testGenerateRandomBytes() + { + $length = 21; + $key = $this->security->generateRandomBytes($length); + $this->assertEquals($length, strlen($key)); + } + public function testGenerateRandomKey() { - $keyLength = 20; - $key = $this->security->generateRandomKey($keyLength); - $this->assertEquals($keyLength, strlen($key)); + $length = 21; + $key = $this->security->generateRandomKey($length); + $this->assertEquals($length, strlen($key)); + $this->assertEquals(1, preg_match('/[A-Za-z0-9_.-]+/', $key)); } }