1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
18:
19: App::uses('CakeText', 'Utility');
20:
21: 22: 23: 24: 25:
26: class Security {
27:
28: 29: 30: 31: 32:
33: public static $hashType = null;
34:
35: 36: 37: 38: 39:
40: public static $hashCost = '10';
41:
42: 43: 44: 45: 46: 47:
48: public static function inactiveMins() {
49: switch (Configure::read('Security.level')) {
50: case 'high':
51: return 10;
52: case 'medium':
53: return 100;
54: case 'low':
55: default:
56: return 300;
57: }
58: }
59:
60: 61: 62: 63: 64: 65:
66: public static function generateAuthKey() {
67: return Security::hash(CakeText::uuid());
68: }
69:
70: 71: 72: 73: 74: 75: 76:
77: public static function validateAuthKey($authKey) {
78: return true;
79: }
80:
81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107:
108: public static function hash($string, $type = null, $salt = false) {
109: if (empty($type)) {
110: $type = static::$hashType;
111: }
112: $type = strtolower($type);
113:
114: if ($type === 'blowfish') {
115: return static::_crypt($string, $salt);
116: }
117: if ($salt) {
118: if (!is_string($salt)) {
119: $salt = Configure::read('Security.salt');
120: }
121: $string = $salt . $string;
122: }
123:
124: if (!$type || $type === 'sha1') {
125: if (function_exists('sha1')) {
126: return sha1($string);
127: }
128: $type = 'sha256';
129: }
130:
131: if ($type === 'sha256' && function_exists('mhash')) {
132: return bin2hex(mhash(MHASH_SHA256, $string));
133: }
134:
135: if (function_exists('hash')) {
136: return hash($type, $string);
137: }
138: return md5($string);
139: }
140:
141: 142: 143: 144: 145: 146: 147: 148:
149: public static function setHash($hash) {
150: static::$hashType = $hash;
151: }
152:
153: 154: 155: 156: 157: 158:
159: public static function setCost($cost) {
160: if ($cost < 4 || $cost > 31) {
161: trigger_error(__d(
162: 'cake_dev',
163: 'Invalid value, cost must be between %s and %s',
164: array(4, 31)
165: ), E_USER_WARNING);
166: return null;
167: }
168: static::$hashCost = $cost;
169: }
170:
171: 172: 173: 174: 175: 176: 177: 178: 179:
180: public static function randomBytes($length) {
181: if (function_exists('random_bytes')) {
182: return random_bytes($length);
183: }
184: if (function_exists('openssl_random_pseudo_bytes')) {
185: return openssl_random_pseudo_bytes($length);
186: }
187: if (function_exists('mcrypt_create_iv')) {
188: return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
189: }
190: trigger_error(
191: 'You do not have a safe source of random data available. ' .
192: 'Install either the openssl extension, the mcrypt extension, or paragonie/random_compat. ' .
193: 'Falling back to an insecure random source.',
194: E_USER_WARNING
195: );
196: $bytes = '';
197: $byteLength = 0;
198: while ($byteLength < $length) {
199: $bytes .= static::hash(CakeText::uuid() . uniqid(mt_rand(), true), 'sha512', true);
200: $byteLength = strlen($bytes);
201: }
202: return substr($bytes, 0, $length);
203: }
204:
205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219:
220: public static function cipher($text, $key) {
221: if (empty($key)) {
222: trigger_error(__d('cake_dev', 'You cannot use an empty key for %s', 'Security::cipher()'), E_USER_WARNING);
223: return '';
224: }
225:
226: srand((int)(float)Configure::read('Security.cipherSeed'));
227: $out = '';
228: $keyLength = strlen($key);
229: for ($i = 0, $textLength = strlen($text); $i < $textLength; $i++) {
230: $j = ord(substr($key, $i % $keyLength, 1));
231: while ($j--) {
232: rand(0, 255);
233: }
234: $mask = rand(0, 255);
235: $out .= chr(ord(substr($text, $i, 1)) ^ $mask);
236: }
237: srand();
238: return $out;
239: }
240:
241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252:
253: public static function rijndael($text, $key, $operation) {
254: if (empty($key)) {
255: trigger_error(__d('cake_dev', 'You cannot use an empty key for %s', 'Security::rijndael()'), E_USER_WARNING);
256: return '';
257: }
258: if (empty($operation) || !in_array($operation, array('encrypt', 'decrypt'))) {
259: trigger_error(__d('cake_dev', 'You must specify the operation for Security::rijndael(), either encrypt or decrypt'), E_USER_WARNING);
260: return '';
261: }
262: if (strlen($key) < 32) {
263: trigger_error(__d('cake_dev', 'You must use a key larger than 32 bytes for Security::rijndael()'), E_USER_WARNING);
264: return '';
265: }
266: $algorithm = MCRYPT_RIJNDAEL_256;
267: $mode = MCRYPT_MODE_CBC;
268: $ivSize = mcrypt_get_iv_size($algorithm, $mode);
269:
270: $cryptKey = substr($key, 0, 32);
271:
272: if ($operation === 'encrypt') {
273: $iv = mcrypt_create_iv($ivSize, MCRYPT_RAND);
274: return $iv . '$$' . mcrypt_encrypt($algorithm, $cryptKey, $text, $mode, $iv);
275: }
276:
277: if (substr($text, $ivSize, 2) !== '$$') {
278: $iv = substr($key, strlen($key) - 32, 32);
279: return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");
280: }
281: $iv = substr($text, 0, $ivSize);
282: $text = substr($text, $ivSize + 2);
283: return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");
284: }
285:
286: 287: 288: 289: 290: 291: 292: 293:
294: protected static function _salt($length = 22) {
295: $salt = str_replace(
296: array('+', '='),
297: '.',
298: base64_encode(sha1(uniqid(Configure::read('Security.salt'), true), true))
299: );
300: return substr($salt, 0, $length);
301: }
302:
303: 304: 305: 306: 307: 308: 309:
310: protected static function _crypt($password, $salt = false) {
311: if ($salt === false || $salt === null || $salt === '') {
312: $salt = static::_salt(22);
313: $salt = vsprintf('$2a$%02d$%s', array(static::$hashCost, $salt));
314: }
315:
316: $invalidCipher = (
317: strpos($salt, '$2y$') !== 0 &&
318: strpos($salt, '$2x$') !== 0 &&
319: strpos($salt, '$2a$') !== 0
320: );
321: if ($salt === true || $invalidCipher || strlen($salt) < 29) {
322: trigger_error(__d(
323: 'cake_dev',
324: 'Invalid salt: %s for %s Please visit http://www.php.net/crypt and read the appropriate section for building %s salts.',
325: array($salt, 'blowfish', 'blowfish')
326: ), E_USER_WARNING);
327: return '';
328: }
329: return crypt($password, $salt);
330: }
331:
332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344:
345: public static function encrypt($plain, $key, $hmacSalt = null) {
346: static::_checkKey($key, 'encrypt()');
347:
348: if ($hmacSalt === null) {
349: $hmacSalt = Configure::read('Security.salt');
350: }
351:
352:
353: $key = substr(hash('sha256', $key . $hmacSalt), 0, 32);
354:
355: if (Configure::read('Security.useOpenSsl')) {
356: $method = 'AES-256-CBC';
357: $ivSize = openssl_cipher_iv_length($method);
358: $iv = openssl_random_pseudo_bytes($ivSize);
359: $padLength = (int)ceil((strlen($plain) ?: 1) / $ivSize) * $ivSize;
360: $ciphertext = openssl_encrypt(str_pad($plain, $padLength, "\0"), $method, $key, true, $iv);
361:
362:
363:
364: $ciphertext = $iv . substr($ciphertext, 0, -$ivSize);
365: } else {
366: $algorithm = MCRYPT_RIJNDAEL_128;
367: $mode = MCRYPT_MODE_CBC;
368: $ivSize = mcrypt_get_iv_size($algorithm, $mode);
369: $iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
370: $ciphertext = $iv . mcrypt_encrypt($algorithm, $key, $plain, $mode, $iv);
371: }
372:
373: $hmac = hash_hmac('sha256', $ciphertext, $key);
374: return $hmac . $ciphertext;
375: }
376:
377: 378: 379: 380: 381: 382: 383: 384:
385: protected static function _checkKey($key, $method) {
386: if (strlen($key) < 32) {
387: throw new CakeException(__d('cake_dev', 'Invalid key for %s, key must be at least 256 bits (32 bytes) long.', $method));
388: }
389: }
390:
391: 392: 393: 394: 395: 396: 397: 398: 399:
400: public static function decrypt($cipher, $key, $hmacSalt = null) {
401: static::_checkKey($key, 'decrypt()');
402: if (empty($cipher)) {
403: throw new CakeException(__d('cake_dev', 'The data to decrypt cannot be empty.'));
404: }
405: if ($hmacSalt === null) {
406: $hmacSalt = Configure::read('Security.salt');
407: }
408:
409:
410: $key = substr(hash('sha256', $key . $hmacSalt), 0, 32);
411:
412:
413: $macSize = 64;
414: $hmac = substr($cipher, 0, $macSize);
415: $cipher = substr($cipher, $macSize);
416:
417: $compareHmac = hash_hmac('sha256', $cipher, $key);
418: if ($hmac !== $compareHmac) {
419: return false;
420: }
421:
422: if (Configure::read('Security.useOpenSsl')) {
423: $method = 'AES-256-CBC';
424: $ivSize = openssl_cipher_iv_length($method);
425: $iv = substr($cipher, 0, $ivSize);
426: $cipher = substr($cipher, $ivSize);
427:
428: $padding = openssl_encrypt('', $method, $key, true, substr($cipher, -$ivSize));
429: $plain = openssl_decrypt($cipher . $padding, $method, $key, true, $iv);
430: } else {
431: $algorithm = MCRYPT_RIJNDAEL_128;
432: $mode = MCRYPT_MODE_CBC;
433: $ivSize = mcrypt_get_iv_size($algorithm, $mode);
434: $iv = substr($cipher, 0, $ivSize);
435: $cipher = substr($cipher, $ivSize);
436: $plain = mcrypt_decrypt($algorithm, $key, $cipher, $mode, $iv);
437: }
438:
439: return rtrim($plain, "\0");
440: }
441:
442: }
443: