Stand-Alone PHP Script for Decrypting AES-Encrypted Backups

Public

AES-encrypted backups from `Backup and Migrate` (B&M) module could only be decrypted using the Drupal Admin UI, resulting to backups being unusable when you have a broken site, which defeats the purpose of having backups in the first place. Linux/Unix command-line utilities could not decrypt the backups properly due to custom padding inserted by the the B&M during encryption. One solution is to create a custom PHP script utilizing the codes/ideas of the B&M and AES Drupal modules. The stand-alone PHP script could then be run in a terminal like this:
$ php decryptor.php <ENCRYPTED_BACKUP_FILE_PATH> <AES_KEY> <INITIALIZATION_VECTOR>

Usage:
1. Required and Generated Files:
a. Input: Kite-2017-05-22T18-56-15.mysql.zip.aes
b. Output: Kite-2017-05-22T18-56-15.mysql.zip

2. Required AES Encryption/Decryption Variables:
a. AES Key (e.g. R32jQ3xIveTGCdfAmNSExHxezjNjaC23)
b. AES Initialization Vector (e.g. l1h62UMz7K9l8/G0CtSWIg==)

3. Save the provided code snippets in a `decryptor.php` file and run the command below:
$ php decryptor.php <AES_FILE_PATH> <AES_KEY> <INITIALIZATION_VECTOR>

Parameters:
<ENCRYPTED_BACKUP_FILE_PATH> => ~/Backups/Kite-2017-05-22.mysql.zip.aes
<AES_KEY> => $ drush vget aes_key (fetches the randomly-generated default, unless it's overriden in settings.php)
<INITIALIZATION_VECTOR> => $ drush vget aes_encryption_iv

Sample Run (this will create the unencrypted ~/Backups/Kite-2017-05-22.mysql.zip after):
$ php decryptor.php ~/Backups/Kite-2017-05-22.mysql.zip.aes R32jQ3xIveTGCdfAmNSExHxezjNjaC23 l1h62UMz7K9l8/G0CtSWIg==

Related Issues:
https://www.drupal.org/node/1347194
https://www.drupal.org/node/2515006
https://www.drupal.org/node/1934236
https://www.drupal.org/node/2199399

Get raw version
php
  1. // Customized/trimmed down version from `AES` Drupal module.
  2. // Assumes that AES is implemented using the `mcrypt` PHP extension and
  3. // using the `rijndael-128` cipher.
  4. // See the `aes_decrypt()` in `AES` Drupal module if you have different use case.
  5. function aes_decrypt($string, $custom_key, $custom_iv) {
  6. $cipher = 'rijndael-128';
  7. $implementation = 'mcrypt';
  8. $key = $custom_key;
  9. $iv = base64_decode($custom_iv);
  10.  
  11. // Since `mcrypt_module_open_safe()` has issues.
  12. $td = mcrypt_module_open($cipher, "", MCRYPT_MODE_CBC, "");
  13. $ks = mcrypt_enc_get_key_size($td);
  14. $key = substr(sha1($key), 0, $ks);
  15.  
  16. mcrypt_generic_init($td, $key, $iv);
  17. $decrypted = mdecrypt_generic($td, $string);
  18. mcrypt_generic_deinit($td);
  19. mcrypt_module_close($td);
  20.  
  21. return trim($decrypted);
  22. }
  23.  
  24. // Target AES-encrypted file (e.g. ~/Backups/Kite-2017-05-23.mysql.zip.aes).
  25. $encrypted_compressed_file_path = $argv[1];
  26.  
  27. // AES Encryption Key (e.g. R32jQ3xIveTGCdfAmNSExHxezjNjaC23)
  28. $custom_key = $argv[2];
  29.  
  30. // AES Encryption Initialization Vector (e.g. l1h62UMz7K9l8/G0CtSWIg==).
  31. $custom_iv = $argv[3];
  32.  
  33. // Clone the 'aes' file but w/out the 'aes' extension (preserve the original).
  34. $path_parts = pathinfo($encrypted_compressed_file_path);
  35. $target_compressed_file_path = $path_parts['dirname'] . '/' . $path_parts['filename'];
  36. copy($encrypted_compressed_file_path, $target_compressed_file_path);
  37.  
  38. // Load the compressed file and decrypt.
  39. $data = file_get_contents($target_compressed_file_path);
  40. $data = aes_decrypt($data, $custom_key, $custom_iv);
  41.  
  42. // Post-processing: trim all the padding zeros plus the non-zero marker,
  43. // as emphasized in `Backup and Migrate` Drupal module.
  44. $data = substr(rtrim($data, '\0'), 0, -1);
  45.  
  46. // Overwrite the temporary, compressed file w/ the unencrypted version.
  47. file_put_contents($target_compressed_file_path, $data);

Comments