1: <?php
2: /**
3: * Core Security
4: *
5: * PHP 5
6: *
7: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
8: * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
9: *
10: * Licensed under The MIT License
11: * For full copyright and license information, please see the LICENSE.txt
12: * Redistributions of files must retain the above copyright notice.
13: *
14: * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
15: * @link http://cakephp.org CakePHP(tm) Project
16: * @package Cake.Utility
17: * @since CakePHP(tm) v .0.10.0.1233
18: * @license http://www.opensource.org/licenses/mit-license.php MIT License
19: */
20:
21: App::uses('String', 'Utility');
22:
23: /**
24: * Security Library contains utility methods related to security
25: *
26: * @package Cake.Utility
27: */
28: class Security {
29:
30: /**
31: * Default hash method
32: *
33: * @var string
34: */
35: public static $hashType = null;
36:
37: /**
38: * Default cost
39: *
40: * @var string
41: */
42: public static $hashCost = '10';
43:
44: /**
45: * Get allowed minutes of inactivity based on security level.
46: *
47: * @deprecated Exists for backwards compatibility only, not used by the core
48: * @return integer Allowed inactivity in minutes
49: */
50: public static function inactiveMins() {
51: switch (Configure::read('Security.level')) {
52: case 'high':
53: return 10;
54: case 'medium':
55: return 100;
56: case 'low':
57: default:
58: return 300;
59: }
60: }
61:
62: /**
63: * Generate authorization hash.
64: *
65: * @return string Hash
66: */
67: public static function generateAuthKey() {
68: return Security::hash(String::uuid());
69: }
70:
71: /**
72: * Validate authorization hash.
73: *
74: * @param string $authKey Authorization hash
75: * @return boolean Success
76: */
77: public static function validateAuthKey($authKey) {
78: return true;
79: }
80:
81: /**
82: * Create a hash from string using given method or fallback on next available method.
83: *
84: * #### Using Blowfish
85: *
86: * - Creating Hashes: *Do not supply a salt*. Cake handles salt creation for
87: * you ensuring that each hashed password will have a *unique* salt.
88: * - Comparing Hashes: Simply pass the originally hashed password as the salt.
89: * The salt is prepended to the hash and php handles the parsing automagically.
90: * For convenience the BlowfishAuthenticate adapter is available for use with
91: * the AuthComponent.
92: * - Do NOT use a constant salt for blowfish!
93: *
94: * Creating a blowfish/bcrypt hash:
95: *
96: * {{{
97: * $hash = Security::hash($password, 'blowfish');
98: * }}}
99: *
100: * @param string $string String to hash
101: * @param string $type Method to use (sha1/sha256/md5/blowfish)
102: * @param mixed $salt If true, automatically prepends the application's salt
103: * value to $string (Security.salt). If you are using blowfish the salt
104: * must be false or a previously generated salt.
105: * @return string Hash
106: */
107: public static function hash($string, $type = null, $salt = false) {
108: if (empty($type)) {
109: $type = self::$hashType;
110: }
111: $type = strtolower($type);
112:
113: if ($type === 'blowfish') {
114: return self::_crypt($string, $salt);
115: }
116: if ($salt) {
117: if (!is_string($salt)) {
118: $salt = Configure::read('Security.salt');
119: }
120: $string = $salt . $string;
121: }
122:
123: if (!$type || $type === 'sha1') {
124: if (function_exists('sha1')) {
125: return sha1($string);
126: }
127: $type = 'sha256';
128: }
129:
130: if ($type === 'sha256' && function_exists('mhash')) {
131: return bin2hex(mhash(MHASH_SHA256, $string));
132: }
133:
134: if (function_exists('hash')) {
135: return hash($type, $string);
136: }
137: return md5($string);
138: }
139:
140: /**
141: * Sets the default hash method for the Security object. This affects all objects using
142: * Security::hash().
143: *
144: * @param string $hash Method to use (sha1/sha256/md5/blowfish)
145: * @return void
146: * @see Security::hash()
147: */
148: public static function setHash($hash) {
149: self::$hashType = $hash;
150: }
151:
152: /**
153: * Sets the cost for they blowfish hash method.
154: *
155: * @param integer $cost Valid values are 4-31
156: * @return void
157: */
158: public static function setCost($cost) {
159: if ($cost < 4 || $cost > 31) {
160: trigger_error(__d(
161: 'cake_dev',
162: 'Invalid value, cost must be between %s and %s',
163: array(4, 31)
164: ), E_USER_WARNING);
165: return null;
166: }
167: self::$hashCost = $cost;
168: }
169:
170: /**
171: * Runs $text through a XOR cipher.
172: *
173: * *Note* This is not a cryptographically strong method and should not be used
174: * for sensitive data. Additionally this method does *not* work in environments
175: * where suhosin is enabled.
176: *
177: * Instead you should use Security::rijndael() when you need strong
178: * encryption.
179: *
180: * @param string $text Encrypted string to decrypt, normal string to encrypt
181: * @param string $key Key to use
182: * @return string Encrypted/Decrypted string
183: * @deprecated This method will be removed in 3.x
184: */
185: public static function cipher($text, $key) {
186: if (empty($key)) {
187: trigger_error(__d('cake_dev', 'You cannot use an empty key for Security::cipher()'), E_USER_WARNING);
188: return '';
189: }
190:
191: srand(Configure::read('Security.cipherSeed'));
192: $out = '';
193: $keyLength = strlen($key);
194: for ($i = 0, $textLength = strlen($text); $i < $textLength; $i++) {
195: $j = ord(substr($key, $i % $keyLength, 1));
196: while ($j--) {
197: rand(0, 255);
198: }
199: $mask = rand(0, 255);
200: $out .= chr(ord(substr($text, $i, 1)) ^ $mask);
201: }
202: srand();
203: return $out;
204: }
205:
206: /**
207: * Encrypts/Decrypts a text using the given key using rijndael method.
208: *
209: * Prior to 2.3.1, a fixed initialization vector was used. This was not
210: * secure. This method now uses a random iv, and will silently upgrade values when
211: * they are re-encrypted.
212: *
213: * @param string $text Encrypted string to decrypt, normal string to encrypt
214: * @param string $key Key to use as the encryption key for encrypted data.
215: * @param string $operation Operation to perform, encrypt or decrypt
216: * @return string Encrypted/Decrypted string
217: */
218: public static function rijndael($text, $key, $operation) {
219: if (empty($key)) {
220: trigger_error(__d('cake_dev', 'You cannot use an empty key for Security::rijndael()'), E_USER_WARNING);
221: return '';
222: }
223: if (empty($operation) || !in_array($operation, array('encrypt', 'decrypt'))) {
224: trigger_error(__d('cake_dev', 'You must specify the operation for Security::rijndael(), either encrypt or decrypt'), E_USER_WARNING);
225: return '';
226: }
227: if (strlen($key) < 32) {
228: trigger_error(__d('cake_dev', 'You must use a key larger than 32 bytes for Security::rijndael()'), E_USER_WARNING);
229: return '';
230: }
231: $algorithm = MCRYPT_RIJNDAEL_256;
232: $mode = MCRYPT_MODE_CBC;
233: $ivSize = mcrypt_get_iv_size($algorithm, $mode);
234:
235: $cryptKey = substr($key, 0, 32);
236:
237: if ($operation === 'encrypt') {
238: $iv = mcrypt_create_iv($ivSize, MCRYPT_RAND);
239: return $iv . '$$' . mcrypt_encrypt($algorithm, $cryptKey, $text, $mode, $iv);
240: }
241: // Backwards compatible decrypt with fixed iv
242: if (substr($text, $ivSize, 2) !== '$$') {
243: $iv = substr($key, strlen($key) - 32, 32);
244: return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");
245: }
246: $iv = substr($text, 0, $ivSize);
247: $text = substr($text, $ivSize + 2);
248: return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");
249: }
250:
251: /**
252: * Generates a pseudo random salt suitable for use with php's crypt() function.
253: * The salt length should not exceed 27. The salt will be composed of
254: * [./0-9A-Za-z]{$length}.
255: *
256: * @param integer $length The length of the returned salt
257: * @return string The generated salt
258: */
259: protected static function _salt($length = 22) {
260: $salt = str_replace(
261: array('+', '='),
262: '.',
263: base64_encode(sha1(uniqid(Configure::read('Security.salt'), true), true))
264: );
265: return substr($salt, 0, $length);
266: }
267:
268: /**
269: * One way encryption using php's crypt() function. To use blowfish hashing see ``Security::hash()``
270: *
271: * @param string $password The string to be encrypted.
272: * @param mixed $salt false to generate a new salt or an existing salt.
273: * @return string The hashed string or an empty string on error.
274: */
275: protected static function _crypt($password, $salt = false) {
276: if ($salt === false) {
277: $salt = self::_salt(22);
278: $salt = vsprintf('$2a$%02d$%s', array(self::$hashCost, $salt));
279: }
280:
281: if ($salt === true || strpos($salt, '$2a$') !== 0 || strlen($salt) < 29) {
282: trigger_error(__d(
283: 'cake_dev',
284: 'Invalid salt: %s for %s Please visit http://www.php.net/crypt and read the appropriate section for building %s salts.',
285: array($salt, 'blowfish', 'blowfish')
286: ), E_USER_WARNING);
287: return '';
288: }
289: return crypt($password, $salt);
290: }
291:
292: }
293: