PluginProbe
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment / 3.1.13
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment v3.1.13
3.1.13 3.1.12 3.1.11 3.1.10 3.1.9 3.1.8 3.1.7 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 All 122 releases
firebox / Inc / Core / Helpers / Encryption.php

Encryption.php in FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment 3.1.13, at Inc/Core/Helpers/Encryption.php

272 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package FireBox
4 * @version 3.1.13
5 *
6 * @author FirePlugins <info@fireplugins.com>
7 * @link https://www.fireplugins.com
8 * @copyright Copyright © 2026 FirePlugins All Rights Reserved
9 * @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
10 */
11
12 namespace FireBox\Core\Helpers;
13
14 if (!defined('ABSPATH'))
15 {
16 exit; // Exit if accessed directly.
17 }
18
19 /**
20 * Encrypts integration credentials at rest.
21 *
22 * The key is derived from a fixed salt, which makes stored values portable: a site can be
23 * migrated, cloned or restored from a backup and its credentials remain readable. That is
24 * a deliberate trade — a key tied to the site (wp-config salts, say) would resist a
25 * database-only leak, but would silently invalidate every credential whenever those salts
26 * changed, which happens routinely during migrations.
27 *
28 * Be clear about what this does and does not buy: an attacker holding a database dump can
29 * derive the same key from the plugin source, so this is obfuscation at rest rather than
30 * a defence against disclosure. Treat the credentials themselves as the secret and rotate
31 * them if a dump ever leaks.
32 */
33 class Encryption
34 {
35 /**
36 * Salt the encryption key is derived from.
37 *
38 * Fixed on purpose, so encrypted values survive a site move. See the class docblock.
39 *
40 * @var string
41 */
42 private $salt = 'fbox_enc_3b7d50a8f9e84cb1a4627c8d6e1f90b3';
43
44 /**
45 * Cipher used for stored values.
46 *
47 * @var string
48 */
49 const CIPHER = 'aes-256-cbc';
50
51 /**
52 * Prefix of payloads that carry an HMAC. Older values have no prefix and are
53 * still accepted on read so existing credentials keep working.
54 *
55 * @var string
56 */
57 const AUTHENTICATED_PREFIX = 'fbx2:';
58
59 /**
60 * Encrypt value for storage.
61 *
62 * @param string $value
63 *
64 * @return string|false Payload, '' for empty input, or false when encryption failed.
65 */
66 final public function encrypt($value = '')
67 {
68 $value = trim((string) $value);
69 if ($value === '')
70 {
71 return '';
72 }
73
74 $key = $this->getEncryptionKey();
75 if ($key === '')
76 {
77 return false;
78 }
79
80 $encrypted_payload = $this->encryptWithOpenSSL($value, $key);
81 if ($encrypted_payload === false)
82 {
83 /**
84 * Report the failure rather than returning the plaintext. Storing a raw
85 * credential under a name that implies encryption would leave it in the
86 * database in the clear with no signal to anyone.
87 */
88 return false;
89 }
90
91 // Authenticate iv+ciphertext so a tampered value is rejected instead of decrypted to garbage.
92 $mac = hash_hmac('sha256', $encrypted_payload, $this->getMacKey($key), true);
93
94 return self::AUTHENTICATED_PREFIX . base64_encode($encrypted_payload . $mac);
95 }
96
97 /**
98 * Decrypt value from storage.
99 *
100 * @param mixed $value
101 *
102 * @return string
103 */
104 final public function decrypt($value)
105 {
106 $value = trim((string) $value);
107 if ($value === '')
108 {
109 return '';
110 }
111
112 $authenticated = strpos($value, self::AUTHENTICATED_PREFIX) === 0;
113 $encoded = $authenticated ? substr($value, strlen(self::AUTHENTICATED_PREFIX)) : $value;
114
115 $decoded = base64_decode($encoded, true);
116 if ($decoded === false || $decoded === '')
117 {
118 // Not a payload we produced; hand back the value as stored.
119 return $value;
120 }
121
122 $key = $this->getEncryptionKey();
123 if ($key === '')
124 {
125 return $value;
126 }
127
128 if ($authenticated)
129 {
130 $mac_length = 32;
131 if (strlen($decoded) <= $mac_length)
132 {
133 return '';
134 }
135
136 $payload = substr($decoded, 0, -$mac_length);
137 $mac = substr($decoded, -$mac_length);
138
139 if (!hash_equals(hash_hmac('sha256', $payload, $this->getMacKey($key), true), $mac))
140 {
141 return '';
142 }
143
144 $decoded = $payload;
145 }
146
147 $decrypted = $this->decryptWithOpenSSL($decoded, $key);
148 if ($decrypted === '')
149 {
150 /**
151 * A value that is valid base64 but was never encrypted (a plaintext key that
152 * happens to decode) must come back unchanged rather than as an empty string.
153 */
154 return $value;
155 }
156
157 return trim((string) $decrypted);
158 }
159
160 /**
161 * Returns whether this platform can encrypt.
162 *
163 * @return bool
164 */
165 public static function isAvailable()
166 {
167 return function_exists('openssl_encrypt') && function_exists('openssl_cipher_iv_length');
168 }
169
170 /**
171 * Derives the encryption key.
172 *
173 * @return string Raw 32-byte key, or '' when the salt is unusable.
174 */
175 private function getEncryptionKey()
176 {
177 $salt = trim((string) $this->salt);
178 if ($salt === '')
179 {
180 return '';
181 }
182
183 return hash('sha256', $salt, true);
184 }
185
186 /**
187 * Encrypt plain text using OpenSSL.
188 *
189 * @param string $value
190 * @param string $key
191 *
192 * @return string|false
193 */
194 /**
195 * Separate key for the MAC, derived from the encryption key.
196 *
197 * @param string $key
198 *
199 * @return string
200 */
201 private function getMacKey($key)
202 {
203 return hash('sha256', 'mac|' . $key, true);
204 }
205
206 private function encryptWithOpenSSL($value = '', $key = '')
207 {
208 if (!self::isAvailable())
209 {
210 return false;
211 }
212
213 $iv_length = openssl_cipher_iv_length(self::CIPHER);
214 if (!is_int($iv_length) || $iv_length <= 0)
215 {
216 return false;
217 }
218
219 try
220 {
221 $iv = random_bytes($iv_length);
222 }
223 catch (\Exception $e)
224 {
225 return false;
226 }
227
228 $openssl_raw_data = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : 1;
229 $encrypted = openssl_encrypt($value, self::CIPHER, $key, $openssl_raw_data, $iv);
230 if ($encrypted === false)
231 {
232 return false;
233 }
234
235 return $iv . $encrypted;
236 }
237
238 /**
239 * Decrypt OpenSSL encrypted payload.
240 *
241 * @param string $payload
242 * @param string $key
243 *
244 * @return string
245 */
246 private function decryptWithOpenSSL($payload = '', $key = '')
247 {
248 if (!function_exists('openssl_decrypt') || !function_exists('openssl_cipher_iv_length'))
249 {
250 return '';
251 }
252
253 $iv_length = openssl_cipher_iv_length(self::CIPHER);
254 if (!is_int($iv_length) || $iv_length <= 0 || strlen($payload) <= $iv_length)
255 {
256 return '';
257 }
258
259 $iv = substr($payload, 0, $iv_length);
260 $encrypted = substr($payload, $iv_length);
261 if ($iv === false || $encrypted === false)
262 {
263 return '';
264 }
265
266 $openssl_raw_data = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : 1;
267 $decrypted = openssl_decrypt($encrypted, self::CIPHER, $key, $openssl_raw_data, $iv);
268
269 return $decrypted === false ? '' : (string) $decrypted;
270 }
271 }
272