1: <?php
2: /*****************************************************************************************
3: * X2Engine Open Source Edition is a customer relationship management program developed by
4: * X2Engine, Inc. Copyright (C) 2011-2016 X2Engine Inc.
5: *
6: * This program is free software; you can redistribute it and/or modify it under
7: * the terms of the GNU Affero General Public License version 3 as published by the
8: * Free Software Foundation with the addition of the following permission added
9: * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
10: * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
11: * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12: *
13: * This program is distributed in the hope that it will be useful, but WITHOUT
14: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15: * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
16: * details.
17: *
18: * You should have received a copy of the GNU Affero General Public License along with
19: * this program; if not, see http://www.gnu.org/licenses or write to the Free
20: * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21: * 02110-1301 USA.
22: *
23: * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
24: * California 95067, USA. or at email address contact@x2engine.com.
25: *
26: * The interactive user interfaces in modified source and object code versions
27: * of this program must display Appropriate Legal Notices, as required under
28: * Section 5 of the GNU Affero General Public License version 3.
29: *
30: * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
31: * these Appropriate Legal Notices must retain the display of the "Powered by
32: * X2Engine" logo. If the display of the logo is not reasonably feasible for
33: * technical reasons, the Appropriate Legal Notices must display the words
34: * "Powered by X2Engine".
35: *****************************************************************************************/
36:
37: /**
38: * Standalone encryption utilities class that can retrieve necessary encryption
39: * key/encoding from files.
40: *
41: * @package application.components.util
42: * @author Demitri Morgan <demitri@x2engine.com>
43: */
44: class EncryptUtil {
45:
46: public static $generatedValues = array('IV','key');
47:
48: private $_IV;
49: /**
50: * Encryption key
51: * @var mixed
52: */
53: private $_key;
54:
55: /**
56: * Whether all the necessary dependencies are installed to use encryption.
57: * @var bool
58: */
59: public $canEncrypt;
60:
61: /**
62: * File for storing IV length (for encoding purposes)
63: * @var type
64: */
65: public $IVFile;
66:
67: /**
68: * A file for storing an encryption key
69: * @var string
70: */
71: public $keyFile;
72:
73: /**
74: * Checks dependencies.
75: * @param type $throw Throw an exception if this is set to true and dependencies are missing.
76: * @throws Exception
77: */
78: public static function dependencyCheck($throw) {
79: $hasDeps = extension_loaded('openssl') && extension_loaded('mcrypt');
80: if(!$hasDeps && $throw)
81: throw new Exception('The "openssl" and "mcrypt" extensions are not loaded. The EncryptUtil class cannot function properly.');
82: return $hasDeps;
83: }
84:
85: /**
86: * Generates a new encryption key
87: *
88: * @param integer $length
89: * @return string|bool
90: */
91: public static function genKey($length = 32){
92: $key = openssl_random_pseudo_bytes($length, $strong);
93: return ($strong ? $key : false);
94: }
95:
96: public static function genIV() {
97: return mcrypt_create_iv(
98: mcrypt_get_iv_size(
99: MCRYPT_RIJNDAEL_256,
100: MCRYPT_MODE_ECB
101: ),
102: MCRYPT_RAND
103: );
104: }
105:
106: public function __construct($keyFile=null,$IVFile=null,$throw=true) {
107: $this->canEncrypt = self::dependencyCheck($throw);
108: foreach(array('keyFile','IVFile') as $arg) {
109: $this->$arg = ${$arg};
110: }
111: }
112:
113: /**
114: * Magic getter that obtains a value for an attribute from a file, or by
115: * generating new values.
116: *
117: * The assumption is made: if no storage files are specified, the instance
118: * creates new keys for a single usage without complaining, and does not
119: * store them. Otherwise, if files are specified but do not exist, a new
120: * encryption key is generated (to be stored when {@link saveNew()} is called).
121: *
122: * @return string
123: * @throws Exception
124: */
125: public function __get($name){
126: if(in_array($name,self::$generatedValues)) {
127: $pp = "_$name"; // Private storage property
128: $sf = $name.'File'; // File for storing the property
129: $gf = 'gen'.ucfirst($name); // Function for generating the property
130: if(!isset($this->$pp)){
131: $set = false;
132: if(isset($this->$sf)){
133: $file = realpath($this->$sf);
134: if($file){
135: $this->$pp = file_get_contents($file);
136: $set = true;
137: }
138: }
139: // Must use "$set" because the file may in some cases be empty.
140: if(!(isset($this->$pp)||$set))
141: $this->$pp = call_user_func("self::$gf");
142: }
143: return $this->$pp;
144: } else
145: return $this->$name;
146: }
147:
148: public function __set($name, $value){
149: if(in_array($name,self::$generatedValues)) {
150: $pp = "_$name";
151: return $this->$pp = $value;
152: } else
153: return $this->$name = $value;
154: }
155:
156: /**
157: * Encrypts data.
158: */
159: public function encrypt($data){
160: if($this->key)
161: return base64_encode(rtrim(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $this->key, $data, MCRYPT_MODE_ECB, $this->IV),"\0"));
162: else
163: return $data;
164: }
165:
166: /**
167: * Decrypts data.
168: */
169: public function decrypt($data){
170: if($this->key)
171: return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256,$this->key, base64_decode($data), MCRYPT_MODE_ECB, $this->IV),"\0");
172: else
173: return $data;
174: }
175:
176: /**
177: * Generates and saves an encryption key/IV length in files specified by
178: * {@link _keyFile} and {@link _IVFile}. Throws an exception if the key
179: * couldn't be made securely.
180: *
181: * @param type $safe
182: * @return type
183: * @throws Exception
184: */
185: public function saveNew($safe=true) {
186: foreach(array('key', 'IV') as $attr){
187: $sf = $attr.'File';
188: if(!isset($this->$sf))
189: throw new Exception("Cannot save $attr; path to $sf not set.");
190: $dir = dirname($this->$sf);
191: if(!realpath($dir))
192: throw new Exception(ucfirst($attr)." file's containing directory at $dir not found.");
193: file_put_contents($this->$sf, $this->$attr);
194: }
195: if($safe && !$this->key)
196: throw new Exception('Strength of the encryption key could not be verified.');
197: return $this->key;
198: }
199:
200: /**
201: * Securely generates a random string of 64 characters (hex digits).
202: *
203: * @param mixed $method Forcefully specify a method of generation
204: * @param mixed $hash Forcefully specify a hashing method
205: */
206: public static function secureUniqueIdHash64($method=null,$hash=null) {
207:
208: // First determine if a hashing algorithm is available:
209: if(function_exists('openssl_random_pseudo_bytes') && ($method === null || $method === 1)) {
210: // Use secure random number generation:
211: $rand = openssl_random_pseudo_bytes(1024);
212: } else if(function_exists('mt_rand') && ($method === null || $method === 2)) {
213: // Secure random unavailable. Use Mersenne Twister instead:
214: $charset = array_merge(
215: range('a', 'z'), range('A', 'Z'), array_map(function($i){
216: return (string) $i;
217: }, range(0, 9)), array('!', '@', '#', '$', '%', '^', '&', '*', '(',
218: ')', '-', '_', '\\', '|', "'", '"', ';', ':', ',', '<', '.', '>', '[',
219: '{', ']', '}', '=', '+', ')')
220: );
221: $rand = implode('',array_map(function($i)use($charset){return $charset[mt_rand(0,90)];},range(1,128)));
222: } else if($method === null || $method === 3) {
223: // The extremely insecure default, if no sensible generation methods
224: // are available:
225: $rand = (string) time();
226: }
227: $algos = hash_algos();
228:
229: if(in_array('sha256',$algos) && ($hash === null || $hash === 1)) {
230: return hash('sha256',uniqid().$rand);
231: } else if($hash === null || $hash === 2) {
232: // Clunky default method
233: return str_repeat(md5(uniqid().$rand),2);
234: }
235: }
236: }
237:
238: ?>
239: