PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / 260917
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions v260917
260917 260913 260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 111216 111220 120213 All 189 releases
s2member / src / vendor / defuse / php-encryption / src / Crypto.php

Crypto.php in s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions 260917, at src/vendor/defuse/php-encryption/src/Crypto.php

373 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Defuse\Crypto;
4
5 use Defuse\Crypto\Exception as Ex;
6
7 class Crypto
8 {
9 /**
10 * Encrypts a string with a Key.
11 *
12 * @param string $plaintext
13 * @param Key $key
14 * @param bool $raw_binary
15 *
16 * @throws Ex\EnvironmentIsBrokenException
17 *
18 * @return string
19 */
20 public static function encrypt($plaintext, Key $key, $raw_binary = false)
21 {
22 return self::encryptInternal(
23 $plaintext,
24 KeyOrPassword::createFromKey($key),
25 $raw_binary
26 );
27 }
28
29 /**
30 * Encrypts a string with a password, using a slow key derivation function
31 * to make password cracking more expensive.
32 *
33 * @param string $plaintext
34 * @param string $password
35 * @param bool $raw_binary
36 *
37 * @throws Ex\EnvironmentIsBrokenException
38 *
39 * @return string
40 */
41 public static function encryptWithPassword($plaintext, $password, $raw_binary = false)
42 {
43 return self::encryptInternal(
44 $plaintext,
45 KeyOrPassword::createFromPassword($password),
46 $raw_binary
47 );
48 }
49
50 /**
51 * Decrypts a ciphertext to a string with a Key.
52 *
53 * @param string $ciphertext
54 * @param Key $key
55 * @param bool $raw_binary
56 *
57 * @throws Ex\EnvironmentIsBrokenException
58 * @throws Ex\WrongKeyOrModifiedCiphertextException
59 *
60 * @return string
61 */
62 public static function decrypt($ciphertext, Key $key, $raw_binary = false)
63 {
64 return self::decryptInternal(
65 $ciphertext,
66 KeyOrPassword::createFromKey($key),
67 $raw_binary
68 );
69 }
70
71 /**
72 * Decrypts a ciphertext to a string with a password, using a slow key
73 * derivation function to make password cracking more expensive.
74 *
75 * @param string $ciphertext
76 * @param string $password
77 * @param bool $raw_binary
78 *
79 * @throws Ex\EnvironmentIsBrokenException
80 * @throws Ex\WrongKeyOrModifiedCiphertextException
81 *
82 * @return string
83 */
84 public static function decryptWithPassword($ciphertext, $password, $raw_binary = false)
85 {
86 return self::decryptInternal(
87 $ciphertext,
88 KeyOrPassword::createFromPassword($password),
89 $raw_binary
90 );
91 }
92
93 /**
94 * Decrypts a legacy ciphertext produced by version 1 of this library.
95 *
96 * @param string $ciphertext
97 * @param string $key
98 *
99 * @throws Ex\EnvironmentIsBrokenException
100 * @throws Ex\WrongKeyOrModifiedCiphertextException
101 *
102 * @return string
103 */
104 public static function legacyDecrypt($ciphertext, $key)
105 {
106 RuntimeTests::runtimeTest();
107
108 // Extract the HMAC from the front of the ciphertext.
109 if (Core::ourStrlen($ciphertext) <= Core::LEGACY_MAC_BYTE_SIZE) {
110 throw new Ex\WrongKeyOrModifiedCiphertextException(
111 'Ciphertext is too short.'
112 );
113 }
114 $hmac = Core::ourSubstr($ciphertext, 0, Core::LEGACY_MAC_BYTE_SIZE);
115 if ($hmac === false) {
116 throw new Ex\EnvironmentIsBrokenException();
117 }
118 $ciphertext = Core::ourSubstr($ciphertext, Core::LEGACY_MAC_BYTE_SIZE);
119 if ($ciphertext === false) {
120 throw new Ex\EnvironmentIsBrokenException();
121 }
122
123 // Regenerate the same authentication sub-key.
124 $akey = Core::HKDF(
125 Core::LEGACY_HASH_FUNCTION_NAME,
126 $key,
127 Core::LEGACY_KEY_BYTE_SIZE,
128 Core::LEGACY_AUTHENTICATION_INFO_STRING,
129 null
130 );
131
132 if (self::verifyHMAC($hmac, $ciphertext, $akey)) {
133 // Regenerate the same encryption sub-key.
134 $ekey = Core::HKDF(
135 Core::LEGACY_HASH_FUNCTION_NAME,
136 $key,
137 Core::LEGACY_KEY_BYTE_SIZE,
138 Core::LEGACY_ENCRYPTION_INFO_STRING,
139 null
140 );
141
142 // Extract the IV from the ciphertext.
143 if (Core::ourStrlen($ciphertext) <= Core::LEGACY_BLOCK_BYTE_SIZE) {
144 throw new Ex\WrongKeyOrModifiedCiphertextException(
145 'Ciphertext is too short.'
146 );
147 }
148 $iv = Core::ourSubstr($ciphertext, 0, Core::LEGACY_BLOCK_BYTE_SIZE);
149 if ($iv === false) {
150 throw new Ex\EnvironmentIsBrokenException();
151 }
152 $ciphertext = Core::ourSubstr($ciphertext, Core::LEGACY_BLOCK_BYTE_SIZE);
153 if ($ciphertext === false) {
154 throw new Ex\EnvironmentIsBrokenException();
155 }
156
157 // Do the decryption.
158 $plaintext = self::plainDecrypt($ciphertext, $ekey, $iv, Core::LEGACY_CIPHER_METHOD);
159 return $plaintext;
160 } else {
161 throw new Ex\WrongKeyOrModifiedCiphertextException(
162 'Integrity check failed.'
163 );
164 }
165 }
166
167 /**
168 * Encrypts a string with either a key or a password.
169 *
170 * @param string $plaintext
171 * @param KeyOrPassword $secret
172 * @param bool $raw_binary
173 *
174 * @return string
175 */
176 private static function encryptInternal($plaintext, KeyOrPassword $secret, $raw_binary)
177 {
178 RuntimeTests::runtimeTest();
179
180 $salt = Core::secureRandom(Core::SALT_BYTE_SIZE);
181 $keys = $secret->deriveKeys($salt);
182 $ekey = $keys->getEncryptionKey();
183 $akey = $keys->getAuthenticationKey();
184 $iv = Core::secureRandom(Core::BLOCK_BYTE_SIZE);
185
186 $ciphertext = Core::CURRENT_VERSION . $salt . $iv . self::plainEncrypt($plaintext, $ekey, $iv);
187 $auth = \hash_hmac(Core::HASH_FUNCTION_NAME, $ciphertext, $akey, true);
188 $ciphertext = $ciphertext . $auth;
189
190 if ($raw_binary) {
191 return $ciphertext;
192 }
193 return Encoding::binToHex($ciphertext);
194 }
195
196 /**
197 * Decrypts a ciphertext to a string with either a key or a password.
198 *
199 * @param string $ciphertext
200 * @param KeyOrPassword $secret
201 * @param bool $raw_binary
202 *
203 * @throws Ex\EnvironmentIsBrokenException
204 * @throws Ex\WrongKeyOrModifiedCiphertextException
205 *
206 * @return string
207 */
208 private static function decryptInternal($ciphertext, KeyOrPassword $secret, $raw_binary)
209 {
210 RuntimeTests::runtimeTest();
211
212 if (! $raw_binary) {
213 try {
214 $ciphertext = Encoding::hexToBin($ciphertext);
215 } catch (Ex\BadFormatException $ex) {
216 throw new Ex\WrongKeyOrModifiedCiphertextException(
217 'Ciphertext has invalid hex encoding.'
218 );
219 }
220 }
221
222 if (Core::ourStrlen($ciphertext) < Core::MINIMUM_CIPHERTEXT_SIZE) {
223 throw new Ex\WrongKeyOrModifiedCiphertextException(
224 'Ciphertext is too short.'
225 );
226 }
227
228 // Get and check the version header.
229 $header = Core::ourSubstr($ciphertext, 0, Core::HEADER_VERSION_SIZE);
230 if ($header !== Core::CURRENT_VERSION) {
231 throw new Ex\WrongKeyOrModifiedCiphertextException(
232 'Bad version header.'
233 );
234 }
235
236 // Get the salt.
237 $salt = Core::ourSubstr(
238 $ciphertext,
239 Core::HEADER_VERSION_SIZE,
240 Core::SALT_BYTE_SIZE
241 );
242 if ($salt === false) {
243 throw new Ex\EnvironmentIsBrokenException();
244 }
245
246 // Get the IV.
247 $iv = Core::ourSubstr(
248 $ciphertext,
249 Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE,
250 Core::BLOCK_BYTE_SIZE
251 );
252 if ($iv === false) {
253 throw new Ex\EnvironmentIsBrokenException();
254 }
255
256 // Get the HMAC.
257 $hmac = Core::ourSubstr(
258 $ciphertext,
259 Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE,
260 Core::MAC_BYTE_SIZE
261 );
262 if ($hmac === false) {
263 throw new Ex\EnvironmentIsBrokenException();
264 }
265
266 // Get the actual encrypted ciphertext.
267 $encrypted = Core::ourSubstr(
268 $ciphertext,
269 Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE +
270 Core::BLOCK_BYTE_SIZE,
271 Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE - Core::SALT_BYTE_SIZE -
272 Core::BLOCK_BYTE_SIZE - Core::HEADER_VERSION_SIZE
273 );
274 if ($encrypted === false) {
275 throw new Ex\EnvironmentIsBrokenException();
276 }
277
278 // Derive the separate encryption and authentication keys from the key
279 // or password, whichever it is.
280 $keys = $secret->deriveKeys($salt);
281
282 if (self::verifyHMAC($hmac, $header . $salt . $iv . $encrypted, $keys->getAuthenticationKey())) {
283 $plaintext = self::plainDecrypt($encrypted, $keys->getEncryptionKey(), $iv, Core::CIPHER_METHOD);
284 return $plaintext;
285 } else {
286 throw new Ex\WrongKeyOrModifiedCiphertextException(
287 'Integrity check failed.'
288 );
289 }
290 }
291
292 /**
293 * Raw unauthenticated encryption (insecure on its own).
294 *
295 * @param string $plaintext
296 * @param string $key
297 * @param string $iv
298 *
299 * @throws Ex\EnvironmentIsBrokenException
300 *
301 * @return string
302 */
303 protected static function plainEncrypt($plaintext, $key, $iv)
304 {
305 Core::ensureConstantExists('OPENSSL_RAW_DATA');
306 Core::ensureFunctionExists('openssl_encrypt');
307 $ciphertext = \openssl_encrypt(
308 $plaintext,
309 Core::CIPHER_METHOD,
310 $key,
311 OPENSSL_RAW_DATA,
312 $iv
313 );
314
315 if ($ciphertext === false) {
316 throw new Ex\EnvironmentIsBrokenException(
317 'openssl_encrypt() failed.'
318 );
319 }
320
321 return $ciphertext;
322 }
323
324 /**
325 * Raw unauthenticated decryption (insecure on its own).
326 *
327 * @param string $ciphertext
328 * @param string $key
329 * @param string $iv
330 * @param string $cipherMethod
331 *
332 * @throws Ex\EnvironmentIsBrokenException
333 *
334 * @return string
335 */
336 protected static function plainDecrypt($ciphertext, $key, $iv, $cipherMethod)
337 {
338 Core::ensureConstantExists('OPENSSL_RAW_DATA');
339 Core::ensureFunctionExists('openssl_decrypt');
340 $plaintext = \openssl_decrypt(
341 $ciphertext,
342 $cipherMethod,
343 $key,
344 OPENSSL_RAW_DATA,
345 $iv
346 );
347 if ($plaintext === false) {
348 throw new Ex\EnvironmentIsBrokenException(
349 'openssl_decrypt() failed.'
350 );
351 }
352
353 return $plaintext;
354 }
355
356 /**
357 * Verifies an HMAC without leaking information through side-channels.
358 *
359 * @param string $correct_hmac
360 * @param string $message
361 * @param string $key
362 *
363 * @throws Ex\EnvironmentIsBrokenException
364 *
365 * @return bool
366 */
367 protected static function verifyHMAC($correct_hmac, $message, $key)
368 {
369 $message_hmac = \hash_hmac(Core::HASH_FUNCTION_NAME, $message, $key, true);
370 return Core::hashEquals($correct_hmac, $message_hmac);
371 }
372 }
373