PluginProbe
Auto Alt Text / trunk
Auto Alt Text vtrunk
3.0.3 2.8.2 1.3.1 1.3.2 2.0.0 2.1.0 2.1.1 2.2.0 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.6.1 2.7.0 2.8.0 2.8.1 All 28 releases
auto-alt-text / src / App / Utilities / Encryption.php

Encryption.php in Auto Alt Text trunk, at src/App/Utilities/Encryption.php

164 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace AATXT\App\Utilities;
4
5 use AATXT\Config\Constants;
6 use RuntimeException;
7
8 final class Encryption
9 {
10 private string $key;
11 private string $salt;
12
13 public function __construct()
14 {
15 $this->key = $this->getKey();
16 $this->salt = $this->getSalt();
17 }
18
19 /**
20 * @return Encryption
21 */
22 public static function make(): Encryption
23 {
24 return new self();
25 }
26
27 /**
28 * @param string $value
29 * @return string|bool
30 */
31 public function encrypt(string $value): string
32 {
33 if (empty($value)) {
34 return '';
35 }
36
37 if (!extension_loaded('openssl')) {
38 return $value;
39 }
40
41 $method = 'aes-256-ctr';
42 $ivLength = openssl_cipher_iv_length($method);
43 $iv = openssl_random_pseudo_bytes($ivLength);
44 $raw_value = openssl_encrypt($value . $this->salt, $method, $this->key, 0, $iv);
45 if (!$raw_value) {
46 throw new RuntimeException('Encryption failed.');
47 }
48
49 return base64_encode($iv . $raw_value);
50 }
51
52 /**
53 * Attempts to decrypt a raw value using the provided key and salt.
54 **/
55 private function attemptDecrypt(string $rawValue, string $key, string $salt): string
56 {
57 $decoded = base64_decode($rawValue, true);
58 $method = 'aes-256-ctr';
59 $ivLength = openssl_cipher_iv_length($method);
60 $iv = substr($decoded, 0, $ivLength);
61 $cipher = substr($decoded, $ivLength);
62 $decrypted = openssl_decrypt($cipher, $method, $key, 0, $iv);
63
64 // If decryption fails or the salt is not present, return an empty string
65 if (! $decrypted || substr($decrypted, -strlen($salt)) !== $salt) {
66 return '';
67 }
68 return substr($decrypted, 0, -strlen($salt));
69 }
70
71 /**
72 * Decrypts a raw value trying with the plugin key and salt first, then falling back to the logged-in key and salt.
73 * @param string $rawValue
74 * @return string
75 */
76 public function decrypt(string $rawValue): string
77 {
78 if (empty($rawValue) || ! extension_loaded('openssl')) {
79 return '';
80 }
81
82 try {
83 $plain = $this->attemptDecrypt($rawValue, $this->key, $this->salt);
84 if ($plain !== '') {
85 return $plain;
86 }
87
88 // If the decryption with the plugin key fails, try as fallback with the logged-in key and salt
89 if (defined('LOGGED_IN_KEY') && defined('LOGGED_IN_SALT')) {
90 $plainOld = $this->attemptDecrypt($rawValue, LOGGED_IN_KEY, LOGGED_IN_SALT);
91 if ($plainOld !== '') {
92 return $plainOld;
93 }
94 }
95
96 return '';
97 } catch (\Throwable $e) {
98 throw new RuntimeException('Decryption failed.');
99 }
100 }
101
102 private function getKey(): string
103 {
104 if ( defined( 'AATXT_ENCRYPTION_KEY' ) && '' !== AATXT_ENCRYPTION_KEY ) {
105 return AATXT_ENCRYPTION_KEY;
106 }
107 // If the constant is not defined, use the WordPress constants LOGGED_IN_KEY and LOGGED_IN_SALT
108 if (defined('LOGGED_IN_KEY') && '' !== LOGGED_IN_KEY) {
109 return LOGGED_IN_KEY;
110 }
111
112 // If this is reached, you're either not on a live site or have a serious security issue.
113 return 'warning-not-logged-in-key-constant-defined';
114 }
115
116 public function getSalt(): string
117 {
118 if ( defined( 'AATXT_ENCRYPTION_SALT' ) && '' !== AATXT_ENCRYPTION_SALT ) {
119 return AATXT_ENCRYPTION_SALT;
120 }
121 // If the constant is not defined, use the WordPress constants LOGGED_IN_KEY and LOGGED_IN_SALT
122 if (defined('LOGGED_IN_SALT') && '' !== LOGGED_IN_SALT) {
123 return LOGGED_IN_SALT;
124 }
125
126 // If this is reached, you're either not on a live site or have a serious security issue.
127 return 'warning-not-logged-in-salt-constant-defined';
128 }
129
130 /**
131 * Migrate legacy API keys from the old encryption method to the new one.
132 * This is necessary for backward compatibility with older versions of the plugin.
133 */
134 public function migrateLegacyApiKeys(): void
135 {
136 $fields = [
137 Constants::AATXT_OPTION_FIELD_API_KEY_OPENAI,
138 Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_COMPUTER_VISION,
139 Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_TRANSLATE_INSTANCE,
140 ];
141
142 foreach ($fields as $optionName) {
143 $raw = get_option($optionName);
144 if (empty($raw)) {
145 continue;
146 }
147
148 if (! defined('LOGGED_IN_KEY') || ! defined('LOGGED_IN_SALT')) {
149 continue;
150 }
151 // Try to decrypt options using the old method with the WordPress constants LOGGED_IN_KEY and LOGGED_IN_SALT
152 $plain = $this->attemptDecrypt($raw, LOGGED_IN_KEY, LOGGED_IN_SALT);
153
154 if ($plain === '') {
155 // Decryption failed, cause the option is encrypted with the new method or the old method failed
156 continue;
157 }
158
159 //Resave the option as plain because the action "encryptDataOnUpdate" will encrypt before saving in the database
160 update_option($optionName, $plain);
161 }
162 }
163 }
164