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: trigger_error(
188: 'You do not have a safe source of random data available. ' .
189: 'Install either the openssl extension, or paragonie/random_compat. ' .
190: 'Falling back to an insecure random source.',
191: E_USER_WARNING
192: );
193: $bytes = '';
194: $byteLength = 0;
195: while ($byteLength < $length) {
196: $bytes .= static::hash(CakeText::uuid() . uniqid(mt_rand(), true), 'sha512', true);
197: $byteLength = strlen($bytes);
198: }
199: return substr($bytes, 0, $length);
200: }
201:
202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216:
217: public static function cipher($text, $key) {
218: if (empty($key)) {
219: trigger_error(__d('cake_dev', 'You cannot use an empty key for %s', 'Security::cipher()'), E_USER_WARNING);
220: return '';
221: }
222:
223: srand((int)Configure::read('Security.cipherSeed'));
224: $out = '';
225: $keyLength = strlen($key);
226: for ($i = 0, $textLength = strlen($text); $i < $textLength; $i++) {
227: $j = ord(substr($key, $i % $keyLength, 1));
228: while ($j--) {
229: rand(0, 255);
230: }
231: $mask = rand(0, 255);
232: $out .= chr(ord(substr($text, $i, 1)) ^ $mask);
233: }
234: srand();
235: return $out;
236: }
237:
238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249:
250: public static function rijndael($text, $key, $operation) {
251: if (empty($key)) {
252: trigger_error(__d('cake_dev', 'You cannot use an empty key for %s', 'Security::rijndael()'), E_USER_WARNING);
253: return '';
254: }
255: if (empty($operation) || !in_array($operation, array('encrypt', 'decrypt'))) {
256: trigger_error(__d('cake_dev', 'You must specify the operation for Security::rijndael(), either encrypt or decrypt'), E_USER_WARNING);
257: return '';
258: }
259: if (strlen($key) < 32) {
260: trigger_error(__d('cake_dev', 'You must use a key larger than 32 bytes for Security::rijndael()'), E_USER_WARNING);
261: return '';
262: }
263: $algorithm = MCRYPT_RIJNDAEL_256;
264: $mode = MCRYPT_MODE_CBC;
265: $ivSize = mcrypt_get_iv_size($algorithm, $mode);
266:
267: $cryptKey = substr($key, 0, 32);
268:
269: if ($operation === 'encrypt') {
270: $iv = mcrypt_create_iv($ivSize, MCRYPT_RAND);
271: return $iv . '$$' . mcrypt_encrypt($algorithm, $cryptKey, $text, $mode, $iv);
272: }
273:
274: if (substr($text, $ivSize, 2) !== '$$') {
275: $iv = substr($key, strlen($key) - 32, 32);
276: return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");
277: }
278: $iv = substr($text, 0, $ivSize);
279: $text = substr($text, $ivSize + 2);
280: return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");
281: }
282:
283: 284: 285: 286: 287: 288: 289: 290:
291: protected static function _salt($length = 22) {
292: $salt = str_replace(
293: array('+', '='),
294: '.',
295: base64_encode(sha1(uniqid(Configure::read('Security.salt'), true), true))
296: );
297: return substr($salt, 0, $length);
298: }
299:
300: 301: 302: 303: 304: 305: 306:
307: protected static function _crypt($password, $salt = false) {
308: if ($salt === false || $salt === null || $salt === '') {
309: $salt = static::_salt(22);
310: $salt = vsprintf('$2a$%02d$%s', array(static::$hashCost, $salt));
311: }
312:
313: $invalidCipher = (
314: strpos($salt, '$2y$') !== 0 &&
315: strpos($salt, '$2x$') !== 0 &&
316: strpos($salt, '$2a$') !== 0
317: );
318: if ($salt === true || $invalidCipher || strlen($salt) < 29) {
319: trigger_error(__d(
320: 'cake_dev',
321: 'Invalid salt: %s for %s Please visit http://www.php.net/crypt and read the appropriate section for building %s salts.',
322: array($salt, 'blowfish', 'blowfish')
323: ), E_USER_WARNING);
324: return '';
325: }
326: return crypt($password, $salt);
327: }
328:
329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341:
342: public static function encrypt($plain, $key, $hmacSalt = null) {
343: static::_checkKey($key, 'encrypt()');
344:
345: if ($hmacSalt === null) {
346: $hmacSalt = Configure::read('Security.salt');
347: }
348:
349:
350: $key = substr(hash('sha256', $key . $hmacSalt), 0, 32);
351:
352: $algorithm = MCRYPT_RIJNDAEL_128;
353: $mode = MCRYPT_MODE_CBC;
354:
355: $ivSize = mcrypt_get_iv_size($algorithm, $mode);
356: $iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
357: $ciphertext = $iv . mcrypt_encrypt($algorithm, $key, $plain, $mode, $iv);
358: $hmac = hash_hmac('sha256', $ciphertext, $key);
359: return $hmac . $ciphertext;
360: }
361:
362: 363: 364: 365: 366: 367: 368: 369:
370: protected static function _checkKey($key, $method) {
371: if (strlen($key) < 32) {
372: throw new CakeException(__d('cake_dev', 'Invalid key for %s, key must be at least 256 bits (32 bytes) long.', $method));
373: }
374: }
375:
376: 377: 378: 379: 380: 381: 382: 383: 384:
385: public static function decrypt($cipher, $key, $hmacSalt = null) {
386: static::_checkKey($key, 'decrypt()');
387: if (empty($cipher)) {
388: throw new CakeException(__d('cake_dev', 'The data to decrypt cannot be empty.'));
389: }
390: if ($hmacSalt === null) {
391: $hmacSalt = Configure::read('Security.salt');
392: }
393:
394:
395: $key = substr(hash('sha256', $key . $hmacSalt), 0, 32);
396:
397:
398: $macSize = 64;
399: $hmac = substr($cipher, 0, $macSize);
400: $cipher = substr($cipher, $macSize);
401:
402: $compareHmac = hash_hmac('sha256', $cipher, $key);
403: if ($hmac !== $compareHmac) {
404: return false;
405: }
406:
407: $algorithm = MCRYPT_RIJNDAEL_128;
408: $mode = MCRYPT_MODE_CBC;
409: $ivSize = mcrypt_get_iv_size($algorithm, $mode);
410:
411: $iv = substr($cipher, 0, $ivSize);
412: $cipher = substr($cipher, $ivSize);
413: $plain = mcrypt_decrypt($algorithm, $key, $cipher, $mode, $iv);
414: return rtrim($plain, "\0");
415: }
416:
417: }
418: