SecurityManager.php 9.15 KB
Newer Older
Qiang Xue committed
1 2
<?php
/**
Qiang Xue committed
3
 * SecurityManager class file.
Qiang Xue committed
4 5
 *
 * @link http://www.yiiframework.com/
Qiang Xue committed
6
 * @copyright Copyright &copy; 2008 Yii Software LLC
Qiang Xue committed
7 8 9
 * @license http://www.yiiframework.com/license/
 */

Qiang Xue committed
10 11
namespace yii\base;

Qiang Xue committed
12
/**
Qiang Xue committed
13
 * SecurityManager provides private keys, hashing and encryption functions.
Qiang Xue committed
14 15
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
Qiang Xue committed
16
 * @since 2.0
Qiang Xue committed
17
 */
Qiang Xue committed
18
class SecurityManager extends ApplicationComponent
Qiang Xue committed
19
{
Qiang Xue committed
20 21
	const STATE_VALIDATION_KEY = 'Yii.SecurityManager.validationkey';
	const STATE_ENCRYPTION_KEY = 'Yii.SecurityManager.encryptionkey';
Qiang Xue committed
22 23 24 25 26 27 28 29

	/**
	 * @var string the name of the hashing algorithm to be used by {@link computeHMAC}.
	 * See {@link http://php.net/manual/en/function.hash-algos.php hash-algos} for the list of possible
	 * hash algorithms. Note that if you are using PHP 5.1.1 or below, you can only use 'sha1' or 'md5'.
	 *
	 * Defaults to 'sha1', meaning using SHA1 hash algorithm.
	 */
Qiang Xue committed
30
	public $hashAlgorithm = 'sha1';
Qiang Xue committed
31 32 33 34 35 36 37 38 39
	/**
	 * @var mixed the name of the crypt algorithm to be used by {@link encrypt} and {@link decrypt}.
	 * This will be passed as the first parameter to {@link http://php.net/manual/en/function.mcrypt-module-open.php mcrypt_module_open}.
	 *
	 * This property can also be configured as an array. In this case, the array elements will be passed in order
	 * as parameters to mcrypt_module_open. For example, <code>array('rijndael-256', '', 'ofb', '')</code>.
	 *
	 * Defaults to 'des', meaning using DES crypt algorithm.
	 */
Qiang Xue committed
40
	public $cryptAlgorithm = 'des';
Qiang Xue committed
41 42 43 44 45 46 47 48 49

	private $_validationKey;
	private $_encryptionKey;

	/**
	 * @return string a randomly generated private key
	 */
	protected function generateRandomKey()
	{
Qiang Xue committed
50
		return sprintf('%08x%08x%08x%08x', mt_rand(), mt_rand(), mt_rand(), mt_rand());
Qiang Xue committed
51 52 53 54 55 56 57 58
	}

	/**
	 * @return string the private key used to generate HMAC.
	 * If the key is not explicitly set, a random one is generated and returned.
	 */
	public function getValidationKey()
	{
Qiang Xue committed
59
		if ($this->_validationKey !== null) {
Qiang Xue committed
60
			return $this->_validationKey;
Qiang Xue committed
61
		} else {
Qiang Xue committed
62
			if (($key = \Yii::$application->getGlobalState(self::STATE_VALIDATION_KEY)) !== null) {
Qiang Xue committed
63
				$this->setValidationKey($key);
Qiang Xue committed
64
			} else {
Qiang Xue committed
65
				$key = $this->generateRandomKey();
Qiang Xue committed
66
				$this->setValidationKey($key);
Qiang Xue committed
67
				\Yii::$application->setGlobalState(self::STATE_VALIDATION_KEY, $key);
Qiang Xue committed
68 69 70 71 72 73 74 75 76 77 78
			}
			return $this->_validationKey;
		}
	}

	/**
	 * @param string $value the key used to generate HMAC
	 * @throws CException if the key is empty
	 */
	public function setValidationKey($value)
	{
Qiang Xue committed
79 80
		if (!empty($value)) {
			$this->_validationKey = $value;
Qiang Xue committed
81
		} else {
Qiang Xue committed
82 83
			throw new CException(Yii::t('yii', 'SecurityManager.validationKey cannot be empty.'));
		}
Qiang Xue committed
84 85 86 87 88 89 90 91
	}

	/**
	 * @return string the private key used to encrypt/decrypt data.
	 * If the key is not explicitly set, a random one is generated and returned.
	 */
	public function getEncryptionKey()
	{
Qiang Xue committed
92
		if ($this->_encryptionKey !== null) {
Qiang Xue committed
93
			return $this->_encryptionKey;
Qiang Xue committed
94
		} else {
Qiang Xue committed
95
			if (($key = \Yii::$application->getGlobalState(self::STATE_ENCRYPTION_KEY)) !== null) {
Qiang Xue committed
96
				$this->setEncryptionKey($key);
Qiang Xue committed
97
			} else {
Qiang Xue committed
98
				$key = $this->generateRandomKey();
Qiang Xue committed
99
				$this->setEncryptionKey($key);
Qiang Xue committed
100
				\Yii::$application->setGlobalState(self::STATE_ENCRYPTION_KEY, $key);
Qiang Xue committed
101 102 103 104 105 106 107 108 109 110 111
			}
			return $this->_encryptionKey;
		}
	}

	/**
	 * @param string $value the key used to encrypt/decrypt data.
	 * @throws CException if the key is empty
	 */
	public function setEncryptionKey($value)
	{
Qiang Xue committed
112 113
		if (!empty($value)) {
			$this->_encryptionKey = $value;
Qiang Xue committed
114
		} else {
Qiang Xue committed
115 116
			throw new CException(Yii::t('yii', 'SecurityManager.encryptionKey cannot be empty.'));
		}
Qiang Xue committed
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
	}

	/**
	 * This method has been deprecated since version 1.1.3.
	 * Please use {@link hashAlgorithm} instead.
	 * @return string
	 */
	public function getValidation()
	{
		return $this->hashAlgorithm;
	}

	/**
	 * This method has been deprecated since version 1.1.3.
	 * Please use {@link hashAlgorithm} instead.
	 * @param string $value -
	 */
	public function setValidation($value)
	{
Qiang Xue committed
136
		$this->hashAlgorithm = $value;
Qiang Xue committed
137 138 139 140 141 142 143 144 145
	}

	/**
	 * Encrypts data.
	 * @param string $data data to be encrypted.
	 * @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}.
	 * @return string the encrypted data
	 * @throws CException if PHP Mcrypt extension is not loaded
	 */
Qiang Xue committed
146
	public function encrypt($data, $key = null)
Qiang Xue committed
147
	{
Qiang Xue committed
148 149
		$module = $this->openCryptModule();
		$key = $this->substr($key === null ? md5($this->getEncryptionKey()) : $key, 0, mcrypt_enc_get_key_size($module));
Qiang Xue committed
150
		srand();
Qiang Xue committed
151 152 153
		$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND);
		mcrypt_generic_init($module, $key, $iv);
		$encrypted = $iv . mcrypt_generic($module, $data);
Qiang Xue committed
154 155 156 157 158 159 160 161 162 163 164 165
		mcrypt_generic_deinit($module);
		mcrypt_module_close($module);
		return $encrypted;
	}

	/**
	 * Decrypts data
	 * @param string $data data to be decrypted.
	 * @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}.
	 * @return string the decrypted data
	 * @throws CException if PHP Mcrypt extension is not loaded
	 */
Qiang Xue committed
166
	public function decrypt($data, $key = null)
Qiang Xue committed
167
	{
Qiang Xue committed
168 169 170 171 172 173
		$module = $this->openCryptModule();
		$key = $this->substr($key === null ? md5($this->getEncryptionKey()) : $key, 0, mcrypt_enc_get_key_size($module));
		$ivSize = mcrypt_enc_get_iv_size($module);
		$iv = $this->substr($data, 0, $ivSize);
		mcrypt_generic_init($module, $key, $iv);
		$decrypted = mdecrypt_generic($module, $this->substr($data, $ivSize, $this->strlen($data)));
Qiang Xue committed
174 175
		mcrypt_generic_deinit($module);
		mcrypt_module_close($module);
Qiang Xue committed
176
		return rtrim($decrypted, "\0");
Qiang Xue committed
177 178 179 180 181 182 183 184 185
	}

	/**
	 * Opens the mcrypt module with the configuration specified in {@link cryptAlgorithm}.
	 * @return resource the mycrypt module handle.
	 * @since 1.1.3
	 */
	protected function openCryptModule()
	{
Qiang Xue committed
186 187 188
		if (extension_loaded('mcrypt')) {
			if (is_array($this->cryptAlgorithm)) {
				$module = @call_user_func_array('mcrypt_module_open', $this->cryptAlgorithm);
Qiang Xue committed
189
			} else {
Qiang Xue committed
190 191
				$module = @mcrypt_module_open($this->cryptAlgorithm, '', MCRYPT_MODE_CBC, '');
			}
Qiang Xue committed
192

Qiang Xue committed
193 194 195
			if ($module === false) {
				throw new CException(Yii::t('yii', 'Failed to initialize the mcrypt module.'));
			}
Qiang Xue committed
196 197

			return $module;
Qiang Xue committed
198
		} else {
Qiang Xue committed
199 200
			throw new CException(Yii::t('yii', 'SecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.'));
		}
Qiang Xue committed
201 202 203 204 205 206 207 208
	}

	/**
	 * Prefixes data with an HMAC.
	 * @param string $data data to be hashed.
	 * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
	 * @return string data prefixed with HMAC
	 */
Qiang Xue committed
209
	public function hashData($data, $key = null)
Qiang Xue committed
210
	{
Qiang Xue committed
211
		return $this->computeHMAC($data, $key) . $data;
Qiang Xue committed
212 213 214 215 216 217 218 219 220 221
	}

	/**
	 * Validates if data is tampered.
	 * @param string $data data to be validated. The data must be previously
	 * generated using {@link hashData()}.
	 * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
	 * @return string the real data with HMAC stripped off. False if the data
	 * is tampered.
	 */
Qiang Xue committed
222
	public function validateData($data, $key = null)
Qiang Xue committed
223
	{
Qiang Xue committed
224 225 226 227 228
		$len = $this->strlen($this->computeHMAC('test'));
		if ($this->strlen($data) >= $len) {
			$hmac = $this->substr($data, 0, $len);
			$data2 = $this->substr($data, $len, $this->strlen($data));
			return $hmac === $this->computeHMAC($data2, $key) ? $data2 : false;
Qiang Xue committed
229
		} else {
Qiang Xue committed
230
			return false;
Qiang Xue committed
231
		}
Qiang Xue committed
232 233 234 235 236 237 238 239
	}

	/**
	 * Computes the HMAC for the data with {@link getValidationKey ValidationKey}.
	 * @param string $data data to be generated HMAC
	 * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
	 * @return string the HMAC for the data
	 */
Qiang Xue committed
240
	protected function computeHMAC($data, $key = null)
Qiang Xue committed
241
	{
Qiang Xue committed
242 243 244
		if ($key === null) {
			$key = $this->getValidationKey();
		}
Qiang Xue committed
245

Qiang Xue committed
246
		if (function_exists('hash_hmac')) {
Qiang Xue committed
247
			return hash_hmac($this->hashAlgorithm, $data, $key);
Qiang Xue committed
248
		}
Qiang Xue committed
249

Qiang Xue committed
250 251 252
		if (!strcasecmp($this->hashAlgorithm, 'sha1')) {
			$pack = 'H40';
			$func = 'sha1';
Qiang Xue committed
253
		} else {
Qiang Xue committed
254 255 256 257 258
			$pack = 'H32';
			$func = 'md5';
		}
		if ($this->strlen($key) > 64) {
			$key = pack($pack, $func($key));
Qiang Xue committed
259
		}
Qiang Xue committed
260 261
		if ($this->strlen($key) < 64) {
			$key = str_pad($key, 64, chr(0));
Qiang Xue committed
262
		}
Qiang Xue committed
263
		$key = $this->substr($key, 0, 64);
Qiang Xue committed
264 265 266 267 268 269 270 271 272 273 274
		return $func((str_repeat(chr(0x5C), 64) ^ $key) . pack($pack, $func((str_repeat(chr(0x36), 64) ^ $key) . $data)));
	}

	/**
	 * Returns the length of the given string.
	 * If available uses the multibyte string function mb_strlen.
	 * @param string $string the string being measured for length
	 * @return int the length of the string
	 */
	private function strlen($string)
	{
Qiang Xue committed
275
		return function_exists('mb_strlen') ? mb_strlen($string, '8bit') : strlen($string);
Qiang Xue committed
276 277 278 279 280 281 282 283 284 285
	}

	/**
	 * Returns the portion of string specified by the start and length parameters.
	 * If available uses the multibyte string function mb_substr
	 * @param string $string the input string. Must be one character or longer.
	 * @param int $start the starting position
	 * @param int $length the desired portion length
	 * @return string the extracted part of string, or FALSE on failure or an empty string.
	 */
Qiang Xue committed
286
	private function substr($string, $start, $length)
Qiang Xue committed
287
	{
Qiang Xue committed
288
		return function_exists('mb_substr') ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length);
Qiang Xue committed
289 290
	}
}