PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 1.16.5
UpdraftPlus: WP Backup & Migration Plugin v1.16.5
1.26.7 1.26.6 1.26.5 1.26.4 1.26.3 1.9.19 1.9.25 1.9.26 1.9.30 1.9.31 1.9.32 1.9.4 1.9.40 1.9.41 1.9.42 1.9.43 1.9.44 1.9.45 1.9.46 1.9.5 1.9.50 1.9.51 1.9.60 1.9.62 1.9.63 All 371 releases
updraftplus / includes / class-updraftplus-encryption.php

class-updraftplus-encryption.php in UpdraftPlus: WP Backup & Migration Plugin 1.16.5, at includes/class-updraftplus-encryption.php

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