PluginProbe
InfiniteWP Client / trunk
InfiniteWP Client vtrunk
1.13.10 1.13.7 trunk 0.1.4 0.1.5 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.10 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.11.0 1.11.1 1.12.1 1.12.3 All 92 releases
iwp-client / backup / encrypt.php

encrypt.php in InfiniteWP Client trunk, at backup/encrypt.php

377 lines 14.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! defined('ABSPATH') )
4 die();
5
6 class IWP_MMB_Encryption {
7
8 /**
9 * This will decrypt an encrypted file
10 *
11 * @param String $fullpath This is the full filesystem path to the encrypted file location
12 * @param String $key This is the key to be used when decrypting
13 * @param Boolean $to_temporary_file Use if the resulting file is not intended to be kept
14 *
15 * @return Boolean|Array -An array with info on the decryption; or false for failure
16 */
17 public static function decrypt($fullpath, $key, $to_temporary_file = false) {
18
19 global $iwp_backup_core;
20
21 $ensure_phpseclib = $iwp_backup_core->ensure_phpseclib('Crypt_Rijndael', 'Crypt/Rijndael');
22
23 if (is_wp_error($ensure_phpseclib)) {
24 $iwp_backup_core->log("Failed to load phpseclib classes (".$ensure_phpseclib->get_error_code()."): ".$ensure_phpseclib->get_error_message());
25 $iwp_backup_core->log("Failed to load phpseclib classes (".$ensure_phpseclib->get_error_code()."): ".$ensure_phpseclib->get_error_message(), 'error');
26 return false;
27 }
28
29 // open file to read
30 if (false === ($file_handle = fopen($fullpath, 'rb'))) return false;
31
32 $decrypted_path = dirname($fullpath).'/decrypt_'.basename($fullpath).'.tmp';
33 // open new file from new path
34 if (false === ($decrypted_handle = fopen($decrypted_path, 'wb+'))) return false;
35
36 // setup encryption
37 $rijndael = new Crypt_Rijndael();
38 $rijndael->setKey($key);
39 $rijndael->disablePadding();
40 $rijndael->enableContinuousBuffer();
41
42 if (defined('IWP_DECRYPTION_ENGINE')) {
43 if ('openssl' == IWP_DECRYPTION_ENGINE) {
44 $rijndael->setPreferredEngine(CRYPT_ENGINE_OPENSSL);
45 } elseif ('mcrypt' == IWP_DECRYPTION_ENGINE) {
46 $rijndael->setPreferredEngine(CRYPT_ENGINE_MCRYPT);
47 } elseif ('internal' == IWP_DECRYPTION_ENGINE) {
48 $rijndael->setPreferredEngine(CRYPT_ENGINE_INTERNAL);
49 }
50 }
51
52 $file_size = filesize($fullpath);
53 $bytes_decrypted = 0;
54 $buffer_size = defined('IWP_CRYPT_BUFFER_SIZE') ? IWP_CRYPT_BUFFER_SIZE : 2097152;
55
56 // loop around the file
57 while ($bytes_decrypted < $file_size) {
58 // read buffer sized amount from file
59 if (false === ($file_part = fread($file_handle, $buffer_size))) return false;
60 // check to ensure padding is needed before decryption
61 $length = strlen($file_part);
62 if (0 != $length % 16) {
63 $pad = 16 - ($length % 16);
64 $file_part = str_pad($file_part, $length + $pad, chr($pad));
65 }
66
67 $decrypted_data = $rijndael->decrypt($file_part);
68
69 if (0 == $bytes_decrypted) {
70 if (IWP_MMB_Encryption::str_ends_with($fullpath, '.gz.crypt')) {
71 $first_two_chars = unpack('C*', substr($decrypted_data, 0, 2));
72 // The first two decrypted bytes of the .gz file should always be 1f 8b
73 if (31 != $first_two_chars[1] || 139 != $first_two_chars[2]) {
74 return false;
75 }
76 } elseif (IWP_MMB_Encryption::str_ends_with($fullpath, '.zip.crypt')) {
77 $first_four_chars = unpack('C*', substr($decrypted_data, 0, 2));
78 // The first four decrypted bytes of the .zip file should always be 50 4B 03 04 or 50 4B 05 06 or 50 4B 07 08
79 if (80 != $first_four_chars[1] || 75 != $first_four_chars[2] || !in_array($first_four_chars[3], array(3, 5, 7)) || !in_array($first_four_chars[3], array(4, 6, 8))) {
80 return false;
81 }
82
83 }
84 }
85
86 $is_last_block = ($bytes_decrypted + strlen($decrypted_data) >= $file_size);
87
88 $write_bytes = min($file_size - $bytes_decrypted, strlen($decrypted_data));
89 if ($is_last_block) {
90 $is_padding = false;
91 $last_byte = ord(substr($decrypted_data, -1, 1));
92 if ($last_byte < 16) {
93 $is_padding = true;
94 for ($j = 1; $j<=$last_byte; $j++) {
95 if (substr($decrypted_data, -$j, 1) != chr($last_byte)) $is_padding = false;
96 }
97 }
98 if ($is_padding) {
99 $write_bytes -= $last_byte;
100 }
101 }
102
103 if (false === fwrite($decrypted_handle, $decrypted_data, $write_bytes)) return false;
104 $bytes_decrypted += $buffer_size;
105 }
106
107 // close the main file handle
108 fclose($decrypted_handle);
109 // close original file
110 fclose($file_handle);
111
112 // remove the crypt extension from the end as this causes issues when opening
113 $fullpath_new = preg_replace('/\.crypt$/', '', $fullpath, 1);
114 // //need to replace original file with tmp file
115
116 $fullpath_basename = basename($fullpath_new);
117
118 if ($to_temporary_file) {
119 return array(
120 'fullpath' => $decrypted_path,
121 'basename' => $fullpath_basename
122 );
123 }
124
125 if (false === rename($decrypted_path, $fullpath_new)) return false;
126
127 // need to send back the new decrypted path
128 $decrypt_return = array(
129 'fullpath' => $fullpath_new,
130 'basename' => $fullpath_basename
131 );
132
133 return $decrypt_return;
134 }
135
136 /**
137 * This is the encryption process when encrypting a file
138 *
139 * @param String $fullpath This is the full path to the DB file that needs ecrypting
140 * @param String $key This is the key (salting) to be used when encrypting
141 *
142 * @return String|Boolean - Return the full path of the encrypted file, or false for an error
143 */
144 public static function encrypt($fullpath, $key) {
145
146 global $iwp_backup_core;
147
148 if (!function_exists('mcrypt_encrypt') && !extension_loaded('openssl')) {
149 $iwp_backup_core->log(sprintf(__('Your web-server does not have the %s module installed.', 'InfiniteWP'), 'PHP/mcrypt / PHP/OpenSSL').' '.__('Without it, encryption will be a lot slower.', 'InfiniteWP'), 'warning', 'nocrypt');
150 }
151
152 // include Rijndael library from phpseclib
153 $ensure_phpseclib = $iwp_backup_core->ensure_phpseclib('Crypt_Rijndael', 'Crypt/Rijndael');
154
155 if (is_wp_error($ensure_phpseclib)) {
156 $iwp_backup_core->log("Failed to load phpseclib classes (".$ensure_phpseclib->get_error_code()."): ".$ensure_phpseclib->get_error_message());
157 return false;
158 }
159
160 // open file to read
161 if (false === ($file_handle = fopen($fullpath, 'rb'))) {
162 $iwp_backup_core->log("Failed to open file for read access: $fullpath");
163 return false;
164 }
165
166 // encrypted path name. The trailing .tmp ensures that it will be cleaned up by the temporary file reaper eventually, if needs be.
167 $encrypted_path = dirname($fullpath).'/encrypt_'.basename($fullpath).'.tmp';
168
169 $data_encrypted = 0;
170 $buffer_size = defined('IWP_CRYPT_BUFFER_SIZE') ? IWP_CRYPT_BUFFER_SIZE : 2097152;
171
172 $time_last_logged = microtime(true);
173
174 $file_size = filesize($fullpath);
175
176 // Set initial value to false so we can check it later and decide what to do
177 $resumption = false;
178
179 // setup encryption
180 $rijndael = new Crypt_Rijndael();
181 $rijndael->setKey($key);
182 $rijndael->disablePadding();
183 $rijndael->enableContinuousBuffer();
184
185 // First we need to get the block length, this method returns the length in bits we need to change this back to bytes in order to use it with the file operation methods.
186 $block_length = $rijndael->getBlockLength() >> 3;
187
188 // Check if the path already exists as this could be a resumption
189 if (file_exists($encrypted_path)) {
190
191 $iwp_backup_core->log("Temporary encryption file found, will try to resume the encryption");
192
193 // The temp file exists so set resumption to true
194 $resumption = true;
195
196 // Get the file size as this is needed to help resume the encryption
197 $data_encrypted = filesize($encrypted_path);
198 // Get the true file size e.g without padding used for various resumption paths
199 $true_data_encrypted = $data_encrypted - ($data_encrypted % $buffer_size);
200
201 if ($data_encrypted >= $block_length) {
202
203 // Open existing file from the path
204 if (false === ($encrypted_handle = fopen($encrypted_path, 'rb+'))) {
205 $iwp_backup_core->log("Failed to open file for write access on resumption: $encrypted_path");
206 $resumption = false;
207 }
208
209 // First check if our buffer size needs padding if it does increase buffer size to length that doesn't need padding
210 if (0 != $buffer_size % 16) {
211 $pad = 16 - ($buffer_size % 16);
212 $true_buffer_size = $buffer_size + $pad;
213 } else {
214 $true_buffer_size = $buffer_size;
215 }
216
217 // Now check if using modulo on data encrypted and buffer size returns 0 if it doesn't then the last block was a partial write and we need to discard that and get the last useable IV by adding this value to the block length
218 $partial_data_size = $data_encrypted % $true_buffer_size;
219
220 // We need to reconstruct the IV from the previous run in order for encryption to resume
221 if (-1 === (fseek($encrypted_handle, $data_encrypted - ($block_length + $partial_data_size)))) {
222 $iwp_backup_core->log("Failed to move file pointer to correct position to get IV: $encrypted_path");
223 $resumption = false;
224 }
225
226 // Read previous block length from file
227 if (false === ($iv = fread($encrypted_handle, $block_length))) {
228 $iwp_backup_core->log("Failed to read from file to get IV: $encrypted_path");
229 $resumption = false;
230 }
231
232 $rijndael->setIV($iv);
233
234 // Now we need to set the file pointer for the original file to the correct position and take into account the padding added, this padding needs to be removed to get the true amount of bytes read from the original file
235 if (-1 === (fseek($file_handle, $true_data_encrypted))) {
236 $iwp_backup_core->log("Failed to move file pointer to correct position to resume encryption: $fullpath");
237 $resumption = false;
238 }
239
240 } else {
241 // If we enter here then the temp file exists but it is either empty or has one incomplete block we may as well start again
242 $resumption = false;
243 }
244
245 if (!$resumption) {
246 $iwp_backup_core->log("Could not resume the encryption will now try to start again");
247 // remove the existing encrypted file as it's no good to us now
248 @unlink($encrypted_path);
249 // reset the data encrypted so that the loop can be entered
250 $data_encrypted = 0;
251 // setup encryption to reset the IV
252 $rijndael = new Crypt_Rijndael();
253 $rijndael->setKey($key);
254 $rijndael->disablePadding();
255 $rijndael->enableContinuousBuffer();
256 // reset the file pointer and then we should be able to start from fresh
257 if (-1 === (fseek($file_handle, 0))) {
258 $iwp_backup_core->log("Failed to move file pointer to start position to restart encryption: $fullpath");
259 $resumption = false;
260 }
261 }
262 }
263
264 if (!$resumption) {
265 // open new file from new path
266 if (false === ($encrypted_handle = fopen($encrypted_path, 'wb+'))) {
267 $iwp_backup_core->log("Failed to open file for write access: $encrypted_path");
268 return false;
269 }
270 }
271
272 // loop around the file
273 while ($data_encrypted < $file_size) {
274
275 // read buffer-sized amount from file
276 if (false === ($file_part = fread($file_handle, $buffer_size))) {
277 $iwp_backup_core->log("Failed to read from file: $fullpath");
278 return false;
279 }
280
281 // check to ensure padding is needed before encryption
282 $length = strlen($file_part);
283 if (0 != $length % 16) {
284 $pad = 16 - ($length % 16);
285 $file_part = str_pad($file_part, $length + $pad, chr($pad));
286 }
287
288 $encrypted_data = $rijndael->encrypt($file_part);
289
290 if (false === fwrite($encrypted_handle, $encrypted_data)) {
291 $iwp_backup_core->log("Failed to write to file: $encrypted_path");
292 return false;
293 }
294
295 $data_encrypted += $buffer_size;
296
297 $time_since_last_logged = microtime(true) - $time_last_logged;
298 if ($time_since_last_logged > 5) {
299 $time_since_last_logged = microtime(true);
300 $iwp_backup_core->log("Encrypting file: completed $data_encrypted bytes");
301 }
302
303 }
304
305 // close the main file handle
306 fclose($encrypted_handle);
307 fclose($file_handle);
308
309 // encrypted path
310 $result_path = $fullpath.'.crypt';
311
312 // need to replace original file with tmp file
313 if (false === rename($encrypted_path, $result_path)) {
314 $iwp_backup_core->log("File rename failed: $encrypted_path -> $result_path");
315 return false;
316 }
317
318 return $result_path;
319 }
320
321 /**
322 * This function spools the decrypted contents of a file to the browser
323 *
324 * @param String $fullpath This is the full path to the encrypted file
325 * @param String $encryption This is the key used to decrypt the file
326 *
327 * @uses header()
328 */
329 public static function spool_crypted_file($fullpath, $encryption) {
330
331 global $iwp_backup_core;
332
333 if ('' == $encryption) $encryption = IWP_MMB_Backup_Options::get_iwp_backup_option('IWP_encryptionphrase');
334
335 if ('' == $encryption) {
336 header('Content-type: text/plain');
337 _e("Decryption failed. The database file is encrypted, but you have no encryption key entered.", 'iwp_backup_core');
338 $iwp_backup_core->log('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.', 'error');
339 } else {
340
341 // now decrypt the file and return array
342 $decrypted_file = self::decrypt($fullpath, $encryption, true);
343
344 // check to ensure there is a response back
345 if (is_array($decrypted_file)) {
346 header('Content-type: application/x-gzip');
347 header("Content-Disposition: attachment; filename=\"".$decrypted_file['basename']."\";");
348 header("Content-Length: ".filesize($decrypted_file['fullpath']));
349 readfile($decrypted_file['fullpath']);
350
351 // need to remove the file as this is no longer needed on the local server
352 unlink($decrypted_file['fullpath']);
353 } else {
354 header('Content-type: text/plain');
355 echo __("Decryption failed. The most likely cause is that you used the wrong key.", 'iwp_backup_core')." ".__('The decryption key used:', 'iwp_backup_core').' '.$encryption;
356
357 }
358 }
359 }
360
361 /**
362 * Indicate whether an indicated backup file is encrypted or not, as indicated by the suffix
363 *
364 * @param String $file - the filename
365 *
366 * @return Boolean
367 */
368 public static function is_file_encrypted($file) {
369 return preg_match('/\.crypt$/i', $file);
370 }
371
372 public static function str_ends_with($haystack, $needle) {
373 if (substr($haystack, - strlen($needle)) == $needle) return true;
374 return false;
375 }
376 }
377