PluginProbe
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment / 3.1.14
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment v3.1.14
3.1.14 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 All 123 releases
← All changes | Inc/Core/Helpers/Encryption.php +107 -17 3.1.7 → 3.1.14 View file →
@@ -1,8 +1,8 @@
1 1 <?php
2 2 /**
3 3 * @package FireBox
4 - * @version 3.1.7
4 + * @version 3.1.14
5 5 *
6 6 * @author FirePlugins <[email protected]>
7 7 * @link https://www.fireplugins.com
8 8 * @copyright Copyright © 2026 FirePlugins All Rights Reserved
@@ -15,23 +15,54 @@
15 15 {
16 16 exit; // Exit if accessed directly.
17 17 }
18 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 + */
19 33 class Encryption
20 34 {
21 35 /**
22 - * Fixed salt for portable encryption across site exports/imports.
36 + * Salt the encryption key is derived from.
23 37 *
38 + * Fixed on purpose, so encrypted values survive a site move. See the class docblock.
39 + *
24 40 * @var string
25 41 */
26 - private $fixed_salt = 'fbox_enc_3b7d50a8f9e84cb1a4627c8d6e1f90b3';
42 + private $salt = 'fbox_enc_3b7d50a8f9e84cb1a4627c8d6e1f90b3';
27 43
28 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 + /**
29 60 * Encrypt value for storage.
30 61 *
31 62 * @param string $value
32 63 *
33 - * @return string
64 + * @return string|false Payload, '' for empty input, or false when encryption failed.
34 65 */
35 66 final public function encrypt($value = '')
36 67 {
37 68 $value = trim((string) $value);
@@ -42,18 +73,26 @@
42 73
43 74 $key = $this->getEncryptionKey();
44 75 if ($key === '')
45 76 {
46 - return $value;
77 + return false;
47 78 }
48 79
49 80 $encrypted_payload = $this->encryptWithOpenSSL($value, $key);
50 81 if ($encrypted_payload === false)
51 82 {
52 - return $value;
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;
53 89 }
54 90
55 - return base64_encode($encrypted_payload);
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);
56 95 }
57 96
58 97 /**
59 98 * Decrypt value from storage.
@@ -69,11 +108,15 @@
69 108 {
70 109 return '';
71 110 }
72 111
73 - $decoded = base64_decode($value, true);
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);
74 116 if ($decoded === false || $decoded === '')
75 117 {
118 + // Not a payload we produced; hand back the value as stored.
76 119 return $value;
77 120 }
78 121
79 122 $key = $this->getEncryptionKey();
@@ -81,11 +124,34 @@
81 124 {
82 125 return $value;
83 126 }
84 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 +
85 147 $decrypted = $this->decryptWithOpenSSL($decoded, $key);
86 148 if ($decrypted === '')
87 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 + */
88 154 return $value;
89 155 }
90 156
91 157 return trim((string) $decrypted);
@@ -90,11 +156,26 @@
90 156
91 157 return trim((string) $decrypted);
92 158 }
93 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 + */
94 175 private function getEncryptionKey()
95 176 {
96 - $salt = trim((string) $this->fixed_salt);
177 + $salt = trim((string) $this->salt);
97 178 if ($salt === '')
98 179 {
99 180 return '';
100 181 }
@@ -109,17 +190,28 @@
109 190 * @param string $key
110 191 *
111 192 * @return string|false
112 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 +
113 206 private function encryptWithOpenSSL($value = '', $key = '')
114 207 {
115 - if (!function_exists('openssl_encrypt') || !function_exists('openssl_cipher_iv_length'))
208 + if (!self::isAvailable())
116 209 {
117 210 return false;
118 211 }
119 212
120 - $cipher = 'aes-256-cbc';
121 - $iv_length = openssl_cipher_iv_length($cipher);
213 + $iv_length = openssl_cipher_iv_length(self::CIPHER);
122 214 if (!is_int($iv_length) || $iv_length <= 0)
123 215 {
124 216 return false;
125 217 }
@@ -133,9 +225,9 @@
133 225 return false;
134 226 }
135 227
136 228 $openssl_raw_data = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : 1;
137 - $encrypted = openssl_encrypt($value, $cipher, $key, $openssl_raw_data, $iv);
229 + $encrypted = openssl_encrypt($value, self::CIPHER, $key, $openssl_raw_data, $iv);
138 230 if ($encrypted === false)
139 231 {
140 232 return false;
141 233 }
@@ -157,10 +249,9 @@
157 249 {
158 250 return '';
159 251 }
160 252
161 - $cipher = 'aes-256-cbc';
162 - $iv_length = openssl_cipher_iv_length($cipher);
253 + $iv_length = openssl_cipher_iv_length(self::CIPHER);
163 254 if (!is_int($iv_length) || $iv_length <= 0 || strlen($payload) <= $iv_length)
164 255 {
165 256 return '';
166 257 }
@@ -172,10 +263,9 @@
172 263 return '';
173 264 }
174 265
175 266 $openssl_raw_data = defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : 1;
176 - $decrypted = openssl_decrypt($encrypted, $cipher, $key, $openssl_raw_data, $iv);
267 + $decrypted = openssl_decrypt($encrypted, self::CIPHER, $key, $openssl_raw_data, $iv);
177 268
178 269 return $decrypted === false ? '' : (string) $decrypted;
179 270 }
180 -
181 271 }