run:R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
160.83 KB
2026-03-11 16:18:52
R W Run
53.68 KB
2026-03-11 16:18:52
R W Run
53.83 KB
2026-03-11 16:18:52
R W Run
53.53 KB
2026-03-11 16:18:52
R W Run
158 By
2026-03-11 16:18:52
R W Run
error_log
📄Compat.php
1<?php
2
3/**
4 * Libsodium compatibility layer
5 *
6 * This is the only class you should be interfacing with, as a user of
7 * sodium_compat.
8 *
9 * If the PHP extension for libsodium is installed, it will always use that
10 * instead of our implementations. You get better performance and stronger
11 * guarantees against side-channels that way.
12 *
13 * However, if your users don't have the PHP extension installed, we offer a
14 * compatible interface here. It will give you the correct results as if the
15 * PHP extension was installed. It won't be as fast, of course.
16 *
17 * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION *
18 * *
19 * Until audited, this is probably not safe to use! DANGER WILL ROBINSON *
20 * *
21 * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION *
22 */
23
24if (class_exists('ParagonIE_Sodium_Compat', false)) {
25 return;
26}
27
28/**
29 * @api
30 */
31class ParagonIE_Sodium_Compat
32{
33 /**
34 * This parameter prevents the use of the PECL extension.
35 * It should only be used for unit testing.
36 *
37 * @var bool
38 */
39 public static $disableFallbackForUnitTests = false;
40
41 /**
42 * Use fast multiplication rather than our constant-time multiplication
43 * implementation. Can be enabled at runtime. Only enable this if you
44 * are absolutely certain that there is no timing leak on your platform.
45 *
46 * @var bool
47 */
48 public static $fastMult = false;
49
50 const LIBRARY_MAJOR_VERSION = 9;
51 const LIBRARY_MINOR_VERSION = 1;
52 const LIBRARY_VERSION_MAJOR = 9;
53 const LIBRARY_VERSION_MINOR = 1;
54 const VERSION_STRING = 'polyfill-1.0.8';
55
56 // From libsodium
57 const BASE64_VARIANT_ORIGINAL = 1;
58 const BASE64_VARIANT_ORIGINAL_NO_PADDING = 3;
59 const BASE64_VARIANT_URLSAFE = 5;
60 const BASE64_VARIANT_URLSAFE_NO_PADDING = 7;
61 const CRYPTO_AEAD_AES256GCM_KEYBYTES = 32;
62 const CRYPTO_AEAD_AES256GCM_NSECBYTES = 0;
63 const CRYPTO_AEAD_AES256GCM_NPUBBYTES = 12;
64 const CRYPTO_AEAD_AES256GCM_ABYTES = 16;
65 const CRYPTO_AEAD_AEGIS128L_KEYBYTES = 16;
66 const CRYPTO_AEAD_AEGIS128L_NSECBYTES = 0;
67 const CRYPTO_AEAD_AEGIS128L_NPUBBYTES = 16;
68 const CRYPTO_AEAD_AEGIS128L_ABYTES = 32;
69 const CRYPTO_AEAD_AEGIS256_KEYBYTES = 32;
70 const CRYPTO_AEAD_AEGIS256_NSECBYTES = 0;
71 const CRYPTO_AEAD_AEGIS256_NPUBBYTES = 32;
72 const CRYPTO_AEAD_AEGIS256_ABYTES = 32;
73 const CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES = 32;
74 const CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES = 0;
75 const CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES = 8;
76 const CRYPTO_AEAD_CHACHA20POLY1305_ABYTES = 16;
77 const CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES = 32;
78 const CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES = 0;
79 const CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES = 12;
80 const CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES = 16;
81 const CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES = 32;
82 const CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NSECBYTES = 0;
83 const CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES = 24;
84 const CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES = 16;
85 const CRYPTO_AUTH_BYTES = 32;
86 const CRYPTO_AUTH_KEYBYTES = 32;
87 const CRYPTO_BOX_SEALBYTES = 16;
88 const CRYPTO_BOX_SECRETKEYBYTES = 32;
89 const CRYPTO_BOX_PUBLICKEYBYTES = 32;
90 const CRYPTO_BOX_KEYPAIRBYTES = 64;
91 const CRYPTO_BOX_MACBYTES = 16;
92 const CRYPTO_BOX_NONCEBYTES = 24;
93 const CRYPTO_BOX_SEEDBYTES = 32;
94 const CRYPTO_CORE_RISTRETTO255_BYTES = 32;
95 const CRYPTO_CORE_RISTRETTO255_SCALARBYTES = 32;
96 const CRYPTO_CORE_RISTRETTO255_HASHBYTES = 64;
97 const CRYPTO_CORE_RISTRETTO255_NONREDUCEDSCALARBYTES = 64;
98 const CRYPTO_KDF_BYTES_MIN = 16;
99 const CRYPTO_KDF_BYTES_MAX = 64;
100 const CRYPTO_KDF_CONTEXTBYTES = 8;
101 const CRYPTO_KDF_KEYBYTES = 32;
102 const CRYPTO_KX_BYTES = 32;
103 const CRYPTO_KX_PRIMITIVE = 'x25519blake2b';
104 const CRYPTO_KX_SEEDBYTES = 32;
105 const CRYPTO_KX_KEYPAIRBYTES = 64;
106 const CRYPTO_KX_PUBLICKEYBYTES = 32;
107 const CRYPTO_KX_SECRETKEYBYTES = 32;
108 const CRYPTO_KX_SESSIONKEYBYTES = 32;
109 const CRYPTO_GENERICHASH_BYTES = 32;
110 const CRYPTO_GENERICHASH_BYTES_MIN = 16;
111 const CRYPTO_GENERICHASH_BYTES_MAX = 64;
112 const CRYPTO_GENERICHASH_KEYBYTES = 32;
113 const CRYPTO_GENERICHASH_KEYBYTES_MIN = 16;
114 const CRYPTO_GENERICHASH_KEYBYTES_MAX = 64;
115 const CRYPTO_PWHASH_SALTBYTES = 16;
116 const CRYPTO_PWHASH_STRPREFIX = '$argon2id$';
117 const CRYPTO_PWHASH_ALG_ARGON2I13 = 1;
118 const CRYPTO_PWHASH_ALG_ARGON2ID13 = 2;
119 const CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE = 33554432;
120 const CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE = 4;
121 const CRYPTO_PWHASH_MEMLIMIT_MODERATE = 134217728;
122 const CRYPTO_PWHASH_OPSLIMIT_MODERATE = 6;
123 const CRYPTO_PWHASH_MEMLIMIT_SENSITIVE = 536870912;
124 const CRYPTO_PWHASH_OPSLIMIT_SENSITIVE = 8;
125 const CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES = 32;
126 const CRYPTO_PWHASH_SCRYPTSALSA208SHA256_STRPREFIX = '$7$';
127 const CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_INTERACTIVE = 534288;
128 const CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_INTERACTIVE = 16777216;
129 const CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_SENSITIVE = 33554432;
130 const CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_SENSITIVE = 1073741824;
131 const CRYPTO_SCALARMULT_BYTES = 32;
132 const CRYPTO_SCALARMULT_SCALARBYTES = 32;
133 const CRYPTO_SCALARMULT_RISTRETTO255_BYTES = 32;
134 const CRYPTO_SCALARMULT_RISTRETTO255_SCALARBYTES = 32;
135 const CRYPTO_SHORTHASH_BYTES = 8;
136 const CRYPTO_SHORTHASH_KEYBYTES = 16;
137 const CRYPTO_SECRETBOX_KEYBYTES = 32;
138 const CRYPTO_SECRETBOX_MACBYTES = 16;
139 const CRYPTO_SECRETBOX_NONCEBYTES = 24;
140 const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES = 17;
141 const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES = 24;
142 const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES = 32;
143 const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH = 0;
144 const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PULL = 1;
145 const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY = 2;
146 const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL = 3;
147 const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX = 0x3fffffff80;
148 const CRYPTO_SIGN_BYTES = 64;
149 const CRYPTO_SIGN_SEEDBYTES = 32;
150 const CRYPTO_SIGN_PUBLICKEYBYTES = 32;
151 const CRYPTO_SIGN_SECRETKEYBYTES = 64;
152 const CRYPTO_SIGN_KEYPAIRBYTES = 96;
153 const CRYPTO_STREAM_KEYBYTES = 32;
154 const CRYPTO_STREAM_NONCEBYTES = 24;
155 const CRYPTO_STREAM_XCHACHA20_KEYBYTES = 32;
156 const CRYPTO_STREAM_XCHACHA20_NONCEBYTES = 24;
157
158 /**
159 * Add two numbers (little-endian unsigned), storing the value in the first
160 * parameter.
161 *
162 * This mutates $val.
163 *
164 * @param string $val
165 * @param string $addv
166 * @return void
167 * @throws SodiumException
168 */
169 public static function add(
170 #[\SensitiveParameter]
171 &$val,
172 #[\SensitiveParameter]
173 $addv
174 ) {
175 $val_len = ParagonIE_Sodium_Core_Util::strlen($val);
176 $addv_len = ParagonIE_Sodium_Core_Util::strlen($addv);
177 if ($val_len !== $addv_len) {
178 throw new SodiumException('values must have the same length');
179 }
180 $A = ParagonIE_Sodium_Core_Util::stringToIntArray($val);
181 $B = ParagonIE_Sodium_Core_Util::stringToIntArray($addv);
182
183 $c = 0;
184 for ($i = 0; $i < $val_len; $i++) {
185 $c += ($A[$i] + $B[$i]);
186 $A[$i] = ($c & 0xff);
187 $c >>= 8;
188 }
189 $val = ParagonIE_Sodium_Core_Util::intArrayToString($A);
190 }
191
192 /**
193 * @param string $encoded
194 * @param int $variant
195 * @param string $ignore
196 * @return string
197 * @throws SodiumException
198 */
199 public static function base642bin(
200 #[\SensitiveParameter]
201 $encoded,
202 $variant,
203 $ignore = ''
204 ) {
205 /* Type checks: */
206 ParagonIE_Sodium_Core_Util::declareScalarType($encoded, 'string', 1);
207
208 /** @var string $encoded */
209 $encoded = (string) $encoded;
210
211 // Just strip before decoding
212 if (!empty($ignore)) {
213 $encoded = str_replace($ignore, '', $encoded);
214 }
215
216 try {
217 switch ($variant) {
218 case self::BASE64_VARIANT_ORIGINAL:
219 return ParagonIE_Sodium_Core_Base64_Original::decode($encoded, true);
220 case self::BASE64_VARIANT_ORIGINAL_NO_PADDING:
221 return ParagonIE_Sodium_Core_Base64_Original::decodeNoPadding($encoded);
222 case self::BASE64_VARIANT_URLSAFE:
223 return ParagonIE_Sodium_Core_Base64_UrlSafe::decode($encoded, true);
224 case self::BASE64_VARIANT_URLSAFE_NO_PADDING:
225 return ParagonIE_Sodium_Core_Base64_UrlSafe::decodeNoPadding($encoded);
226 default:
227 throw new SodiumException('invalid base64 variant identifier');
228 }
229 } catch (Exception $ex) {
230 if ($ex instanceof SodiumException) {
231 throw $ex;
232 }
233 throw new SodiumException('invalid base64 string', 0, $ex);
234 }
235 }
236
237 /**
238 * @param string $decoded
239 * @param int $variant
240 * @return string
241 * @throws SodiumException
242 */
243 public static function bin2base64(
244 #[\SensitiveParameter]
245 $decoded,
246 $variant
247 ) {
248 /* Type checks: */
249 ParagonIE_Sodium_Core_Util::declareScalarType($decoded, 'string', 1);
250 /** @var string $decoded */
251 $decoded = (string) $decoded;
252 if (ParagonIE_Sodium_Core_Util::strlen($decoded) === 0) {
253 return '';
254 }
255
256 switch ($variant) {
257 case self::BASE64_VARIANT_ORIGINAL:
258 return ParagonIE_Sodium_Core_Base64_Original::encode($decoded);
259 case self::BASE64_VARIANT_ORIGINAL_NO_PADDING:
260 return ParagonIE_Sodium_Core_Base64_Original::encodeUnpadded($decoded);
261 case self::BASE64_VARIANT_URLSAFE:
262 return ParagonIE_Sodium_Core_Base64_UrlSafe::encode($decoded);
263 case self::BASE64_VARIANT_URLSAFE_NO_PADDING:
264 return ParagonIE_Sodium_Core_Base64_UrlSafe::encodeUnpadded($decoded);
265 default:
266 throw new SodiumException('invalid base64 variant identifier');
267 }
268 }
269
270 /**
271 * Cache-timing-safe implementation of bin2hex().
272 *
273 * @param string $string A string (probably raw binary)
274 * @return string A hexadecimal-encoded string
275 * @throws SodiumException
276 * @throws TypeError
277 * @psalm-suppress MixedArgument
278 */
279 public static function bin2hex(
280 #[\SensitiveParameter]
281 $string
282 ) {
283 /* Type checks: */
284 ParagonIE_Sodium_Core_Util::declareScalarType($string, 'string', 1);
285
286 if (self::useNewSodiumAPI()) {
287 return (string) sodium_bin2hex($string);
288 }
289 if (self::use_fallback('bin2hex')) {
290 return (string) call_user_func('\\Sodium\\bin2hex', $string);
291 }
292 return ParagonIE_Sodium_Core_Util::bin2hex($string);
293 }
294
295 /**
296 * Compare two strings, in constant-time.
297 * Compared to memcmp(), compare() is more useful for sorting.
298 *
299 * @param string $left The left operand; must be a string
300 * @param string $right The right operand; must be a string
301 * @return int If < 0 if the left operand is less than the right
302 * If = 0 if both strings are equal
303 * If > 0 if the right operand is less than the left
304 * @throws SodiumException
305 * @throws TypeError
306 * @psalm-suppress MixedArgument
307 */
308 public static function compare(
309 #[\SensitiveParameter]
310 $left,
311 #[\SensitiveParameter]
312 $right
313 ) {
314 /* Type checks: */
315 ParagonIE_Sodium_Core_Util::declareScalarType($left, 'string', 1);
316 ParagonIE_Sodium_Core_Util::declareScalarType($right, 'string', 2);
317
318 if (self::useNewSodiumAPI()) {
319 return (int) sodium_compare($left, $right);
320 }
321 if (self::use_fallback('compare')) {
322 return (int) call_user_func('\\Sodium\\compare', $left, $right);
323 }
324 return ParagonIE_Sodium_Core_Util::compare($left, $right);
325 }
326
327 /**
328 * Authenticated Encryption with Associated Data: Decryption
329 *
330 * Algorithm:
331 * AEGIS-128L
332 *
333 * @param string $ciphertext Encrypted message (with MAC appended)
334 * @param string $assocData Authenticated Associated Data (unencrypted)
335 * @param string $nonce Number to be used only Once; must be 32 bytes
336 * @param string $key Encryption key
337 *
338 * @return string The original plaintext message
339 * @throws SodiumException
340 * @throws TypeError
341 * @psalm-suppress MixedArgument
342 * @psalm-suppress MixedInferredReturnType
343 * @psalm-suppress MixedReturnStatement
344 */
345 public static function crypto_aead_aegis128l_decrypt(
346 $ciphertext = '',
347 $assocData = '',
348 $nonce = '',
349 #[\SensitiveParameter]
350 $key = ''
351 ) {
352 ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
353 ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
354 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
355 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
356
357 /* Input validation: */
358 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_AEGIS128L_NPUBBYTES) {
359 throw new SodiumException('Nonce must be CRYPTO_AEAD_AEGIS128L_NPUBBYTES long');
360 }
361 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_AEGIS128L_KEYBYTES) {
362 throw new SodiumException('Key must be CRYPTO_AEAD_AEGIS128L_KEYBYTES long');
363 }
364 $ct_length = ParagonIE_Sodium_Core_Util::strlen($ciphertext);
365 if ($ct_length < self::CRYPTO_AEAD_AEGIS128L_ABYTES) {
366 throw new SodiumException('Message must be at least CRYPTO_AEAD_AEGIS128L_ABYTES long');
367 }
368
369 $ct = ParagonIE_Sodium_Core_Util::substr(
370 $ciphertext,
371 0,
372 $ct_length - self::CRYPTO_AEAD_AEGIS128L_ABYTES
373 );
374 $tag = ParagonIE_Sodium_Core_Util::substr(
375 $ciphertext,
376 $ct_length - self::CRYPTO_AEAD_AEGIS128L_ABYTES,
377 self::CRYPTO_AEAD_AEGIS128L_ABYTES
378 );
379 return ParagonIE_Sodium_Core_AEGIS128L::decrypt($ct, $tag, $assocData, $key, $nonce);
380 }
381
382 /**
383 * Authenticated Encryption with Associated Data: Encryption
384 *
385 * Algorithm:
386 * AEGIS-128L
387 *
388 * @param string $plaintext Message to be encrypted
389 * @param string $assocData Authenticated Associated Data (unencrypted)
390 * @param string $nonce Number to be used only Once; must be 32 bytes
391 * @param string $key Encryption key
392 *
393 * @return string Ciphertext with 32-byte authentication tag appended
394 * @throws SodiumException
395 * @throws TypeError
396 * @psalm-suppress MixedArgument
397 */
398 public static function crypto_aead_aegis128l_encrypt(
399 #[\SensitiveParameter]
400 $plaintext = '',
401 $assocData = '',
402 $nonce = '',
403 #[\SensitiveParameter]
404 $key = ''
405 ) {
406 ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
407 ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
408 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
409 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
410
411 /* Input validation: */
412 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_AEGIS128L_NPUBBYTES) {
413 throw new SodiumException('Nonce must be CRYPTO_AEAD_AEGIS128L_NPUBBYTES long');
414 }
415 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_AEGIS128L_KEYBYTES) {
416 throw new SodiumException('Key must be CRYPTO_AEAD_AEGIS128L_KEYBYTES long');
417 }
418
419 list($ct, $tag) = ParagonIE_Sodium_Core_AEGIS128L::encrypt($plaintext, $assocData, $key, $nonce);
420 return $ct . $tag;
421 }
422
423 /**
424 * Return a secure random key for use with the AEGIS-128L
425 * symmetric AEAD interface.
426 *
427 * @return string
428 * @throws Exception
429 * @throws Error
430 */
431 public static function crypto_aead_aegis128l_keygen()
432 {
433 return random_bytes(self::CRYPTO_AEAD_AEGIS128L_KEYBYTES);
434 }
435
436 /**
437 * Authenticated Encryption with Associated Data: Decryption
438 *
439 * Algorithm:
440 * AEGIS-256
441 *
442 * @param string $ciphertext Encrypted message (with MAC appended)
443 * @param string $assocData Authenticated Associated Data (unencrypted)
444 * @param string $nonce Number to be used only Once; must be 32 bytes
445 * @param string $key Encryption key
446 *
447 * @return string The original plaintext message
448 * @throws SodiumException
449 * @throws TypeError
450 * @psalm-suppress MixedArgument
451 * @psalm-suppress MixedInferredReturnType
452 * @psalm-suppress MixedReturnStatement
453 */
454 public static function crypto_aead_aegis256_decrypt(
455 $ciphertext = '',
456 $assocData = '',
457 $nonce = '',
458 #[\SensitiveParameter]
459 $key = ''
460 ) {
461 ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
462 ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
463 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
464 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
465
466 /* Input validation: */
467 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_AEGIS256_NPUBBYTES) {
468 throw new SodiumException('Nonce must be CRYPTO_AEAD_AEGIS256_NPUBBYTES long');
469 }
470 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_AEGIS256_KEYBYTES) {
471 throw new SodiumException('Key must be CRYPTO_AEAD_AEGIS256_KEYBYTES long');
472 }
473 $ct_length = ParagonIE_Sodium_Core_Util::strlen($ciphertext);
474 if ($ct_length < self::CRYPTO_AEAD_AEGIS256_ABYTES) {
475 throw new SodiumException('Message must be at least CRYPTO_AEAD_AEGIS256_ABYTES long');
476 }
477
478 $ct = ParagonIE_Sodium_Core_Util::substr(
479 $ciphertext,
480 0,
481 $ct_length - self::CRYPTO_AEAD_AEGIS256_ABYTES
482 );
483 $tag = ParagonIE_Sodium_Core_Util::substr(
484 $ciphertext,
485 $ct_length - self::CRYPTO_AEAD_AEGIS256_ABYTES,
486 self::CRYPTO_AEAD_AEGIS256_ABYTES
487 );
488 return ParagonIE_Sodium_Core_AEGIS256::decrypt($ct, $tag, $assocData, $key, $nonce);
489 }
490
491 /**
492 * Authenticated Encryption with Associated Data: Encryption
493 *
494 * Algorithm:
495 * AEGIS-256
496 *
497 * @param string $plaintext Message to be encrypted
498 * @param string $assocData Authenticated Associated Data (unencrypted)
499 * @param string $nonce Number to be used only Once; must be 32 bytes
500 * @param string $key Encryption key
501 *
502 * @return string Ciphertext with 32-byte authentication tag appended
503 * @throws SodiumException
504 * @throws TypeError
505 * @psalm-suppress MixedArgument
506 */
507 public static function crypto_aead_aegis256_encrypt(
508 #[\SensitiveParameter]
509 $plaintext = '',
510 $assocData = '',
511 $nonce = '',
512 #[\SensitiveParameter]
513 $key = ''
514 ) {
515 ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
516 ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
517 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
518 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
519
520 /* Input validation: */
521 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_AEGIS256_NPUBBYTES) {
522 throw new SodiumException('Nonce must be CRYPTO_AEAD_AEGIS256_NPUBBYTES long');
523 }
524 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_AEGIS256_KEYBYTES) {
525 throw new SodiumException('Key must be CRYPTO_AEAD_AEGIS256_KEYBYTES long');
526 }
527
528 list($ct, $tag) = ParagonIE_Sodium_Core_AEGIS256::encrypt($plaintext, $assocData, $key, $nonce);
529 return $ct . $tag;
530 }
531
532 /**
533 * Return a secure random key for use with the AEGIS-256
534 * symmetric AEAD interface.
535 *
536 * @return string
537 * @throws Exception
538 * @throws Error
539 */
540 public static function crypto_aead_aegis256_keygen()
541 {
542 return random_bytes(self::CRYPTO_AEAD_AEGIS256_KEYBYTES);
543 }
544
545 /**
546 * Is AES-256-GCM even available to use?
547 *
548 * @return bool
549 * @psalm-suppress UndefinedFunction
550 * @psalm-suppress MixedInferredReturnType
551 * @psalm-suppress MixedReturnStatement
552 */
553 public static function crypto_aead_aes256gcm_is_available()
554 {
555 if (self::useNewSodiumAPI()) {
556 return sodium_crypto_aead_aes256gcm_is_available();
557 }
558 if (self::use_fallback('crypto_aead_aes256gcm_is_available')) {
559 return call_user_func('\\Sodium\\crypto_aead_aes256gcm_is_available');
560 }
561 if (PHP_VERSION_ID < 70100) {
562 // OpenSSL doesn't support AEAD before 7.1.0
563 return false;
564 }
565 if (!extension_loaded('openssl')) {
566 return false;
567 }
568 if (!is_callable('openssl_encrypt') || !is_callable('openssl_decrypt')) {
569 // OpenSSL isn't installed
570 return false;
571 }
572 return (bool) in_array('aes-256-gcm', openssl_get_cipher_methods());
573 }
574
575 /**
576 * Authenticated Encryption with Associated Data: Decryption
577 *
578 * Algorithm:
579 * AES-256-GCM
580 *
581 * This mode uses a 64-bit random nonce with a 64-bit counter.
582 * IETF mode uses a 96-bit random nonce with a 32-bit counter.
583 *
584 * @param string $ciphertext Encrypted message (with Poly1305 MAC appended)
585 * @param string $assocData Authenticated Associated Data (unencrypted)
586 * @param string $nonce Number to be used only Once; must be 8 bytes
587 * @param string $key Encryption key
588 *
589 * @return string|bool The original plaintext message
590 * @throws SodiumException
591 * @throws TypeError
592 * @psalm-suppress MixedArgument
593 * @psalm-suppress MixedInferredReturnType
594 * @psalm-suppress MixedReturnStatement
595 */
596 public static function crypto_aead_aes256gcm_decrypt(
597 $ciphertext = '',
598 $assocData = '',
599 $nonce = '',
600 #[\SensitiveParameter]
601 $key = ''
602 ) {
603 if (!self::crypto_aead_aes256gcm_is_available()) {
604 throw new SodiumException('AES-256-GCM is not available');
605 }
606 ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
607 ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
608 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
609 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
610
611 /* Input validation: */
612 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_AES256GCM_NPUBBYTES) {
613 throw new SodiumException('Nonce must be CRYPTO_AEAD_AES256GCM_NPUBBYTES long');
614 }
615 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_AES256GCM_KEYBYTES) {
616 throw new SodiumException('Key must be CRYPTO_AEAD_AES256GCM_KEYBYTES long');
617 }
618 if (ParagonIE_Sodium_Core_Util::strlen($ciphertext) < self::CRYPTO_AEAD_AES256GCM_ABYTES) {
619 throw new SodiumException('Message must be at least CRYPTO_AEAD_AES256GCM_ABYTES long');
620 }
621 if (!extension_loaded('openssl')) {
622 throw new SodiumException('The OpenSSL extension is not installed');
623 }
624 if (!is_callable('openssl_decrypt')) {
625 throw new SodiumException('The OpenSSL extension is not installed, or openssl_decrypt() is not available');
626 }
627
628 /** @var string $ctext */
629 $ctext = ParagonIE_Sodium_Core_Util::substr($ciphertext, 0, -self::CRYPTO_AEAD_AES256GCM_ABYTES);
630 /** @var string $authTag */
631 $authTag = ParagonIE_Sodium_Core_Util::substr($ciphertext, -self::CRYPTO_AEAD_AES256GCM_ABYTES, 16);
632 return openssl_decrypt(
633 $ctext,
634 'aes-256-gcm',
635 $key,
636 OPENSSL_RAW_DATA,
637 $nonce,
638 $authTag,
639 $assocData
640 );
641 }
642
643 /**
644 * Authenticated Encryption with Associated Data: Encryption
645 *
646 * Algorithm:
647 * AES-256-GCM
648 *
649 * @param string $plaintext Message to be encrypted
650 * @param string $assocData Authenticated Associated Data (unencrypted)
651 * @param string $nonce Number to be used only Once; must be 8 bytes
652 * @param string $key Encryption key
653 *
654 * @return string Ciphertext with a 16-byte GCM message
655 * authentication code appended
656 * @throws SodiumException
657 * @throws TypeError
658 * @psalm-suppress MixedArgument
659 */
660 public static function crypto_aead_aes256gcm_encrypt(
661 #[\SensitiveParameter]
662 $plaintext = '',
663 $assocData = '',
664 $nonce = '',
665 #[\SensitiveParameter]
666 $key = ''
667 ) {
668 if (!self::crypto_aead_aes256gcm_is_available()) {
669 throw new SodiumException('AES-256-GCM is not available');
670 }
671 ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
672 ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
673 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
674 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
675
676 /* Input validation: */
677 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_AES256GCM_NPUBBYTES) {
678 throw new SodiumException('Nonce must be CRYPTO_AEAD_AES256GCM_NPUBBYTES long');
679 }
680 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_AES256GCM_KEYBYTES) {
681 throw new SodiumException('Key must be CRYPTO_AEAD_AES256GCM_KEYBYTES long');
682 }
683
684 if (!extension_loaded('openssl')) {
685 throw new SodiumException('The OpenSSL extension is not installed');
686 }
687 if (!is_callable('openssl_encrypt')) {
688 throw new SodiumException('The OpenSSL extension is not installed, or openssl_encrypt() is not available');
689 }
690
691 $authTag = '';
692 $ciphertext = openssl_encrypt(
693 $plaintext,
694 'aes-256-gcm',
695 $key,
696 OPENSSL_RAW_DATA,
697 $nonce,
698 $authTag,
699 $assocData
700 );
701 return $ciphertext . $authTag;
702 }
703
704 /**
705 * Return a secure random key for use with the AES-256-GCM
706 * symmetric AEAD interface.
707 *
708 * @return string
709 * @throws Exception
710 * @throws Error
711 */
712 public static function crypto_aead_aes256gcm_keygen()
713 {
714 return random_bytes(self::CRYPTO_AEAD_AES256GCM_KEYBYTES);
715 }
716
717 /**
718 * Authenticated Encryption with Associated Data: Decryption
719 *
720 * Algorithm:
721 * ChaCha20-Poly1305
722 *
723 * This mode uses a 64-bit random nonce with a 64-bit counter.
724 * IETF mode uses a 96-bit random nonce with a 32-bit counter.
725 *
726 * @param string $ciphertext Encrypted message (with Poly1305 MAC appended)
727 * @param string $assocData Authenticated Associated Data (unencrypted)
728 * @param string $nonce Number to be used only Once; must be 8 bytes
729 * @param string $key Encryption key
730 *
731 * @return string The original plaintext message
732 * @throws SodiumException
733 * @throws TypeError
734 * @psalm-suppress MixedArgument
735 * @psalm-suppress MixedInferredReturnType
736 * @psalm-suppress MixedReturnStatement
737 */
738 public static function crypto_aead_chacha20poly1305_decrypt(
739 $ciphertext = '',
740 $assocData = '',
741 $nonce = '',
742 #[\SensitiveParameter]
743 $key = ''
744 ) {
745 /* Type checks: */
746 ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
747 ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
748 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
749 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
750
751 /* Input validation: */
752 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES) {
753 throw new SodiumException('Nonce must be CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES long');
754 }
755 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES) {
756 throw new SodiumException('Key must be CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES long');
757 }
758 if (ParagonIE_Sodium_Core_Util::strlen($ciphertext) < self::CRYPTO_AEAD_CHACHA20POLY1305_ABYTES) {
759 throw new SodiumException('Message must be at least CRYPTO_AEAD_CHACHA20POLY1305_ABYTES long');
760 }
761
762 if (self::useNewSodiumAPI()) {
763 /**
764 * @psalm-suppress InvalidReturnStatement
765 * @psalm-suppress FalsableReturnStatement
766 */
767 return sodium_crypto_aead_chacha20poly1305_decrypt(
768 $ciphertext,
769 $assocData,
770 $nonce,
771 $key
772 );
773 }
774 if (self::use_fallback('crypto_aead_chacha20poly1305_decrypt')) {
775 return call_user_func(
776 '\\Sodium\\crypto_aead_chacha20poly1305_decrypt',
777 $ciphertext,
778 $assocData,
779 $nonce,
780 $key
781 );
782 }
783 if (PHP_INT_SIZE === 4) {
784 return ParagonIE_Sodium_Crypto32::aead_chacha20poly1305_decrypt(
785 $ciphertext,
786 $assocData,
787 $nonce,
788 $key
789 );
790 }
791 return ParagonIE_Sodium_Crypto::aead_chacha20poly1305_decrypt(
792 $ciphertext,
793 $assocData,
794 $nonce,
795 $key
796 );
797 }
798
799 /**
800 * Authenticated Encryption with Associated Data
801 *
802 * Algorithm:
803 * ChaCha20-Poly1305
804 *
805 * This mode uses a 64-bit random nonce with a 64-bit counter.
806 * IETF mode uses a 96-bit random nonce with a 32-bit counter.
807 *
808 * @param string $plaintext Message to be encrypted
809 * @param string $assocData Authenticated Associated Data (unencrypted)
810 * @param string $nonce Number to be used only Once; must be 8 bytes
811 * @param string $key Encryption key
812 *
813 * @return string Ciphertext with a 16-byte Poly1305 message
814 * authentication code appended
815 * @throws SodiumException
816 * @throws TypeError
817 * @psalm-suppress MixedArgument
818 */
819 public static function crypto_aead_chacha20poly1305_encrypt(
820 #[\SensitiveParameter]
821 $plaintext = '',
822 $assocData = '',
823 $nonce = '',
824 #[\SensitiveParameter]
825 $key = ''
826 ) {
827 /* Type checks: */
828 ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
829 ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
830 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
831 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
832
833 /* Input validation: */
834 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES) {
835 throw new SodiumException('Nonce must be CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES long');
836 }
837 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES) {
838 throw new SodiumException('Key must be CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES long');
839 }
840
841 if (self::useNewSodiumAPI()) {
842 return (string) sodium_crypto_aead_chacha20poly1305_encrypt(
843 $plaintext,
844 $assocData,
845 $nonce,
846 $key
847 );
848 }
849 if (self::use_fallback('crypto_aead_chacha20poly1305_encrypt')) {
850 return (string) call_user_func(
851 '\\Sodium\\crypto_aead_chacha20poly1305_encrypt',
852 $plaintext,
853 $assocData,
854 $nonce,
855 $key
856 );
857 }
858 if (PHP_INT_SIZE === 4) {
859 return ParagonIE_Sodium_Crypto32::aead_chacha20poly1305_encrypt(
860 $plaintext,
861 $assocData,
862 $nonce,
863 $key
864 );
865 }
866 return ParagonIE_Sodium_Crypto::aead_chacha20poly1305_encrypt(
867 $plaintext,
868 $assocData,
869 $nonce,
870 $key
871 );
872 }
873
874 /**
875 * Authenticated Encryption with Associated Data: Decryption
876 *
877 * Algorithm:
878 * ChaCha20-Poly1305
879 *
880 * IETF mode uses a 96-bit random nonce with a 32-bit counter.
881 * Regular mode uses a 64-bit random nonce with a 64-bit counter.
882 *
883 * @param string $ciphertext Encrypted message (with Poly1305 MAC appended)
884 * @param string $assocData Authenticated Associated Data (unencrypted)
885 * @param string $nonce Number to be used only Once; must be 12 bytes
886 * @param string $key Encryption key
887 *
888 * @return string The original plaintext message
889 * @throws SodiumException
890 * @throws TypeError
891 * @psalm-suppress MixedArgument
892 * @psalm-suppress MixedInferredReturnType
893 * @psalm-suppress MixedReturnStatement
894 */
895 public static function crypto_aead_chacha20poly1305_ietf_decrypt(
896 $ciphertext = '',
897 $assocData = '',
898 $nonce = '',
899 #[\SensitiveParameter]
900 $key = ''
901 ) {
902 /* Type checks: */
903 ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
904 ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
905 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
906 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
907
908 /* Input validation: */
909 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES) {
910 throw new SodiumException('Nonce must be CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES long');
911 }
912 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES) {
913 throw new SodiumException('Key must be CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES long');
914 }
915 if (ParagonIE_Sodium_Core_Util::strlen($ciphertext) < self::CRYPTO_AEAD_CHACHA20POLY1305_ABYTES) {
916 throw new SodiumException('Message must be at least CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES long');
917 }
918
919 if (self::useNewSodiumAPI()) {
920 /**
921 * @psalm-suppress InvalidReturnStatement
922 * @psalm-suppress FalsableReturnStatement
923 */
924 return sodium_crypto_aead_chacha20poly1305_ietf_decrypt(
925 $ciphertext,
926 $assocData,
927 $nonce,
928 $key
929 );
930 }
931 if (self::use_fallback('crypto_aead_chacha20poly1305_ietf_decrypt')) {
932 return call_user_func(
933 '\\Sodium\\crypto_aead_chacha20poly1305_ietf_decrypt',
934 $ciphertext,
935 $assocData,
936 $nonce,
937 $key
938 );
939 }
940 if (PHP_INT_SIZE === 4) {
941 return ParagonIE_Sodium_Crypto32::aead_chacha20poly1305_ietf_decrypt(
942 $ciphertext,
943 $assocData,
944 $nonce,
945 $key
946 );
947 }
948 return ParagonIE_Sodium_Crypto::aead_chacha20poly1305_ietf_decrypt(
949 $ciphertext,
950 $assocData,
951 $nonce,
952 $key
953 );
954 }
955
956 /**
957 * Return a secure random key for use with the ChaCha20-Poly1305
958 * symmetric AEAD interface.
959 *
960 * @return string
961 * @throws Exception
962 * @throws Error
963 */
964 public static function crypto_aead_chacha20poly1305_keygen()
965 {
966 return random_bytes(self::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES);
967 }
968
969 /**
970 * Authenticated Encryption with Associated Data
971 *
972 * Algorithm:
973 * ChaCha20-Poly1305
974 *
975 * IETF mode uses a 96-bit random nonce with a 32-bit counter.
976 * Regular mode uses a 64-bit random nonce with a 64-bit counter.
977 *
978 * @param string $plaintext Message to be encrypted
979 * @param string $assocData Authenticated Associated Data (unencrypted)
980 * @param string $nonce Number to be used only Once; must be 8 bytes
981 * @param string $key Encryption key
982 *
983 * @return string Ciphertext with a 16-byte Poly1305 message
984 * authentication code appended
985 * @throws SodiumException
986 * @throws TypeError
987 * @psalm-suppress MixedArgument
988 */
989 public static function crypto_aead_chacha20poly1305_ietf_encrypt(
990 #[\SensitiveParameter]
991 $plaintext = '',
992 $assocData = '',
993 $nonce = '',
994 #[\SensitiveParameter]
995 $key = ''
996 ) {
997 /* Type checks: */
998 ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
999 if (!is_null($assocData)) {
1000 ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
1001 }
1002 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
1003 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
1004
1005 /* Input validation: */
1006 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES) {
1007 throw new SodiumException('Nonce must be CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES long');
1008 }
1009 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES) {
1010 throw new SodiumException('Key must be CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES long');
1011 }
1012
1013 if (self::useNewSodiumAPI()) {
1014 return (string) sodium_crypto_aead_chacha20poly1305_ietf_encrypt(
1015 $plaintext,
1016 $assocData,
1017 $nonce,
1018 $key
1019 );
1020 }
1021 if (self::use_fallback('crypto_aead_chacha20poly1305_ietf_encrypt')) {
1022 return (string) call_user_func(
1023 '\\Sodium\\crypto_aead_chacha20poly1305_ietf_encrypt',
1024 $plaintext,
1025 $assocData,
1026 $nonce,
1027 $key
1028 );
1029 }
1030 if (PHP_INT_SIZE === 4) {
1031 return ParagonIE_Sodium_Crypto32::aead_chacha20poly1305_ietf_encrypt(
1032 $plaintext,
1033 $assocData,
1034 $nonce,
1035 $key
1036 );
1037 }
1038 return ParagonIE_Sodium_Crypto::aead_chacha20poly1305_ietf_encrypt(
1039 $plaintext,
1040 $assocData,
1041 $nonce,
1042 $key
1043 );
1044 }
1045
1046 /**
1047 * Return a secure random key for use with the ChaCha20-Poly1305
1048 * symmetric AEAD interface. (IETF version)
1049 *
1050 * @return string
1051 * @throws Exception
1052 * @throws Error
1053 */
1054 public static function crypto_aead_chacha20poly1305_ietf_keygen()
1055 {
1056 return random_bytes(self::CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES);
1057 }
1058
1059 /**
1060 * Authenticated Encryption with Associated Data: Decryption
1061 *
1062 * Algorithm:
1063 * XChaCha20-Poly1305
1064 *
1065 * This mode uses a 64-bit random nonce with a 64-bit counter.
1066 * IETF mode uses a 96-bit random nonce with a 32-bit counter.
1067 *
1068 * @param string $ciphertext Encrypted message (with Poly1305 MAC appended)
1069 * @param string $assocData Authenticated Associated Data (unencrypted)
1070 * @param string $nonce Number to be used only Once; must be 8 bytes
1071 * @param string $key Encryption key
1072 * @param bool $dontFallback Don't fallback to ext/sodium
1073 *
1074 * @return string|bool The original plaintext message
1075 * @throws SodiumException
1076 * @throws TypeError
1077 * @psalm-suppress MixedArgument
1078 */
1079 public static function crypto_aead_xchacha20poly1305_ietf_decrypt(
1080 $ciphertext = '',
1081 $assocData = '',
1082 $nonce = '',
1083 #[\SensitiveParameter]
1084 $key = '',
1085 $dontFallback = false
1086 ) {
1087 /* Type checks: */
1088 ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
1089 if (!is_null($assocData)) {
1090 ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
1091 } else {
1092 $assocData = '';
1093 }
1094 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
1095 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
1096
1097 /* Input validation: */
1098 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES) {
1099 throw new SodiumException('Nonce must be CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES long');
1100 }
1101 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES) {
1102 throw new SodiumException('Key must be CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES long');
1103 }
1104 if (ParagonIE_Sodium_Core_Util::strlen($ciphertext) < self::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES) {
1105 throw new SodiumException('Message must be at least CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES long');
1106 }
1107 if (self::useNewSodiumAPI() && !$dontFallback) {
1108 if (is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_decrypt')) {
1109 return sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
1110 $ciphertext,
1111 $assocData,
1112 $nonce,
1113 $key
1114 );
1115 }
1116 }
1117
1118 if (PHP_INT_SIZE === 4) {
1119 return ParagonIE_Sodium_Crypto32::aead_xchacha20poly1305_ietf_decrypt(
1120 $ciphertext,
1121 $assocData,
1122 $nonce,
1123 $key
1124 );
1125 }
1126 return ParagonIE_Sodium_Crypto::aead_xchacha20poly1305_ietf_decrypt(
1127 $ciphertext,
1128 $assocData,
1129 $nonce,
1130 $key
1131 );
1132 }
1133
1134 /**
1135 * Authenticated Encryption with Associated Data
1136 *
1137 * Algorithm:
1138 * XChaCha20-Poly1305
1139 *
1140 * This mode uses a 64-bit random nonce with a 64-bit counter.
1141 * IETF mode uses a 96-bit random nonce with a 32-bit counter.
1142 *
1143 * @param string $plaintext Message to be encrypted
1144 * @param string $assocData Authenticated Associated Data (unencrypted)
1145 * @param string $nonce Number to be used only Once; must be 8 bytes
1146 * @param string $key Encryption key
1147 * @param bool $dontFallback Don't fallback to ext/sodium
1148 *
1149 * @return string Ciphertext with a 16-byte Poly1305 message
1150 * authentication code appended
1151 * @throws SodiumException
1152 * @throws TypeError
1153 * @psalm-suppress MixedArgument
1154 */
1155 public static function crypto_aead_xchacha20poly1305_ietf_encrypt(
1156 #[\SensitiveParameter]
1157 $plaintext = '',
1158 $assocData = '',
1159 $nonce = '',
1160 #[\SensitiveParameter]
1161 $key = '',
1162 $dontFallback = false
1163 ) {
1164 /* Type checks: */
1165 ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
1166 if (!is_null($assocData)) {
1167 ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
1168 } else {
1169 $assocData = '';
1170 }
1171 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
1172 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
1173
1174 /* Input validation: */
1175 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES) {
1176 throw new SodiumException('Nonce must be CRYPTO_AEAD_XCHACHA20POLY1305_NPUBBYTES long');
1177 }
1178 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES) {
1179 throw new SodiumException('Key must be CRYPTO_AEAD_XCHACHA20POLY1305_KEYBYTES long');
1180 }
1181 if (self::useNewSodiumAPI() && !$dontFallback) {
1182 if (is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
1183 return sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
1184 $plaintext,
1185 $assocData,
1186 $nonce,
1187 $key
1188 );
1189 }
1190 }
1191
1192 if (PHP_INT_SIZE === 4) {
1193 return ParagonIE_Sodium_Crypto32::aead_xchacha20poly1305_ietf_encrypt(
1194 $plaintext,
1195 $assocData,
1196 $nonce,
1197 $key
1198 );
1199 }
1200 return ParagonIE_Sodium_Crypto::aead_xchacha20poly1305_ietf_encrypt(
1201 $plaintext,
1202 $assocData,
1203 $nonce,
1204 $key
1205 );
1206 }
1207
1208 /**
1209 * Return a secure random key for use with the XChaCha20-Poly1305
1210 * symmetric AEAD interface.
1211 *
1212 * @return string
1213 * @throws Exception
1214 * @throws Error
1215 */
1216 public static function crypto_aead_xchacha20poly1305_ietf_keygen()
1217 {
1218 return random_bytes(self::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES);
1219 }
1220
1221 /**
1222 * Authenticate a message. Uses symmetric-key cryptography.
1223 *
1224 * Algorithm:
1225 * HMAC-SHA512-256. Which is HMAC-SHA-512 truncated to 256 bits.
1226 * Not to be confused with HMAC-SHA-512/256 which would use the
1227 * SHA-512/256 hash function (uses different initial parameters
1228 * but still truncates to 256 bits to sidestep length-extension
1229 * attacks).
1230 *
1231 * @param string $message Message to be authenticated
1232 * @param string $key Symmetric authentication key
1233 * @return string Message authentication code
1234 * @throws SodiumException
1235 * @throws TypeError
1236 * @psalm-suppress MixedArgument
1237 */
1238 public static function crypto_auth(
1239 $message,
1240 #[\SensitiveParameter]
1241 $key
1242 ) {
1243 /* Type checks: */
1244 ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
1245 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 2);
1246
1247 /* Input validation: */
1248 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AUTH_KEYBYTES) {
1249 throw new SodiumException('Argument 2 must be CRYPTO_AUTH_KEYBYTES long.');
1250 }
1251
1252 if (self::useNewSodiumAPI()) {
1253 return (string) sodium_crypto_auth($message, $key);
1254 }
1255 if (self::use_fallback('crypto_auth')) {
1256 return (string) call_user_func('\\Sodium\\crypto_auth', $message, $key);
1257 }
1258 if (PHP_INT_SIZE === 4) {
1259 return ParagonIE_Sodium_Crypto32::auth($message, $key);
1260 }
1261 return ParagonIE_Sodium_Crypto::auth($message, $key);
1262 }
1263
1264 /**
1265 * @return string
1266 * @throws Exception
1267 * @throws Error
1268 */
1269 public static function crypto_auth_keygen()
1270 {
1271 return random_bytes(self::CRYPTO_AUTH_KEYBYTES);
1272 }
1273
1274 /**
1275 * Verify the MAC of a message previously authenticated with crypto_auth.
1276 *
1277 * @param string $mac Message authentication code
1278 * @param string $message Message whose authenticity you are attempting to
1279 * verify (with a given MAC and key)
1280 * @param string $key Symmetric authentication key
1281 * @return bool TRUE if authenticated, FALSE otherwise
1282 * @throws SodiumException
1283 * @throws TypeError
1284 * @psalm-suppress MixedArgument
1285 */
1286 public static function crypto_auth_verify(
1287 $mac,
1288 $message,
1289 #[\SensitiveParameter]
1290 $key
1291 ) {
1292 /* Type checks: */
1293 ParagonIE_Sodium_Core_Util::declareScalarType($mac, 'string', 1);
1294 ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 2);
1295 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);
1296
1297 /* Input validation: */
1298 if (ParagonIE_Sodium_Core_Util::strlen($mac) !== self::CRYPTO_AUTH_BYTES) {
1299 throw new SodiumException('Argument 1 must be CRYPTO_AUTH_BYTES long.');
1300 }
1301 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AUTH_KEYBYTES) {
1302 throw new SodiumException('Argument 3 must be CRYPTO_AUTH_KEYBYTES long.');
1303 }
1304
1305 if (self::useNewSodiumAPI()) {
1306 return (bool) sodium_crypto_auth_verify($mac, $message, $key);
1307 }
1308 if (self::use_fallback('crypto_auth_verify')) {
1309 return (bool) call_user_func('\\Sodium\\crypto_auth_verify', $mac, $message, $key);
1310 }
1311 if (PHP_INT_SIZE === 4) {
1312 return ParagonIE_Sodium_Crypto32::auth_verify($mac, $message, $key);
1313 }
1314 return ParagonIE_Sodium_Crypto::auth_verify($mac, $message, $key);
1315 }
1316
1317 /**
1318 * Authenticated asymmetric-key encryption. Both the sender and recipient
1319 * may decrypt messages.
1320 *
1321 * Algorithm: X25519-XSalsa20-Poly1305.
1322 * X25519: Elliptic-Curve Diffie Hellman over Curve25519.
1323 * XSalsa20: Extended-nonce variant of salsa20.
1324 * Poyl1305: Polynomial MAC for one-time message authentication.
1325 *
1326 * @param string $plaintext The message to be encrypted
1327 * @param string $nonce A Number to only be used Once; must be 24 bytes
1328 * @param string $keypair Your secret key and your recipient's public key
1329 * @return string Ciphertext with 16-byte Poly1305 MAC
1330 * @throws SodiumException
1331 * @throws TypeError
1332 * @psalm-suppress MixedArgument
1333 */
1334 public static function crypto_box(
1335 $plaintext,
1336 $nonce,
1337 #[\SensitiveParameter]
1338 $keypair
1339 ) {
1340 /* Type checks: */
1341 ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
1342 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
1343 ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 3);
1344
1345 /* Input validation: */
1346 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_BOX_NONCEBYTES) {
1347 throw new SodiumException('Argument 2 must be CRYPTO_BOX_NONCEBYTES long.');
1348 }
1349 if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_BOX_KEYPAIRBYTES) {
1350 throw new SodiumException('Argument 3 must be CRYPTO_BOX_KEYPAIRBYTES long.');
1351 }
1352
1353 if (self::useNewSodiumAPI()) {
1354 return (string) sodium_crypto_box($plaintext, $nonce, $keypair);
1355 }
1356 if (self::use_fallback('crypto_box')) {
1357 return (string) call_user_func('\\Sodium\\crypto_box', $plaintext, $nonce, $keypair);
1358 }
1359 if (PHP_INT_SIZE === 4) {
1360 return ParagonIE_Sodium_Crypto32::box($plaintext, $nonce, $keypair);
1361 }
1362 return ParagonIE_Sodium_Crypto::box($plaintext, $nonce, $keypair);
1363 }
1364
1365 /**
1366 * Anonymous public-key encryption. Only the recipient may decrypt messages.
1367 *
1368 * Algorithm: X25519-XSalsa20-Poly1305, as with crypto_box.
1369 * The sender's X25519 keypair is ephemeral.
1370 * Nonce is generated from the BLAKE2b hash of both public keys.
1371 *
1372 * This provides ciphertext integrity.
1373 *
1374 * @param string $plaintext Message to be sealed
1375 * @param string $publicKey Your recipient's public key
1376 * @return string Sealed message that only your recipient can
1377 * decrypt
1378 * @throws SodiumException
1379 * @throws TypeError
1380 * @psalm-suppress MixedArgument
1381 */
1382 public static function crypto_box_seal(
1383 #[\SensitiveParameter]
1384 $plaintext,
1385 $publicKey
1386 ) {
1387 /* Type checks: */
1388 ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
1389 ParagonIE_Sodium_Core_Util::declareScalarType($publicKey, 'string', 2);
1390
1391 /* Input validation: */
1392 if (ParagonIE_Sodium_Core_Util::strlen($publicKey) !== self::CRYPTO_BOX_PUBLICKEYBYTES) {
1393 throw new SodiumException('Argument 2 must be CRYPTO_BOX_PUBLICKEYBYTES long.');
1394 }
1395
1396 if (self::useNewSodiumAPI()) {
1397 return (string) sodium_crypto_box_seal($plaintext, $publicKey);
1398 }
1399 if (self::use_fallback('crypto_box_seal')) {
1400 return (string) call_user_func('\\Sodium\\crypto_box_seal', $plaintext, $publicKey);
1401 }
1402 if (PHP_INT_SIZE === 4) {
1403 return ParagonIE_Sodium_Crypto32::box_seal($plaintext, $publicKey);
1404 }
1405 return ParagonIE_Sodium_Crypto::box_seal($plaintext, $publicKey);
1406 }
1407
1408 /**
1409 * Opens a message encrypted with crypto_box_seal(). Requires
1410 * the recipient's keypair (sk || pk) to decrypt successfully.
1411 *
1412 * This validates ciphertext integrity.
1413 *
1414 * @param string $ciphertext Sealed message to be opened
1415 * @param string $keypair Your crypto_box keypair
1416 * @return string The original plaintext message
1417 * @throws SodiumException
1418 * @throws TypeError
1419 * @psalm-suppress MixedArgument
1420 * @psalm-suppress MixedInferredReturnType
1421 * @psalm-suppress MixedReturnStatement
1422 */
1423 public static function crypto_box_seal_open(
1424 $ciphertext,
1425 #[\SensitiveParameter]
1426 $keypair
1427 ) {
1428 /* Type checks: */
1429 ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
1430 ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 2);
1431
1432 /* Input validation: */
1433 if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_BOX_KEYPAIRBYTES) {
1434 throw new SodiumException('Argument 2 must be CRYPTO_BOX_KEYPAIRBYTES long.');
1435 }
1436
1437 if (self::useNewSodiumAPI()) {
1438 /**
1439 * @psalm-suppress InvalidReturnStatement
1440 * @psalm-suppress FalsableReturnStatement
1441 */
1442 return sodium_crypto_box_seal_open($ciphertext, $keypair);
1443 }
1444 if (self::use_fallback('crypto_box_seal_open')) {
1445 return call_user_func('\\Sodium\\crypto_box_seal_open', $ciphertext, $keypair);
1446 }
1447 if (PHP_INT_SIZE === 4) {
1448 return ParagonIE_Sodium_Crypto32::box_seal_open($ciphertext, $keypair);
1449 }
1450 return ParagonIE_Sodium_Crypto::box_seal_open($ciphertext, $keypair);
1451 }
1452
1453 /**
1454 * Generate a new random X25519 keypair.
1455 *
1456 * @return string A 64-byte string; the first 32 are your secret key, while
1457 * the last 32 are your public key. crypto_box_secretkey()
1458 * and crypto_box_publickey() exist to separate them so you
1459 * don't accidentally get them mixed up!
1460 * @throws SodiumException
1461 * @throws TypeError
1462 * @psalm-suppress MixedArgument
1463 */
1464 public static function crypto_box_keypair()
1465 {
1466 if (self::useNewSodiumAPI()) {
1467 return (string) sodium_crypto_box_keypair();
1468 }
1469 if (self::use_fallback('crypto_box_keypair')) {
1470 return (string) call_user_func('\\Sodium\\crypto_box_keypair');
1471 }
1472 if (PHP_INT_SIZE === 4) {
1473 return ParagonIE_Sodium_Crypto32::box_keypair();
1474 }
1475 return ParagonIE_Sodium_Crypto::box_keypair();
1476 }
1477
1478 /**
1479 * Combine two keys into a keypair for use in library methods that expect
1480 * a keypair. This doesn't necessarily have to be the same person's keys.
1481 *
1482 * @param string $secretKey Secret key
1483 * @param string $publicKey Public key
1484 * @return string Keypair
1485 * @throws SodiumException
1486 * @throws TypeError
1487 * @psalm-suppress MixedArgument
1488 */
1489 public static function crypto_box_keypair_from_secretkey_and_publickey(
1490 #[\SensitiveParameter]
1491 $secretKey,
1492 $publicKey
1493 ) {
1494 /* Type checks: */
1495 ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 1);
1496 ParagonIE_Sodium_Core_Util::declareScalarType($publicKey, 'string', 2);
1497
1498 /* Input validation: */
1499 if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_BOX_SECRETKEYBYTES) {
1500 throw new SodiumException('Argument 1 must be CRYPTO_BOX_SECRETKEYBYTES long.');
1501 }
1502 if (ParagonIE_Sodium_Core_Util::strlen($publicKey) !== self::CRYPTO_BOX_PUBLICKEYBYTES) {
1503 throw new SodiumException('Argument 2 must be CRYPTO_BOX_PUBLICKEYBYTES long.');
1504 }
1505
1506 if (self::useNewSodiumAPI()) {
1507 return (string) sodium_crypto_box_keypair_from_secretkey_and_publickey($secretKey, $publicKey);
1508 }
1509 if (self::use_fallback('crypto_box_keypair_from_secretkey_and_publickey')) {
1510 return (string) call_user_func('\\Sodium\\crypto_box_keypair_from_secretkey_and_publickey', $secretKey, $publicKey);
1511 }
1512 if (PHP_INT_SIZE === 4) {
1513 return ParagonIE_Sodium_Crypto32::box_keypair_from_secretkey_and_publickey($secretKey, $publicKey);
1514 }
1515 return ParagonIE_Sodium_Crypto::box_keypair_from_secretkey_and_publickey($secretKey, $publicKey);
1516 }
1517
1518 /**
1519 * Decrypt a message previously encrypted with crypto_box().
1520 *
1521 * @param string $ciphertext Encrypted message
1522 * @param string $nonce Number to only be used Once; must be 24 bytes
1523 * @param string $keypair Your secret key and the sender's public key
1524 * @return string The original plaintext message
1525 * @throws SodiumException
1526 * @throws TypeError
1527 * @psalm-suppress MixedArgument
1528 * @psalm-suppress MixedInferredReturnType
1529 * @psalm-suppress MixedReturnStatement
1530 */
1531 public static function crypto_box_open(
1532 $ciphertext,
1533 $nonce,
1534 #[\SensitiveParameter]
1535 $keypair
1536 ) {
1537 /* Type checks: */
1538 ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
1539 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
1540 ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 3);
1541
1542 /* Input validation: */
1543 if (ParagonIE_Sodium_Core_Util::strlen($ciphertext) < self::CRYPTO_BOX_MACBYTES) {
1544 throw new SodiumException('Argument 1 must be at least CRYPTO_BOX_MACBYTES long.');
1545 }
1546 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_BOX_NONCEBYTES) {
1547 throw new SodiumException('Argument 2 must be CRYPTO_BOX_NONCEBYTES long.');
1548 }
1549 if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_BOX_KEYPAIRBYTES) {
1550 throw new SodiumException('Argument 3 must be CRYPTO_BOX_KEYPAIRBYTES long.');
1551 }
1552
1553 if (self::useNewSodiumAPI()) {
1554 /**
1555 * @psalm-suppress InvalidReturnStatement
1556 * @psalm-suppress FalsableReturnStatement
1557 */
1558 return sodium_crypto_box_open($ciphertext, $nonce, $keypair);
1559 }
1560 if (self::use_fallback('crypto_box_open')) {
1561 return call_user_func('\\Sodium\\crypto_box_open', $ciphertext, $nonce, $keypair);
1562 }
1563 if (PHP_INT_SIZE === 4) {
1564 return ParagonIE_Sodium_Crypto32::box_open($ciphertext, $nonce, $keypair);
1565 }
1566 return ParagonIE_Sodium_Crypto::box_open($ciphertext, $nonce, $keypair);
1567 }
1568
1569 /**
1570 * Extract the public key from a crypto_box keypair.
1571 *
1572 * @param string $keypair Keypair containing secret and public key
1573 * @return string Your crypto_box public key
1574 * @throws SodiumException
1575 * @throws TypeError
1576 * @psalm-suppress MixedArgument
1577 */
1578 public static function crypto_box_publickey(
1579 #[\SensitiveParameter]
1580 $keypair
1581 ) {
1582 /* Type checks: */
1583 ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 1);
1584
1585 /* Input validation: */
1586 if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_BOX_KEYPAIRBYTES) {
1587 throw new SodiumException('Argument 1 must be CRYPTO_BOX_KEYPAIRBYTES long.');
1588 }
1589
1590 if (self::useNewSodiumAPI()) {
1591 return (string) sodium_crypto_box_publickey($keypair);
1592 }
1593 if (self::use_fallback('crypto_box_publickey')) {
1594 return (string) call_user_func('\\Sodium\\crypto_box_publickey', $keypair);
1595 }
1596 if (PHP_INT_SIZE === 4) {
1597 return ParagonIE_Sodium_Crypto32::box_publickey($keypair);
1598 }
1599 return ParagonIE_Sodium_Crypto::box_publickey($keypair);
1600 }
1601
1602 /**
1603 * Calculate the X25519 public key from a given X25519 secret key.
1604 *
1605 * @param string $secretKey Any X25519 secret key
1606 * @return string The corresponding X25519 public key
1607 * @throws SodiumException
1608 * @throws TypeError
1609 * @psalm-suppress MixedArgument
1610 */
1611 public static function crypto_box_publickey_from_secretkey(
1612 #[\SensitiveParameter]
1613 $secretKey
1614 ) {
1615 /* Type checks: */
1616 ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 1);
1617
1618 /* Input validation: */
1619 if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_BOX_SECRETKEYBYTES) {
1620 throw new SodiumException('Argument 1 must be CRYPTO_BOX_SECRETKEYBYTES long.');
1621 }
1622
1623 if (self::useNewSodiumAPI()) {
1624 return (string) sodium_crypto_box_publickey_from_secretkey($secretKey);
1625 }
1626 if (self::use_fallback('crypto_box_publickey_from_secretkey')) {
1627 return (string) call_user_func('\\Sodium\\crypto_box_publickey_from_secretkey', $secretKey);
1628 }
1629 if (PHP_INT_SIZE === 4) {
1630 return ParagonIE_Sodium_Crypto32::box_publickey_from_secretkey($secretKey);
1631 }
1632 return ParagonIE_Sodium_Crypto::box_publickey_from_secretkey($secretKey);
1633 }
1634
1635 /**
1636 * Extract the secret key from a crypto_box keypair.
1637 *
1638 * @param string $keypair
1639 * @return string Your crypto_box secret key
1640 * @throws SodiumException
1641 * @throws TypeError
1642 * @psalm-suppress MixedArgument
1643 */
1644 public static function crypto_box_secretkey(
1645 #[\SensitiveParameter]
1646 $keypair
1647 ) {
1648 /* Type checks: */
1649 ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 1);
1650
1651 /* Input validation: */
1652 if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_BOX_KEYPAIRBYTES) {
1653 throw new SodiumException('Argument 1 must be CRYPTO_BOX_KEYPAIRBYTES long.');
1654 }
1655
1656 if (self::useNewSodiumAPI()) {
1657 return (string) sodium_crypto_box_secretkey($keypair);
1658 }
1659 if (self::use_fallback('crypto_box_secretkey')) {
1660 return (string) call_user_func('\\Sodium\\crypto_box_secretkey', $keypair);
1661 }
1662 if (PHP_INT_SIZE === 4) {
1663 return ParagonIE_Sodium_Crypto32::box_secretkey($keypair);
1664 }
1665 return ParagonIE_Sodium_Crypto::box_secretkey($keypair);
1666 }
1667
1668 /**
1669 * Generate an X25519 keypair from a seed.
1670 *
1671 * @param string $seed
1672 * @return string
1673 * @throws SodiumException
1674 * @throws TypeError
1675 * @psalm-suppress MixedArgument
1676 * @psalm-suppress UndefinedFunction
1677 */
1678 public static function crypto_box_seed_keypair(
1679 #[\SensitiveParameter]
1680 $seed
1681 ) {
1682 /* Type checks: */
1683 ParagonIE_Sodium_Core_Util::declareScalarType($seed, 'string', 1);
1684
1685 if (self::useNewSodiumAPI()) {
1686 return (string) sodium_crypto_box_seed_keypair($seed);
1687 }
1688 if (self::use_fallback('crypto_box_seed_keypair')) {
1689 return (string) call_user_func('\\Sodium\\crypto_box_seed_keypair', $seed);
1690 }
1691 if (PHP_INT_SIZE === 4) {
1692 return ParagonIE_Sodium_Crypto32::box_seed_keypair($seed);
1693 }
1694 return ParagonIE_Sodium_Crypto::box_seed_keypair($seed);
1695 }
1696
1697 /**
1698 * Calculates a BLAKE2b hash, with an optional key.
1699 *
1700 * @param string $message The message to be hashed
1701 * @param string|null $key If specified, must be a string between 16
1702 * and 64 bytes long
1703 * @param int $length Output length in bytes; must be between 16
1704 * and 64 (default = 32)
1705 * @return string Raw binary
1706 * @throws SodiumException
1707 * @throws TypeError
1708 * @psalm-suppress MixedArgument
1709 */
1710 public static function crypto_generichash(
1711 $message,
1712 #[\SensitiveParameter]
1713 $key = '',
1714 $length = self::CRYPTO_GENERICHASH_BYTES
1715 ) {
1716 /* Type checks: */
1717 ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
1718 if (is_null($key)) {
1719 $key = '';
1720 }
1721 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 2);
1722 ParagonIE_Sodium_Core_Util::declareScalarType($length, 'int', 3);
1723
1724 /* Input validation: */
1725 if (!empty($key)) {
1726 if (ParagonIE_Sodium_Core_Util::strlen($key) < self::CRYPTO_GENERICHASH_KEYBYTES_MIN) {
1727 throw new SodiumException('Unsupported key size. Must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes long.');
1728 }
1729 if (ParagonIE_Sodium_Core_Util::strlen($key) > self::CRYPTO_GENERICHASH_KEYBYTES_MAX) {
1730 throw new SodiumException('Unsupported key size. Must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes long.');
1731 }
1732 }
1733
1734 if (self::useNewSodiumAPI()) {
1735 return (string) sodium_crypto_generichash($message, $key, $length);
1736 }
1737 if (self::use_fallback('crypto_generichash')) {
1738 return (string) call_user_func('\\Sodium\\crypto_generichash', $message, $key, $length);
1739 }
1740 if (PHP_INT_SIZE === 4) {
1741 return ParagonIE_Sodium_Crypto32::generichash($message, $key, $length);
1742 }
1743 return ParagonIE_Sodium_Crypto::generichash($message, $key, $length);
1744 }
1745
1746 /**
1747 * Get the final BLAKE2b hash output for a given context.
1748 *
1749 * @param string $ctx BLAKE2 hashing context. Generated by crypto_generichash_init().
1750 * @param int $length Hash output size.
1751 * @return string Final BLAKE2b hash.
1752 * @throws SodiumException
1753 * @throws TypeError
1754 * @psalm-suppress MixedArgument
1755 * @psalm-suppress ReferenceConstraintViolation
1756 * @psalm-suppress ConflictingReferenceConstraint
1757 */
1758 public static function crypto_generichash_final(
1759 #[\SensitiveParameter]
1760 &$ctx,
1761 $length = self::CRYPTO_GENERICHASH_BYTES
1762 ) {
1763 /* Type checks: */
1764 ParagonIE_Sodium_Core_Util::declareScalarType($ctx, 'string', 1);
1765 ParagonIE_Sodium_Core_Util::declareScalarType($length, 'int', 2);
1766
1767 if (self::useNewSodiumAPI()) {
1768 return sodium_crypto_generichash_final($ctx, $length);
1769 }
1770 if (self::use_fallback('crypto_generichash_final')) {
1771 $func = '\\Sodium\\crypto_generichash_final';
1772 return (string) $func($ctx, $length);
1773 }
1774 if ($length < 1) {
1775 try {
1776 self::memzero($ctx);
1777 } catch (SodiumException $ex) {
1778 unset($ctx);
1779 }
1780 return '';
1781 }
1782 if (PHP_INT_SIZE === 4) {
1783 $result = ParagonIE_Sodium_Crypto32::generichash_final($ctx, $length);
1784 } else {
1785 $result = ParagonIE_Sodium_Crypto::generichash_final($ctx, $length);
1786 }
1787 try {
1788 self::memzero($ctx);
1789 } catch (SodiumException $ex) {
1790 unset($ctx);
1791 }
1792 return $result;
1793 }
1794
1795 /**
1796 * Initialize a BLAKE2b hashing context, for use in a streaming interface.
1797 *
1798 * @param string|null $key If specified must be a string between 16 and 64 bytes
1799 * @param int $length The size of the desired hash output
1800 * @return string A BLAKE2 hashing context, encoded as a string
1801 * (To be 100% compatible with ext/libsodium)
1802 * @throws SodiumException
1803 * @throws TypeError
1804 * @psalm-suppress MixedArgument
1805 */
1806 public static function crypto_generichash_init(
1807 #[\SensitiveParameter]
1808 $key = '',
1809 $length = self::CRYPTO_GENERICHASH_BYTES
1810 ) {
1811 /* Type checks: */
1812 if (is_null($key)) {
1813 $key = '';
1814 }
1815 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 1);
1816 ParagonIE_Sodium_Core_Util::declareScalarType($length, 'int', 2);
1817
1818 /* Input validation: */
1819 if (!empty($key)) {
1820 if (ParagonIE_Sodium_Core_Util::strlen($key) < self::CRYPTO_GENERICHASH_KEYBYTES_MIN) {
1821 throw new SodiumException('Unsupported key size. Must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes long.');
1822 }
1823 if (ParagonIE_Sodium_Core_Util::strlen($key) > self::CRYPTO_GENERICHASH_KEYBYTES_MAX) {
1824 throw new SodiumException('Unsupported key size. Must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes long.');
1825 }
1826 }
1827
1828 if (self::useNewSodiumAPI()) {
1829 return sodium_crypto_generichash_init($key, $length);
1830 }
1831 if (self::use_fallback('crypto_generichash_init')) {
1832 return (string) call_user_func('\\Sodium\\crypto_generichash_init', $key, $length);
1833 }
1834 if (PHP_INT_SIZE === 4) {
1835 return ParagonIE_Sodium_Crypto32::generichash_init($key, $length);
1836 }
1837 return ParagonIE_Sodium_Crypto::generichash_init($key, $length);
1838 }
1839
1840 /**
1841 * Initialize a BLAKE2b hashing context, for use in a streaming interface.
1842 *
1843 * @param string|null $key If specified must be a string between 16 and 64 bytes
1844 * @param int $length The size of the desired hash output
1845 * @param string $salt Salt (up to 16 bytes)
1846 * @param string $personal Personalization string (up to 16 bytes)
1847 * @return string A BLAKE2 hashing context, encoded as a string
1848 * (To be 100% compatible with ext/libsodium)
1849 * @throws SodiumException
1850 * @throws TypeError
1851 * @psalm-suppress MixedArgument
1852 */
1853 public static function crypto_generichash_init_salt_personal(
1854 #[\SensitiveParameter]
1855 $key = '',
1856 $length = self::CRYPTO_GENERICHASH_BYTES,
1857 $salt = '',
1858 $personal = ''
1859 ) {
1860 /* Type checks: */
1861 if (is_null($key)) {
1862 $key = '';
1863 }
1864 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 1);
1865 ParagonIE_Sodium_Core_Util::declareScalarType($length, 'int', 2);
1866 ParagonIE_Sodium_Core_Util::declareScalarType($salt, 'string', 3);
1867 ParagonIE_Sodium_Core_Util::declareScalarType($personal, 'string', 4);
1868 $salt = str_pad($salt, 16, "\0", STR_PAD_RIGHT);
1869 $personal = str_pad($personal, 16, "\0", STR_PAD_RIGHT);
1870
1871 /* Input validation: */
1872 if (!empty($key)) {
1873 /*
1874 if (ParagonIE_Sodium_Core_Util::strlen($key) < self::CRYPTO_GENERICHASH_KEYBYTES_MIN) {
1875 throw new SodiumException('Unsupported key size. Must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes long.');
1876 }
1877 */
1878 if (ParagonIE_Sodium_Core_Util::strlen($key) > self::CRYPTO_GENERICHASH_KEYBYTES_MAX) {
1879 throw new SodiumException('Unsupported key size. Must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes long.');
1880 }
1881 }
1882 if (PHP_INT_SIZE === 4) {
1883 return ParagonIE_Sodium_Crypto32::generichash_init_salt_personal($key, $length, $salt, $personal);
1884 }
1885 return ParagonIE_Sodium_Crypto::generichash_init_salt_personal($key, $length, $salt, $personal);
1886 }
1887
1888 /**
1889 * Update a BLAKE2b hashing context with additional data.
1890 *
1891 * @param string $ctx BLAKE2 hashing context. Generated by crypto_generichash_init().
1892 * $ctx is passed by reference and gets updated in-place.
1893 * @param-out string $ctx
1894 * @param string $message The message to append to the existing hash state.
1895 * @return void
1896 * @throws SodiumException
1897 * @throws TypeError
1898 * @psalm-suppress MixedArgument
1899 * @psalm-suppress ReferenceConstraintViolation
1900 */
1901 public static function crypto_generichash_update(
1902 #[\SensitiveParameter]
1903 &$ctx,
1904 $message
1905 ) {
1906 /* Type checks: */
1907 ParagonIE_Sodium_Core_Util::declareScalarType($ctx, 'string', 1);
1908 ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 2);
1909
1910 if (self::useNewSodiumAPI()) {
1911 sodium_crypto_generichash_update($ctx, $message);
1912 return;
1913 }
1914 if (self::use_fallback('crypto_generichash_update')) {
1915 $func = '\\Sodium\\crypto_generichash_update';
1916 $func($ctx, $message);
1917 return;
1918 }
1919 if (PHP_INT_SIZE === 4) {
1920 $ctx = ParagonIE_Sodium_Crypto32::generichash_update($ctx, $message);
1921 } else {
1922 $ctx = ParagonIE_Sodium_Crypto::generichash_update($ctx, $message);
1923 }
1924 }
1925
1926 /**
1927 * @return string
1928 * @throws Exception
1929 * @throws Error
1930 */
1931 public static function crypto_generichash_keygen()
1932 {
1933 return random_bytes(self::CRYPTO_GENERICHASH_KEYBYTES);
1934 }
1935
1936 /**
1937 * @param int $subkey_len
1938 * @param int $subkey_id
1939 * @param string $context
1940 * @param string $key
1941 * @return string
1942 * @throws SodiumException
1943 */
1944 public static function crypto_kdf_derive_from_key(
1945 $subkey_len,
1946 $subkey_id,
1947 $context,
1948 #[\SensitiveParameter]
1949 $key
1950 ) {
1951 ParagonIE_Sodium_Core_Util::declareScalarType($subkey_len, 'int', 1);
1952 ParagonIE_Sodium_Core_Util::declareScalarType($subkey_id, 'int', 2);
1953 ParagonIE_Sodium_Core_Util::declareScalarType($context, 'string', 3);
1954 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
1955 $subkey_id = (int) $subkey_id;
1956 $subkey_len = (int) $subkey_len;
1957 $context = (string) $context;
1958 $key = (string) $key;
1959
1960 if ($subkey_len < self::CRYPTO_KDF_BYTES_MIN) {
1961 throw new SodiumException('subkey cannot be smaller than SODIUM_CRYPTO_KDF_BYTES_MIN');
1962 }
1963 if ($subkey_len > self::CRYPTO_KDF_BYTES_MAX) {
1964 throw new SodiumException('subkey cannot be larger than SODIUM_CRYPTO_KDF_BYTES_MAX');
1965 }
1966 if ($subkey_id < 0) {
1967 throw new SodiumException('subkey_id cannot be negative');
1968 }
1969 if (ParagonIE_Sodium_Core_Util::strlen($context) !== self::CRYPTO_KDF_CONTEXTBYTES) {
1970 throw new SodiumException('context should be SODIUM_CRYPTO_KDF_CONTEXTBYTES bytes');
1971 }
1972 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_KDF_KEYBYTES) {
1973 throw new SodiumException('key should be SODIUM_CRYPTO_KDF_KEYBYTES bytes');
1974 }
1975
1976 $salt = ParagonIE_Sodium_Core_Util::store64_le($subkey_id);
1977 $state = self::crypto_generichash_init_salt_personal(
1978 $key,
1979 $subkey_len,
1980 $salt,
1981 $context
1982 );
1983 return self::crypto_generichash_final($state, $subkey_len);
1984 }
1985
1986 /**
1987 * @return string
1988 * @throws Exception
1989 * @throws Error
1990 */
1991 public static function crypto_kdf_keygen()
1992 {
1993 return random_bytes(self::CRYPTO_KDF_KEYBYTES);
1994 }
1995
1996 /**
1997 * Perform a key exchange, between a designated client and a server.
1998 *
1999 * Typically, you would designate one machine to be the client and the
2000 * other to be the server. The first two keys are what you'd expect for
2001 * scalarmult() below, but the latter two public keys don't swap places.
2002 *
2003 * | ALICE | BOB |
2004 * | Client | Server |
2005 * |--------------------------------|-------------------------------------|
2006 * | shared = crypto_kx( | shared = crypto_kx( |
2007 * | alice_sk, | bob_sk, | <- contextual
2008 * | bob_pk, | alice_pk, | <- contextual
2009 * | alice_pk, | alice_pk, | <----- static
2010 * | bob_pk | bob_pk | <----- static
2011 * | ) | ) |
2012 *
2013 * They are used along with the scalarmult product to generate a 256-bit
2014 * BLAKE2b hash unique to the client and server keys.
2015 *
2016 * @param string $my_secret
2017 * @param string $their_public
2018 * @param string $client_public
2019 * @param string $server_public
2020 * @param bool $dontFallback
2021 * @return string
2022 * @throws SodiumException
2023 * @throws TypeError
2024 * @psalm-suppress MixedArgument
2025 */
2026 public static function crypto_kx(
2027 #[\SensitiveParameter]
2028 $my_secret,
2029 $their_public,
2030 $client_public,
2031 $server_public,
2032 $dontFallback = false
2033 ) {
2034 /* Type checks: */
2035 ParagonIE_Sodium_Core_Util::declareScalarType($my_secret, 'string', 1);
2036 ParagonIE_Sodium_Core_Util::declareScalarType($their_public, 'string', 2);
2037 ParagonIE_Sodium_Core_Util::declareScalarType($client_public, 'string', 3);
2038 ParagonIE_Sodium_Core_Util::declareScalarType($server_public, 'string', 4);
2039
2040 /* Input validation: */
2041 if (ParagonIE_Sodium_Core_Util::strlen($my_secret) !== self::CRYPTO_BOX_SECRETKEYBYTES) {
2042 throw new SodiumException('Argument 1 must be CRYPTO_BOX_SECRETKEYBYTES long.');
2043 }
2044 if (ParagonIE_Sodium_Core_Util::strlen($their_public) !== self::CRYPTO_BOX_PUBLICKEYBYTES) {
2045 throw new SodiumException('Argument 2 must be CRYPTO_BOX_PUBLICKEYBYTES long.');
2046 }
2047 if (ParagonIE_Sodium_Core_Util::strlen($client_public) !== self::CRYPTO_BOX_PUBLICKEYBYTES) {
2048 throw new SodiumException('Argument 3 must be CRYPTO_BOX_PUBLICKEYBYTES long.');
2049 }
2050 if (ParagonIE_Sodium_Core_Util::strlen($server_public) !== self::CRYPTO_BOX_PUBLICKEYBYTES) {
2051 throw new SodiumException('Argument 4 must be CRYPTO_BOX_PUBLICKEYBYTES long.');
2052 }
2053
2054 if (self::useNewSodiumAPI() && !$dontFallback) {
2055 if (is_callable('sodium_crypto_kx')) {
2056 return (string) sodium_crypto_kx(
2057 $my_secret,
2058 $their_public,
2059 $client_public,
2060 $server_public
2061 );
2062 }
2063 }
2064 if (self::use_fallback('crypto_kx')) {
2065 return (string) call_user_func(
2066 '\\Sodium\\crypto_kx',
2067 $my_secret,
2068 $their_public,
2069 $client_public,
2070 $server_public
2071 );
2072 }
2073 if (PHP_INT_SIZE === 4) {
2074 return ParagonIE_Sodium_Crypto32::keyExchange(
2075 $my_secret,
2076 $their_public,
2077 $client_public,
2078 $server_public
2079 );
2080 }
2081 return ParagonIE_Sodium_Crypto::keyExchange(
2082 $my_secret,
2083 $their_public,
2084 $client_public,
2085 $server_public
2086 );
2087 }
2088
2089 /**
2090 * @param string $seed
2091 * @return string
2092 * @throws SodiumException
2093 */
2094 public static function crypto_kx_seed_keypair(
2095 #[\SensitiveParameter]
2096 $seed
2097 ) {
2098 ParagonIE_Sodium_Core_Util::declareScalarType($seed, 'string', 1);
2099
2100 $seed = (string) $seed;
2101
2102 if (ParagonIE_Sodium_Core_Util::strlen($seed) !== self::CRYPTO_KX_SEEDBYTES) {
2103 throw new SodiumException('seed must be SODIUM_CRYPTO_KX_SEEDBYTES bytes');
2104 }
2105
2106 $sk = self::crypto_generichash($seed, '', self::CRYPTO_KX_SECRETKEYBYTES);
2107 $pk = self::crypto_scalarmult_base($sk);
2108 return $sk . $pk;
2109 }
2110
2111 /**
2112 * @return string
2113 * @throws Exception
2114 */
2115 public static function crypto_kx_keypair()
2116 {
2117 $sk = self::randombytes_buf(self::CRYPTO_KX_SECRETKEYBYTES);
2118 $pk = self::crypto_scalarmult_base($sk);
2119 return $sk . $pk;
2120 }
2121
2122 /**
2123 * @param string $keypair
2124 * @param string $serverPublicKey
2125 * @return array{0: string, 1: string}
2126 * @throws SodiumException
2127 */
2128 public static function crypto_kx_client_session_keys(
2129 #[\SensitiveParameter]
2130 $keypair,
2131 $serverPublicKey
2132 ) {
2133 ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 1);
2134 ParagonIE_Sodium_Core_Util::declareScalarType($serverPublicKey, 'string', 2);
2135
2136 $keypair = (string) $keypair;
2137 $serverPublicKey = (string) $serverPublicKey;
2138
2139 if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_KX_KEYPAIRBYTES) {
2140 throw new SodiumException('keypair should be SODIUM_CRYPTO_KX_KEYPAIRBYTES bytes');
2141 }
2142 if (ParagonIE_Sodium_Core_Util::strlen($serverPublicKey) !== self::CRYPTO_KX_PUBLICKEYBYTES) {
2143 throw new SodiumException('public keys must be SODIUM_CRYPTO_KX_PUBLICKEYBYTES bytes');
2144 }
2145
2146 $sk = self::crypto_kx_secretkey($keypair);
2147 $pk = self::crypto_kx_publickey($keypair);
2148 $h = self::crypto_generichash_init(null, self::CRYPTO_KX_SESSIONKEYBYTES * 2);
2149 self::crypto_generichash_update($h, self::crypto_scalarmult($sk, $serverPublicKey));
2150 self::crypto_generichash_update($h, $pk);
2151 self::crypto_generichash_update($h, $serverPublicKey);
2152 $sessionKeys = self::crypto_generichash_final($h, self::CRYPTO_KX_SESSIONKEYBYTES * 2);
2153 return array(
2154 ParagonIE_Sodium_Core_Util::substr(
2155 $sessionKeys,
2156 0,
2157 self::CRYPTO_KX_SESSIONKEYBYTES
2158 ),
2159 ParagonIE_Sodium_Core_Util::substr(
2160 $sessionKeys,
2161 self::CRYPTO_KX_SESSIONKEYBYTES,
2162 self::CRYPTO_KX_SESSIONKEYBYTES
2163 )
2164 );
2165 }
2166
2167 /**
2168 * @param string $keypair
2169 * @param string $clientPublicKey
2170 * @return array{0: string, 1: string}
2171 * @throws SodiumException
2172 */
2173 public static function crypto_kx_server_session_keys(
2174 #[\SensitiveParameter]
2175 $keypair,
2176 $clientPublicKey
2177 ) {
2178 ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 1);
2179 ParagonIE_Sodium_Core_Util::declareScalarType($clientPublicKey, 'string', 2);
2180
2181 $keypair = (string) $keypair;
2182 $clientPublicKey = (string) $clientPublicKey;
2183
2184 if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_KX_KEYPAIRBYTES) {
2185 throw new SodiumException('keypair should be SODIUM_CRYPTO_KX_KEYPAIRBYTES bytes');
2186 }
2187 if (ParagonIE_Sodium_Core_Util::strlen($clientPublicKey) !== self::CRYPTO_KX_PUBLICKEYBYTES) {
2188 throw new SodiumException('public keys must be SODIUM_CRYPTO_KX_PUBLICKEYBYTES bytes');
2189 }
2190
2191 $sk = self::crypto_kx_secretkey($keypair);
2192 $pk = self::crypto_kx_publickey($keypair);
2193 $h = self::crypto_generichash_init(null, self::CRYPTO_KX_SESSIONKEYBYTES * 2);
2194 self::crypto_generichash_update($h, self::crypto_scalarmult($sk, $clientPublicKey));
2195 self::crypto_generichash_update($h, $clientPublicKey);
2196 self::crypto_generichash_update($h, $pk);
2197 $sessionKeys = self::crypto_generichash_final($h, self::CRYPTO_KX_SESSIONKEYBYTES * 2);
2198 return array(
2199 ParagonIE_Sodium_Core_Util::substr(
2200 $sessionKeys,
2201 self::CRYPTO_KX_SESSIONKEYBYTES,
2202 self::CRYPTO_KX_SESSIONKEYBYTES
2203 ),
2204 ParagonIE_Sodium_Core_Util::substr(
2205 $sessionKeys,
2206 0,
2207 self::CRYPTO_KX_SESSIONKEYBYTES
2208 )
2209 );
2210 }
2211
2212 /**
2213 * @param string $kp
2214 * @return string
2215 * @throws SodiumException
2216 */
2217 public static function crypto_kx_secretkey(
2218 #[\SensitiveParameter]
2219 $kp
2220 ) {
2221 return ParagonIE_Sodium_Core_Util::substr(
2222 $kp,
2223 0,
2224 self::CRYPTO_KX_SECRETKEYBYTES
2225 );
2226 }
2227
2228 /**
2229 * @param string $kp
2230 * @return string
2231 * @throws SodiumException
2232 */
2233 public static function crypto_kx_publickey($kp)
2234 {
2235 return ParagonIE_Sodium_Core_Util::substr(
2236 $kp,
2237 self::CRYPTO_KX_SECRETKEYBYTES,
2238 self::CRYPTO_KX_PUBLICKEYBYTES
2239 );
2240 }
2241
2242 /**
2243 * @param int $outlen
2244 * @param string $passwd
2245 * @param string $salt
2246 * @param int $opslimit
2247 * @param int $memlimit
2248 * @param int|null $alg
2249 * @return string
2250 * @throws SodiumException
2251 * @throws TypeError
2252 * @psalm-suppress MixedArgument
2253 */
2254 public static function crypto_pwhash(
2255 $outlen,
2256 #[\SensitiveParameter]
2257 $passwd,
2258 $salt,
2259 $opslimit,
2260 $memlimit,
2261 $alg = null
2262 ) {
2263 ParagonIE_Sodium_Core_Util::declareScalarType($outlen, 'int', 1);
2264 ParagonIE_Sodium_Core_Util::declareScalarType($passwd, 'string', 2);
2265 ParagonIE_Sodium_Core_Util::declareScalarType($salt, 'string', 3);
2266 ParagonIE_Sodium_Core_Util::declareScalarType($opslimit, 'int', 4);
2267 ParagonIE_Sodium_Core_Util::declareScalarType($memlimit, 'int', 5);
2268
2269 if (self::useNewSodiumAPI()) {
2270 if (!is_null($alg)) {
2271 ParagonIE_Sodium_Core_Util::declareScalarType($alg, 'int', 6);
2272 return sodium_crypto_pwhash($outlen, $passwd, $salt, $opslimit, $memlimit, $alg);
2273 }
2274 return sodium_crypto_pwhash($outlen, $passwd, $salt, $opslimit, $memlimit);
2275 }
2276 if (self::use_fallback('crypto_pwhash')) {
2277 return (string) call_user_func('\\Sodium\\crypto_pwhash', $outlen, $passwd, $salt, $opslimit, $memlimit);
2278 }
2279 // This is the best we can do.
2280 throw new SodiumException(
2281 'This is not implemented, as it is not possible to implement Argon2i with acceptable performance in pure-PHP'
2282 );
2283 }
2284
2285 /**
2286 * !Exclusive to sodium_compat!
2287 *
2288 * This returns TRUE if the native crypto_pwhash API is available by libsodium.
2289 * This returns FALSE if only sodium_compat is available.
2290 *
2291 * @return bool
2292 */
2293 public static function crypto_pwhash_is_available()
2294 {
2295 if (self::useNewSodiumAPI()) {
2296 return true;
2297 }
2298 if (self::use_fallback('crypto_pwhash')) {
2299 return true;
2300 }
2301 return false;
2302 }
2303
2304 /**
2305 * @param string $passwd
2306 * @param int $opslimit
2307 * @param int $memlimit
2308 * @return string
2309 * @throws SodiumException
2310 * @throws TypeError
2311 * @psalm-suppress MixedArgument
2312 */
2313 public static function crypto_pwhash_str(
2314 #[\SensitiveParameter]
2315 $passwd,
2316 $opslimit,
2317 $memlimit
2318 ) {
2319 ParagonIE_Sodium_Core_Util::declareScalarType($passwd, 'string', 1);
2320 ParagonIE_Sodium_Core_Util::declareScalarType($opslimit, 'int', 2);
2321 ParagonIE_Sodium_Core_Util::declareScalarType($memlimit, 'int', 3);
2322
2323 if (self::useNewSodiumAPI()) {
2324 return sodium_crypto_pwhash_str($passwd, $opslimit, $memlimit);
2325 }
2326 if (self::use_fallback('crypto_pwhash_str')) {
2327 return (string) call_user_func('\\Sodium\\crypto_pwhash_str', $passwd, $opslimit, $memlimit);
2328 }
2329 // This is the best we can do.
2330 throw new SodiumException(
2331 'This is not implemented, as it is not possible to implement Argon2i with acceptable performance in pure-PHP'
2332 );
2333 }
2334
2335 /**
2336 * Do we need to rehash this password?
2337 *
2338 * @param string $hash
2339 * @param int $opslimit
2340 * @param int $memlimit
2341 * @return bool
2342 * @throws SodiumException
2343 */
2344 public static function crypto_pwhash_str_needs_rehash(
2345 #[\SensitiveParameter]
2346 $hash,
2347 $opslimit,
2348 $memlimit
2349 ) {
2350 ParagonIE_Sodium_Core_Util::declareScalarType($hash, 'string', 1);
2351 ParagonIE_Sodium_Core_Util::declareScalarType($opslimit, 'int', 2);
2352 ParagonIE_Sodium_Core_Util::declareScalarType($memlimit, 'int', 3);
2353
2354 // Just grab the first 4 pieces.
2355 $pieces = explode('$', (string) $hash);
2356 $prefix = implode('$', array_slice($pieces, 0, 4));
2357
2358 // Rebuild the expected header.
2359 /** @var int $ops */
2360 $ops = (int) $opslimit;
2361 /** @var int $mem */
2362 $mem = (int) $memlimit >> 10;
2363 $encoded = self::CRYPTO_PWHASH_STRPREFIX . 'v=19$m=' . $mem . ',t=' . $ops . ',p=1';
2364
2365 // Do they match? If so, we don't need to rehash, so return false.
2366 return !ParagonIE_Sodium_Core_Util::hashEquals($encoded, $prefix);
2367 }
2368
2369 /**
2370 * @param string $passwd
2371 * @param string $hash
2372 * @return bool
2373 * @throws SodiumException
2374 * @throws TypeError
2375 * @psalm-suppress MixedArgument
2376 */
2377 public static function crypto_pwhash_str_verify(
2378 #[\SensitiveParameter]
2379 $passwd,
2380 #[\SensitiveParameter]
2381 $hash
2382 ) {
2383 ParagonIE_Sodium_Core_Util::declareScalarType($passwd, 'string', 1);
2384 ParagonIE_Sodium_Core_Util::declareScalarType($hash, 'string', 2);
2385
2386 if (self::useNewSodiumAPI()) {
2387 return (bool) sodium_crypto_pwhash_str_verify($passwd, $hash);
2388 }
2389 if (self::use_fallback('crypto_pwhash_str_verify')) {
2390 return (bool) call_user_func('\\Sodium\\crypto_pwhash_str_verify', $passwd, $hash);
2391 }
2392 // This is the best we can do.
2393 throw new SodiumException(
2394 'This is not implemented, as it is not possible to implement Argon2i with acceptable performance in pure-PHP'
2395 );
2396 }
2397
2398 /**
2399 * @param int $outlen
2400 * @param string $passwd
2401 * @param string $salt
2402 * @param int $opslimit
2403 * @param int $memlimit
2404 * @return string
2405 * @throws SodiumException
2406 * @throws TypeError
2407 */
2408 public static function crypto_pwhash_scryptsalsa208sha256(
2409 $outlen,
2410 #[\SensitiveParameter]
2411 $passwd,
2412 $salt,
2413 $opslimit,
2414 $memlimit
2415 ) {
2416 ParagonIE_Sodium_Core_Util::declareScalarType($outlen, 'int', 1);
2417 ParagonIE_Sodium_Core_Util::declareScalarType($passwd, 'string', 2);
2418 ParagonIE_Sodium_Core_Util::declareScalarType($salt, 'string', 3);
2419 ParagonIE_Sodium_Core_Util::declareScalarType($opslimit, 'int', 4);
2420 ParagonIE_Sodium_Core_Util::declareScalarType($memlimit, 'int', 5);
2421
2422 if (self::useNewSodiumAPI()) {
2423 return (string) sodium_crypto_pwhash_scryptsalsa208sha256(
2424 (int) $outlen,
2425 (string) $passwd,
2426 (string) $salt,
2427 (int) $opslimit,
2428 (int) $memlimit
2429 );
2430 }
2431 if (self::use_fallback('crypto_pwhash_scryptsalsa208sha256')) {
2432 return (string) call_user_func(
2433 '\\Sodium\\crypto_pwhash_scryptsalsa208sha256',
2434 (int) $outlen,
2435 (string) $passwd,
2436 (string) $salt,
2437 (int) $opslimit,
2438 (int) $memlimit
2439 );
2440 }
2441 // This is the best we can do.
2442 throw new SodiumException(
2443 'This is not implemented, as it is not possible to implement Scrypt with acceptable performance in pure-PHP'
2444 );
2445 }
2446
2447 /**
2448 * !Exclusive to sodium_compat!
2449 *
2450 * This returns TRUE if the native crypto_pwhash API is available by libsodium.
2451 * This returns FALSE if only sodium_compat is available.
2452 *
2453 * @return bool
2454 */
2455 public static function crypto_pwhash_scryptsalsa208sha256_is_available()
2456 {
2457 if (self::useNewSodiumAPI()) {
2458 return true;
2459 }
2460 if (self::use_fallback('crypto_pwhash_scryptsalsa208sha256')) {
2461 return true;
2462 }
2463 return false;
2464 }
2465
2466 /**
2467 * @param string $passwd
2468 * @param int $opslimit
2469 * @param int $memlimit
2470 * @return string
2471 * @throws SodiumException
2472 * @throws TypeError
2473 */
2474 public static function crypto_pwhash_scryptsalsa208sha256_str(
2475 #[\SensitiveParameter]
2476 $passwd,
2477 $opslimit,
2478 $memlimit
2479 ) {
2480 ParagonIE_Sodium_Core_Util::declareScalarType($passwd, 'string', 1);
2481 ParagonIE_Sodium_Core_Util::declareScalarType($opslimit, 'int', 2);
2482 ParagonIE_Sodium_Core_Util::declareScalarType($memlimit, 'int', 3);
2483
2484 if (self::useNewSodiumAPI()) {
2485 return (string) sodium_crypto_pwhash_scryptsalsa208sha256_str(
2486 (string) $passwd,
2487 (int) $opslimit,
2488 (int) $memlimit
2489 );
2490 }
2491 if (self::use_fallback('crypto_pwhash_scryptsalsa208sha256_str')) {
2492 return (string) call_user_func(
2493 '\\Sodium\\crypto_pwhash_scryptsalsa208sha256_str',
2494 (string) $passwd,
2495 (int) $opslimit,
2496 (int) $memlimit
2497 );
2498 }
2499 // This is the best we can do.
2500 throw new SodiumException(
2501 'This is not implemented, as it is not possible to implement Scrypt with acceptable performance in pure-PHP'
2502 );
2503 }
2504
2505 /**
2506 * @param string $passwd
2507 * @param string $hash
2508 * @return bool
2509 * @throws SodiumException
2510 * @throws TypeError
2511 */
2512 public static function crypto_pwhash_scryptsalsa208sha256_str_verify(
2513 #[\SensitiveParameter]
2514 $passwd,
2515 #[\SensitiveParameter]
2516 $hash
2517 ) {
2518 ParagonIE_Sodium_Core_Util::declareScalarType($passwd, 'string', 1);
2519 ParagonIE_Sodium_Core_Util::declareScalarType($hash, 'string', 2);
2520
2521 if (self::useNewSodiumAPI()) {
2522 return (bool) sodium_crypto_pwhash_scryptsalsa208sha256_str_verify(
2523 (string) $passwd,
2524 (string) $hash
2525 );
2526 }
2527 if (self::use_fallback('crypto_pwhash_scryptsalsa208sha256_str_verify')) {
2528 return (bool) call_user_func(
2529 '\\Sodium\\crypto_pwhash_scryptsalsa208sha256_str_verify',
2530 (string) $passwd,
2531 (string) $hash
2532 );
2533 }
2534 // This is the best we can do.
2535 throw new SodiumException(
2536 'This is not implemented, as it is not possible to implement Scrypt with acceptable performance in pure-PHP'
2537 );
2538 }
2539
2540 /**
2541 * Calculate the shared secret between your secret key and your
2542 * recipient's public key.
2543 *
2544 * Algorithm: X25519 (ECDH over Curve25519)
2545 *
2546 * @param string $secretKey
2547 * @param string $publicKey
2548 * @return string
2549 * @throws SodiumException
2550 * @throws TypeError
2551 * @psalm-suppress MixedArgument
2552 */
2553 public static function crypto_scalarmult(
2554 #[\SensitiveParameter]
2555 $secretKey,
2556 $publicKey
2557 ) {
2558 /* Type checks: */
2559 ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 1);
2560 ParagonIE_Sodium_Core_Util::declareScalarType($publicKey, 'string', 2);
2561
2562 /* Input validation: */
2563 if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_BOX_SECRETKEYBYTES) {
2564 throw new SodiumException('Argument 1 must be CRYPTO_BOX_SECRETKEYBYTES long.');
2565 }
2566 if (ParagonIE_Sodium_Core_Util::strlen($publicKey) !== self::CRYPTO_BOX_PUBLICKEYBYTES) {
2567 throw new SodiumException('Argument 2 must be CRYPTO_BOX_PUBLICKEYBYTES long.');
2568 }
2569
2570 if (self::useNewSodiumAPI()) {
2571 return sodium_crypto_scalarmult($secretKey, $publicKey);
2572 }
2573 if (self::use_fallback('crypto_scalarmult')) {
2574 return (string) call_user_func('\\Sodium\\crypto_scalarmult', $secretKey, $publicKey);
2575 }
2576
2577 /* Output validation: Forbid all-zero keys */
2578 if (ParagonIE_Sodium_Core_Util::hashEquals($secretKey, str_repeat("\0", self::CRYPTO_BOX_SECRETKEYBYTES))) {
2579 throw new SodiumException('Zero secret key is not allowed');
2580 }
2581 if (ParagonIE_Sodium_Core_Util::hashEquals($publicKey, str_repeat("\0", self::CRYPTO_BOX_PUBLICKEYBYTES))) {
2582 throw new SodiumException('Zero public key is not allowed');
2583 }
2584 if (PHP_INT_SIZE === 4) {
2585 return ParagonIE_Sodium_Crypto32::scalarmult($secretKey, $publicKey);
2586 }
2587 return ParagonIE_Sodium_Crypto::scalarmult($secretKey, $publicKey);
2588 }
2589
2590 /**
2591 * Calculate an X25519 public key from an X25519 secret key.
2592 *
2593 * @param string $secretKey
2594 * @return string
2595 * @throws SodiumException
2596 * @throws TypeError
2597 * @psalm-suppress TooFewArguments
2598 * @psalm-suppress MixedArgument
2599 */
2600 public static function crypto_scalarmult_base(
2601 #[\SensitiveParameter]
2602 $secretKey
2603 ) {
2604 /* Type checks: */
2605 ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 1);
2606
2607 /* Input validation: */
2608 if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_BOX_SECRETKEYBYTES) {
2609 throw new SodiumException('Argument 1 must be CRYPTO_BOX_SECRETKEYBYTES long.');
2610 }
2611
2612 if (self::useNewSodiumAPI()) {
2613 return sodium_crypto_scalarmult_base($secretKey);
2614 }
2615 if (self::use_fallback('crypto_scalarmult_base')) {
2616 return (string) call_user_func('\\Sodium\\crypto_scalarmult_base', $secretKey);
2617 }
2618 if (ParagonIE_Sodium_Core_Util::hashEquals($secretKey, str_repeat("\0", self::CRYPTO_BOX_SECRETKEYBYTES))) {
2619 throw new SodiumException('Zero secret key is not allowed');
2620 }
2621 if (PHP_INT_SIZE === 4) {
2622 return ParagonIE_Sodium_Crypto32::scalarmult_base($secretKey);
2623 }
2624 return ParagonIE_Sodium_Crypto::scalarmult_base($secretKey);
2625 }
2626
2627 /**
2628 * Authenticated symmetric-key encryption.
2629 *
2630 * Algorithm: XSalsa20-Poly1305
2631 *
2632 * @param string $plaintext The message you're encrypting
2633 * @param string $nonce A Number to be used Once; must be 24 bytes
2634 * @param string $key Symmetric encryption key
2635 * @return string Ciphertext with Poly1305 MAC
2636 * @throws SodiumException
2637 * @throws TypeError
2638 * @psalm-suppress MixedArgument
2639 */
2640 public static function crypto_secretbox(
2641 #[\SensitiveParameter]
2642 $plaintext,
2643 $nonce,
2644 #[\SensitiveParameter]
2645 $key
2646 ) {
2647 /* Type checks: */
2648 ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
2649 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
2650 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);
2651
2652 /* Input validation: */
2653 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_SECRETBOX_NONCEBYTES) {
2654 throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_NONCEBYTES long.');
2655 }
2656 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_SECRETBOX_KEYBYTES) {
2657 throw new SodiumException('Argument 3 must be CRYPTO_SECRETBOX_KEYBYTES long.');
2658 }
2659
2660 if (self::useNewSodiumAPI()) {
2661 return sodium_crypto_secretbox($plaintext, $nonce, $key);
2662 }
2663 if (self::use_fallback('crypto_secretbox')) {
2664 return (string) call_user_func('\\Sodium\\crypto_secretbox', $plaintext, $nonce, $key);
2665 }
2666 if (PHP_INT_SIZE === 4) {
2667 return ParagonIE_Sodium_Crypto32::secretbox($plaintext, $nonce, $key);
2668 }
2669 return ParagonIE_Sodium_Crypto::secretbox($plaintext, $nonce, $key);
2670 }
2671
2672 /**
2673 * Decrypts a message previously encrypted with crypto_secretbox().
2674 *
2675 * @param string $ciphertext Ciphertext with Poly1305 MAC
2676 * @param string $nonce A Number to be used Once; must be 24 bytes
2677 * @param string $key Symmetric encryption key
2678 * @return string Original plaintext message
2679 * @throws SodiumException
2680 * @throws TypeError
2681 * @psalm-suppress MixedArgument
2682 * @psalm-suppress MixedInferredReturnType
2683 * @psalm-suppress MixedReturnStatement
2684 */
2685 public static function crypto_secretbox_open(
2686 $ciphertext,
2687 $nonce,
2688 #[\SensitiveParameter]
2689 $key
2690 ) {
2691 /* Type checks: */
2692 ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
2693 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
2694 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);
2695
2696 /* Input validation: */
2697 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_SECRETBOX_NONCEBYTES) {
2698 throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_NONCEBYTES long.');
2699 }
2700 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_SECRETBOX_KEYBYTES) {
2701 throw new SodiumException('Argument 3 must be CRYPTO_SECRETBOX_KEYBYTES long.');
2702 }
2703
2704 if (self::useNewSodiumAPI()) {
2705 /**
2706 * @psalm-suppress InvalidReturnStatement
2707 * @psalm-suppress FalsableReturnStatement
2708 */
2709 return sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
2710 }
2711 if (self::use_fallback('crypto_secretbox_open')) {
2712 return call_user_func('\\Sodium\\crypto_secretbox_open', $ciphertext, $nonce, $key);
2713 }
2714 if (PHP_INT_SIZE === 4) {
2715 return ParagonIE_Sodium_Crypto32::secretbox_open($ciphertext, $nonce, $key);
2716 }
2717 return ParagonIE_Sodium_Crypto::secretbox_open($ciphertext, $nonce, $key);
2718 }
2719
2720 /**
2721 * Return a secure random key for use with crypto_secretbox
2722 *
2723 * @return string
2724 * @throws Exception
2725 * @throws Error
2726 */
2727 public static function crypto_secretbox_keygen()
2728 {
2729 return random_bytes(self::CRYPTO_SECRETBOX_KEYBYTES);
2730 }
2731
2732 /**
2733 * Authenticated symmetric-key encryption.
2734 *
2735 * Algorithm: XChaCha20-Poly1305
2736 *
2737 * @param string $plaintext The message you're encrypting
2738 * @param string $nonce A Number to be used Once; must be 24 bytes
2739 * @param string $key Symmetric encryption key
2740 * @return string Ciphertext with Poly1305 MAC
2741 * @throws SodiumException
2742 * @throws TypeError
2743 * @psalm-suppress MixedArgument
2744 */
2745 public static function crypto_secretbox_xchacha20poly1305($plaintext, $nonce, $key)
2746 {
2747 /* Type checks: */
2748 ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
2749 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
2750 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);
2751
2752 /* Input validation: */
2753 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_SECRETBOX_NONCEBYTES) {
2754 throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_NONCEBYTES long.');
2755 }
2756 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_SECRETBOX_KEYBYTES) {
2757 throw new SodiumException('Argument 3 must be CRYPTO_SECRETBOX_KEYBYTES long.');
2758 }
2759 if (PHP_INT_SIZE === 4) {
2760 return ParagonIE_Sodium_Crypto32::secretbox_xchacha20poly1305($plaintext, $nonce, $key);
2761 }
2762 return ParagonIE_Sodium_Crypto::secretbox_xchacha20poly1305($plaintext, $nonce, $key);
2763 }
2764 /**
2765 * Decrypts a message previously encrypted with crypto_secretbox_xchacha20poly1305().
2766 *
2767 * @param string $ciphertext Ciphertext with Poly1305 MAC
2768 * @param string $nonce A Number to be used Once; must be 24 bytes
2769 * @param string $key Symmetric encryption key
2770 * @return string Original plaintext message
2771 * @throws SodiumException
2772 * @throws TypeError
2773 * @psalm-suppress MixedArgument
2774 */
2775 public static function crypto_secretbox_xchacha20poly1305_open(
2776 $ciphertext,
2777 $nonce,
2778 #[\SensitiveParameter]
2779 $key
2780 ) {
2781 /* Type checks: */
2782 ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
2783 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
2784 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);
2785
2786 /* Input validation: */
2787 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_SECRETBOX_NONCEBYTES) {
2788 throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_NONCEBYTES long.');
2789 }
2790 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_SECRETBOX_KEYBYTES) {
2791 throw new SodiumException('Argument 3 must be CRYPTO_SECRETBOX_KEYBYTES long.');
2792 }
2793 if (ParagonIE_Sodium_Core_Util::strlen($ciphertext) < self::CRYPTO_SECRETBOX_MACBYTES) {
2794 throw new SodiumException("Ciphertext must be at least CRYPTO_SECRETBOX_MACBYTES long");
2795 }
2796
2797 if (PHP_INT_SIZE === 4) {
2798 return ParagonIE_Sodium_Crypto32::secretbox_xchacha20poly1305_open($ciphertext, $nonce, $key);
2799 }
2800 return ParagonIE_Sodium_Crypto::secretbox_xchacha20poly1305_open($ciphertext, $nonce, $key);
2801 }
2802
2803 /**
2804 * @param string $key
2805 * @return array<int, string> Returns a state and a header.
2806 * @throws Exception
2807 * @throws SodiumException
2808 */
2809 public static function crypto_secretstream_xchacha20poly1305_init_push(
2810 #[\SensitiveParameter]
2811 $key
2812 ) {
2813 if (PHP_INT_SIZE === 4) {
2814 return ParagonIE_Sodium_Crypto32::secretstream_xchacha20poly1305_init_push($key);
2815 }
2816 return ParagonIE_Sodium_Crypto::secretstream_xchacha20poly1305_init_push($key);
2817 }
2818
2819 /**
2820 * @param string $header
2821 * @param string $key
2822 * @return string Returns a state.
2823 * @throws Exception
2824 */
2825 public static function crypto_secretstream_xchacha20poly1305_init_pull(
2826 $header,
2827 #[\SensitiveParameter]
2828 $key
2829 ) {
2830 if (ParagonIE_Sodium_Core_Util::strlen($header) < self::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES) {
2831 throw new SodiumException(
2832 'header size should be SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES bytes'
2833 );
2834 }
2835 if (PHP_INT_SIZE === 4) {
2836 return ParagonIE_Sodium_Crypto32::secretstream_xchacha20poly1305_init_pull($key, $header);
2837 }
2838 return ParagonIE_Sodium_Crypto::secretstream_xchacha20poly1305_init_pull($key, $header);
2839 }
2840
2841 /**
2842 * @param string $state
2843 * @param string $msg
2844 * @param string $aad
2845 * @param int $tag
2846 * @return string
2847 * @throws SodiumException
2848 */
2849 public static function crypto_secretstream_xchacha20poly1305_push(
2850 #[\SensitiveParameter]
2851 &$state,
2852 #[\SensitiveParameter]
2853 $msg,
2854 $aad = '',
2855 $tag = 0
2856 ) {
2857 if (PHP_INT_SIZE === 4) {
2858 return ParagonIE_Sodium_Crypto32::secretstream_xchacha20poly1305_push(
2859 $state,
2860 $msg,
2861 $aad,
2862 $tag
2863 );
2864 }
2865 return ParagonIE_Sodium_Crypto::secretstream_xchacha20poly1305_push(
2866 $state,
2867 $msg,
2868 $aad,
2869 $tag
2870 );
2871 }
2872
2873 /**
2874 * @param string $state
2875 * @param string $msg
2876 * @param string $aad
2877 * @return bool|array{0: string, 1: int}
2878 * @throws SodiumException
2879 */
2880 public static function crypto_secretstream_xchacha20poly1305_pull(
2881 #[\SensitiveParameter]
2882 &$state,
2883 $msg,
2884 $aad = ''
2885 ) {
2886 if (PHP_INT_SIZE === 4) {
2887 return ParagonIE_Sodium_Crypto32::secretstream_xchacha20poly1305_pull(
2888 $state,
2889 $msg,
2890 $aad
2891 );
2892 }
2893 return ParagonIE_Sodium_Crypto::secretstream_xchacha20poly1305_pull(
2894 $state,
2895 $msg,
2896 $aad
2897 );
2898 }
2899
2900 /**
2901 * @return string
2902 * @throws Exception
2903 */
2904 public static function crypto_secretstream_xchacha20poly1305_keygen()
2905 {
2906 return random_bytes(self::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES);
2907 }
2908
2909 /**
2910 * @param string $state
2911 * @return void
2912 * @throws SodiumException
2913 */
2914 public static function crypto_secretstream_xchacha20poly1305_rekey(
2915 #[\SensitiveParameter]
2916 &$state
2917 ) {
2918 if (PHP_INT_SIZE === 4) {
2919 ParagonIE_Sodium_Crypto32::secretstream_xchacha20poly1305_rekey($state);
2920 } else {
2921 ParagonIE_Sodium_Crypto::secretstream_xchacha20poly1305_rekey($state);
2922 }
2923 }
2924
2925 /**
2926 * Calculates a SipHash-2-4 hash of a message for a given key.
2927 *
2928 * @param string $message Input message
2929 * @param string $key SipHash-2-4 key
2930 * @return string Hash
2931 * @throws SodiumException
2932 * @throws TypeError
2933 * @psalm-suppress MixedArgument
2934 * @psalm-suppress MixedInferredReturnType
2935 * @psalm-suppress MixedReturnStatement
2936 */
2937 public static function crypto_shorthash(
2938 $message,
2939 #[\SensitiveParameter]
2940 $key
2941 ) {
2942 /* Type checks: */
2943 ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
2944 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 2);
2945
2946 /* Input validation: */
2947 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_SHORTHASH_KEYBYTES) {
2948 throw new SodiumException('Argument 2 must be CRYPTO_SHORTHASH_KEYBYTES long.');
2949 }
2950
2951 if (self::useNewSodiumAPI()) {
2952 return sodium_crypto_shorthash($message, $key);
2953 }
2954 if (self::use_fallback('crypto_shorthash')) {
2955 return (string) call_user_func('\\Sodium\\crypto_shorthash', $message, $key);
2956 }
2957 if (PHP_INT_SIZE === 4) {
2958 return ParagonIE_Sodium_Core32_SipHash::sipHash24($message, $key);
2959 }
2960 return ParagonIE_Sodium_Core_SipHash::sipHash24($message, $key);
2961 }
2962
2963 /**
2964 * Return a secure random key for use with crypto_shorthash
2965 *
2966 * @return string
2967 * @throws Exception
2968 * @throws Error
2969 */
2970 public static function crypto_shorthash_keygen()
2971 {
2972 return random_bytes(self::CRYPTO_SHORTHASH_KEYBYTES);
2973 }
2974
2975 /**
2976 * Returns a signed message. You probably want crypto_sign_detached()
2977 * instead, which only returns the signature.
2978 *
2979 * Algorithm: Ed25519 (EdDSA over Curve25519)
2980 *
2981 * @param string $message Message to be signed.
2982 * @param string $secretKey Secret signing key.
2983 * @return string Signed message (signature is prefixed).
2984 * @throws SodiumException
2985 * @throws TypeError
2986 * @psalm-suppress MixedArgument
2987 * @psalm-suppress MixedInferredReturnType
2988 * @psalm-suppress MixedReturnStatement
2989 */
2990 public static function crypto_sign(
2991 $message,
2992 #[\SensitiveParameter]
2993 $secretKey
2994 ) {
2995 /* Type checks: */
2996 ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
2997 ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 2);
2998
2999 /* Input validation: */
3000 if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_SIGN_SECRETKEYBYTES) {
3001 throw new SodiumException('Argument 2 must be CRYPTO_SIGN_SECRETKEYBYTES long.');
3002 }
3003
3004 if (self::useNewSodiumAPI()) {
3005 return sodium_crypto_sign($message, $secretKey);
3006 }
3007 if (self::use_fallback('crypto_sign')) {
3008 return (string) call_user_func('\\Sodium\\crypto_sign', $message, $secretKey);
3009 }
3010 if (PHP_INT_SIZE === 4) {
3011 return ParagonIE_Sodium_Crypto32::sign($message, $secretKey);
3012 }
3013 return ParagonIE_Sodium_Crypto::sign($message, $secretKey);
3014 }
3015
3016 /**
3017 * Validates a signed message then returns the message.
3018 *
3019 * @param string $signedMessage A signed message
3020 * @param string $publicKey A public key
3021 * @return string The original message (if the signature is
3022 * valid for this public key)
3023 * @throws SodiumException
3024 * @throws TypeError
3025 * @psalm-suppress MixedArgument
3026 * @psalm-suppress MixedInferredReturnType
3027 * @psalm-suppress MixedReturnStatement
3028 */
3029 public static function crypto_sign_open(
3030 $signedMessage,
3031 $publicKey
3032 ) {
3033 /* Type checks: */
3034 ParagonIE_Sodium_Core_Util::declareScalarType($signedMessage, 'string', 1);
3035 ParagonIE_Sodium_Core_Util::declareScalarType($publicKey, 'string', 2);
3036
3037 /* Input validation: */
3038 if (ParagonIE_Sodium_Core_Util::strlen($signedMessage) < self::CRYPTO_SIGN_BYTES) {
3039 throw new SodiumException('Argument 1 must be at least CRYPTO_SIGN_BYTES long.');
3040 }
3041 if (ParagonIE_Sodium_Core_Util::strlen($publicKey) !== self::CRYPTO_SIGN_PUBLICKEYBYTES) {
3042 throw new SodiumException('Argument 2 must be CRYPTO_SIGN_PUBLICKEYBYTES long.');
3043 }
3044
3045 if (self::useNewSodiumAPI()) {
3046 /**
3047 * @psalm-suppress InvalidReturnStatement
3048 * @psalm-suppress FalsableReturnStatement
3049 */
3050 return sodium_crypto_sign_open($signedMessage, $publicKey);
3051 }
3052 if (self::use_fallback('crypto_sign_open')) {
3053 return call_user_func('\\Sodium\\crypto_sign_open', $signedMessage, $publicKey);
3054 }
3055 if (PHP_INT_SIZE === 4) {
3056 return ParagonIE_Sodium_Crypto32::sign_open($signedMessage, $publicKey);
3057 }
3058 return ParagonIE_Sodium_Crypto::sign_open($signedMessage, $publicKey);
3059 }
3060
3061 /**
3062 * Generate a new random Ed25519 keypair.
3063 *
3064 * @return string
3065 * @throws SodiumException
3066 * @throws TypeError
3067 */
3068 public static function crypto_sign_keypair()
3069 {
3070 if (self::useNewSodiumAPI()) {
3071 return sodium_crypto_sign_keypair();
3072 }
3073 if (self::use_fallback('crypto_sign_keypair')) {
3074 return (string) call_user_func('\\Sodium\\crypto_sign_keypair');
3075 }
3076 if (PHP_INT_SIZE === 4) {
3077 return ParagonIE_Sodium_Core32_Ed25519::keypair();
3078 }
3079 return ParagonIE_Sodium_Core_Ed25519::keypair();
3080 }
3081
3082 /**
3083 * @param string $sk
3084 * @param string $pk
3085 * @return string
3086 * @throws SodiumException
3087 */
3088 public static function crypto_sign_keypair_from_secretkey_and_publickey(
3089 #[\SensitiveParameter]
3090 $sk,
3091 $pk
3092 ) {
3093 ParagonIE_Sodium_Core_Util::declareScalarType($sk, 'string', 1);
3094 ParagonIE_Sodium_Core_Util::declareScalarType($pk, 'string', 1);
3095 $sk = (string) $sk;
3096 $pk = (string) $pk;
3097
3098 if (ParagonIE_Sodium_Core_Util::strlen($sk) !== self::CRYPTO_SIGN_SECRETKEYBYTES) {
3099 throw new SodiumException('secretkey should be SODIUM_CRYPTO_SIGN_SECRETKEYBYTES bytes');
3100 }
3101 if (ParagonIE_Sodium_Core_Util::strlen($pk) !== self::CRYPTO_SIGN_PUBLICKEYBYTES) {
3102 throw new SodiumException('publickey should be SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES bytes');
3103 }
3104
3105 if (self::useNewSodiumAPI()) {
3106 return sodium_crypto_sign_keypair_from_secretkey_and_publickey($sk, $pk);
3107 }
3108 return $sk . $pk;
3109 }
3110
3111 /**
3112 * Generate an Ed25519 keypair from a seed.
3113 *
3114 * @param string $seed Input seed
3115 * @return string Keypair
3116 * @throws SodiumException
3117 * @throws TypeError
3118 * @psalm-suppress MixedArgument
3119 */
3120 public static function crypto_sign_seed_keypair(
3121 #[\SensitiveParameter]
3122 $seed
3123 ) {
3124 ParagonIE_Sodium_Core_Util::declareScalarType($seed, 'string', 1);
3125
3126 if (self::useNewSodiumAPI()) {
3127 return sodium_crypto_sign_seed_keypair($seed);
3128 }
3129 if (self::use_fallback('crypto_sign_keypair')) {
3130 return (string) call_user_func('\\Sodium\\crypto_sign_seed_keypair', $seed);
3131 }
3132 $publicKey = '';
3133 $secretKey = '';
3134 if (PHP_INT_SIZE === 4) {
3135 ParagonIE_Sodium_Core32_Ed25519::seed_keypair($publicKey, $secretKey, $seed);
3136 } else {
3137 ParagonIE_Sodium_Core_Ed25519::seed_keypair($publicKey, $secretKey, $seed);
3138 }
3139 return $secretKey . $publicKey;
3140 }
3141
3142 /**
3143 * Extract an Ed25519 public key from an Ed25519 keypair.
3144 *
3145 * @param string $keypair Keypair
3146 * @return string Public key
3147 * @throws SodiumException
3148 * @throws TypeError
3149 * @psalm-suppress MixedArgument
3150 */
3151 public static function crypto_sign_publickey(
3152 #[\SensitiveParameter]
3153 $keypair
3154 ) {
3155 /* Type checks: */
3156 ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 1);
3157
3158 /* Input validation: */
3159 if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_SIGN_KEYPAIRBYTES) {
3160 throw new SodiumException('Argument 1 must be CRYPTO_SIGN_KEYPAIRBYTES long.');
3161 }
3162
3163 if (self::useNewSodiumAPI()) {
3164 return sodium_crypto_sign_publickey($keypair);
3165 }
3166 if (self::use_fallback('crypto_sign_publickey')) {
3167 return (string) call_user_func('\\Sodium\\crypto_sign_publickey', $keypair);
3168 }
3169 if (PHP_INT_SIZE === 4) {
3170 return ParagonIE_Sodium_Core32_Ed25519::publickey($keypair);
3171 }
3172 return ParagonIE_Sodium_Core_Ed25519::publickey($keypair);
3173 }
3174
3175 /**
3176 * Calculate an Ed25519 public key from an Ed25519 secret key.
3177 *
3178 * @param string $secretKey Your Ed25519 secret key
3179 * @return string The corresponding Ed25519 public key
3180 * @throws SodiumException
3181 * @throws TypeError
3182 * @psalm-suppress MixedArgument
3183 */
3184 public static function crypto_sign_publickey_from_secretkey(
3185 #[\SensitiveParameter]
3186 $secretKey
3187 ) {
3188 /* Type checks: */
3189 ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 1);
3190
3191 /* Input validation: */
3192 if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_SIGN_SECRETKEYBYTES) {
3193 throw new SodiumException('Argument 1 must be CRYPTO_SIGN_SECRETKEYBYTES long.');
3194 }
3195
3196 if (self::useNewSodiumAPI()) {
3197 return sodium_crypto_sign_publickey_from_secretkey($secretKey);
3198 }
3199 if (self::use_fallback('crypto_sign_publickey_from_secretkey')) {
3200 return (string) call_user_func('\\Sodium\\crypto_sign_publickey_from_secretkey', $secretKey);
3201 }
3202 if (PHP_INT_SIZE === 4) {
3203 return ParagonIE_Sodium_Core32_Ed25519::publickey_from_secretkey($secretKey);
3204 }
3205 return ParagonIE_Sodium_Core_Ed25519::publickey_from_secretkey($secretKey);
3206 }
3207
3208 /**
3209 * Extract an Ed25519 secret key from an Ed25519 keypair.
3210 *
3211 * @param string $keypair Keypair
3212 * @return string Secret key
3213 * @throws SodiumException
3214 * @throws TypeError
3215 * @psalm-suppress MixedArgument
3216 */
3217 public static function crypto_sign_secretkey(
3218 #[\SensitiveParameter]
3219 $keypair
3220 ) {
3221 /* Type checks: */
3222 ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 1);
3223
3224 /* Input validation: */
3225 if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_SIGN_KEYPAIRBYTES) {
3226 throw new SodiumException('Argument 1 must be CRYPTO_SIGN_KEYPAIRBYTES long.');
3227 }
3228
3229 if (self::useNewSodiumAPI()) {
3230 return sodium_crypto_sign_secretkey($keypair);
3231 }
3232 if (self::use_fallback('crypto_sign_secretkey')) {
3233 return (string) call_user_func('\\Sodium\\crypto_sign_secretkey', $keypair);
3234 }
3235 if (PHP_INT_SIZE === 4) {
3236 return ParagonIE_Sodium_Core32_Ed25519::secretkey($keypair);
3237 }
3238 return ParagonIE_Sodium_Core_Ed25519::secretkey($keypair);
3239 }
3240
3241 /**
3242 * Calculate the Ed25519 signature of a message and return ONLY the signature.
3243 *
3244 * Algorithm: Ed25519 (EdDSA over Curve25519)
3245 *
3246 * @param string $message Message to be signed
3247 * @param string $secretKey Secret signing key
3248 * @return string Digital signature
3249 * @throws SodiumException
3250 * @throws TypeError
3251 * @psalm-suppress MixedArgument
3252 */
3253 public static function crypto_sign_detached(
3254 $message,
3255 #[\SensitiveParameter]
3256 $secretKey
3257 ) {
3258 /* Type checks: */
3259 ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
3260 ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 2);
3261
3262 /* Input validation: */
3263 if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_SIGN_SECRETKEYBYTES) {
3264 throw new SodiumException('Argument 2 must be CRYPTO_SIGN_SECRETKEYBYTES long.');
3265 }
3266
3267 if (self::useNewSodiumAPI()) {
3268 return sodium_crypto_sign_detached($message, $secretKey);
3269 }
3270 if (self::use_fallback('crypto_sign_detached')) {
3271 return (string) call_user_func('\\Sodium\\crypto_sign_detached', $message, $secretKey);
3272 }
3273 if (PHP_INT_SIZE === 4) {
3274 return ParagonIE_Sodium_Crypto32::sign_detached($message, $secretKey);
3275 }
3276 return ParagonIE_Sodium_Crypto::sign_detached($message, $secretKey);
3277 }
3278
3279 /**
3280 * Verify the Ed25519 signature of a message.
3281 *
3282 * @param string $signature Digital sginature
3283 * @param string $message Message to be verified
3284 * @param string $publicKey Public key
3285 * @return bool TRUE if this signature is good for this public key;
3286 * FALSE otherwise
3287 * @throws SodiumException
3288 * @throws TypeError
3289 * @psalm-suppress MixedArgument
3290 */
3291 public static function crypto_sign_verify_detached($signature, $message, $publicKey)
3292 {
3293 /* Type checks: */
3294 ParagonIE_Sodium_Core_Util::declareScalarType($signature, 'string', 1);
3295 ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 2);
3296 ParagonIE_Sodium_Core_Util::declareScalarType($publicKey, 'string', 3);
3297
3298 /* Input validation: */
3299 if (ParagonIE_Sodium_Core_Util::strlen($signature) !== self::CRYPTO_SIGN_BYTES) {
3300 throw new SodiumException('Argument 1 must be CRYPTO_SIGN_BYTES long.');
3301 }
3302 if (ParagonIE_Sodium_Core_Util::strlen($publicKey) !== self::CRYPTO_SIGN_PUBLICKEYBYTES) {
3303 throw new SodiumException('Argument 3 must be CRYPTO_SIGN_PUBLICKEYBYTES long.');
3304 }
3305
3306 if (self::useNewSodiumAPI()) {
3307 return sodium_crypto_sign_verify_detached($signature, $message, $publicKey);
3308 }
3309 if (self::use_fallback('crypto_sign_verify_detached')) {
3310 return (bool) call_user_func(
3311 '\\Sodium\\crypto_sign_verify_detached',
3312 $signature,
3313 $message,
3314 $publicKey
3315 );
3316 }
3317 if (PHP_INT_SIZE === 4) {
3318 return ParagonIE_Sodium_Crypto32::sign_verify_detached($signature, $message, $publicKey);
3319 }
3320 return ParagonIE_Sodium_Crypto::sign_verify_detached($signature, $message, $publicKey);
3321 }
3322
3323 /**
3324 * Convert an Ed25519 public key to a Curve25519 public key
3325 *
3326 * @param string $pk
3327 * @return string
3328 * @throws SodiumException
3329 * @throws TypeError
3330 * @psalm-suppress MixedArgument
3331 */
3332 public static function crypto_sign_ed25519_pk_to_curve25519($pk)
3333 {
3334 /* Type checks: */
3335 ParagonIE_Sodium_Core_Util::declareScalarType($pk, 'string', 1);
3336
3337 /* Input validation: */
3338 if (ParagonIE_Sodium_Core_Util::strlen($pk) < self::CRYPTO_SIGN_PUBLICKEYBYTES) {
3339 throw new SodiumException('Argument 1 must be at least CRYPTO_SIGN_PUBLICKEYBYTES long.');
3340 }
3341 if (self::useNewSodiumAPI()) {
3342 if (is_callable('crypto_sign_ed25519_pk_to_curve25519')) {
3343 return (string) sodium_crypto_sign_ed25519_pk_to_curve25519($pk);
3344 }
3345 }
3346 if (self::use_fallback('crypto_sign_ed25519_pk_to_curve25519')) {
3347 return (string) call_user_func('\\Sodium\\crypto_sign_ed25519_pk_to_curve25519', $pk);
3348 }
3349 if (PHP_INT_SIZE === 4) {
3350 return ParagonIE_Sodium_Core32_Ed25519::pk_to_curve25519($pk);
3351 }
3352 return ParagonIE_Sodium_Core_Ed25519::pk_to_curve25519($pk);
3353 }
3354
3355 /**
3356 * Convert an Ed25519 secret key to a Curve25519 secret key
3357 *
3358 * @param string $sk
3359 * @return string
3360 * @throws SodiumException
3361 * @throws TypeError
3362 * @psalm-suppress MixedArgument
3363 */
3364 public static function crypto_sign_ed25519_sk_to_curve25519(
3365 #[\SensitiveParameter]
3366 $sk
3367 ) {
3368 /* Type checks: */
3369 ParagonIE_Sodium_Core_Util::declareScalarType($sk, 'string', 1);
3370
3371 /* Input validation: */
3372 if (ParagonIE_Sodium_Core_Util::strlen($sk) < self::CRYPTO_SIGN_SEEDBYTES) {
3373 throw new SodiumException('Argument 1 must be at least CRYPTO_SIGN_SEEDBYTES long.');
3374 }
3375 if (self::useNewSodiumAPI()) {
3376 if (is_callable('crypto_sign_ed25519_sk_to_curve25519')) {
3377 return sodium_crypto_sign_ed25519_sk_to_curve25519($sk);
3378 }
3379 }
3380 if (self::use_fallback('crypto_sign_ed25519_sk_to_curve25519')) {
3381 return (string) call_user_func('\\Sodium\\crypto_sign_ed25519_sk_to_curve25519', $sk);
3382 }
3383
3384 $h = hash('sha512', ParagonIE_Sodium_Core_Util::substr($sk, 0, 32), true);
3385 $h[0] = ParagonIE_Sodium_Core_Util::intToChr(
3386 ParagonIE_Sodium_Core_Util::chrToInt($h[0]) & 248
3387 );
3388 $h[31] = ParagonIE_Sodium_Core_Util::intToChr(
3389 (ParagonIE_Sodium_Core_Util::chrToInt($h[31]) & 127) | 64
3390 );
3391 return ParagonIE_Sodium_Core_Util::substr($h, 0, 32);
3392 }
3393
3394 /**
3395 * Expand a key and nonce into a keystream of pseudorandom bytes.
3396 *
3397 * @param int $len Number of bytes desired
3398 * @param string $nonce Number to be used Once; must be 24 bytes
3399 * @param string $key XSalsa20 key
3400 * @return string Pseudorandom stream that can be XORed with messages
3401 * to provide encryption (but not authentication; see
3402 * Poly1305 or crypto_auth() for that, which is not
3403 * optional for security)
3404 * @throws SodiumException
3405 * @throws TypeError
3406 * @psalm-suppress MixedArgument
3407 */
3408 public static function crypto_stream(
3409 $len,
3410 $nonce,
3411 #[\SensitiveParameter]
3412 $key
3413 ) {
3414 /* Type checks: */
3415 ParagonIE_Sodium_Core_Util::declareScalarType($len, 'int', 1);
3416 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
3417 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);
3418
3419 /* Input validation: */
3420 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_STREAM_NONCEBYTES) {
3421 throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_NONCEBYTES long.');
3422 }
3423 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_STREAM_KEYBYTES) {
3424 throw new SodiumException('Argument 3 must be CRYPTO_STREAM_KEYBYTES long.');
3425 }
3426
3427 if (self::useNewSodiumAPI()) {
3428 return sodium_crypto_stream($len, $nonce, $key);
3429 }
3430 if (self::use_fallback('crypto_stream')) {
3431 return (string) call_user_func('\\Sodium\\crypto_stream', $len, $nonce, $key);
3432 }
3433 if (PHP_INT_SIZE === 4) {
3434 return ParagonIE_Sodium_Core32_XSalsa20::xsalsa20($len, $nonce, $key);
3435 }
3436 return ParagonIE_Sodium_Core_XSalsa20::xsalsa20($len, $nonce, $key);
3437 }
3438
3439 /**
3440 * DANGER! UNAUTHENTICATED ENCRYPTION!
3441 *
3442 * Unless you are following expert advice, do not use this feature.
3443 *
3444 * Algorithm: XSalsa20
3445 *
3446 * This DOES NOT provide ciphertext integrity.
3447 *
3448 * @param string $message Plaintext message
3449 * @param string $nonce Number to be used Once; must be 24 bytes
3450 * @param string $key Encryption key
3451 * @return string Encrypted text which is vulnerable to chosen-
3452 * ciphertext attacks unless you implement some
3453 * other mitigation to the ciphertext (i.e.
3454 * Encrypt then MAC)
3455 * @throws SodiumException
3456 * @throws TypeError
3457 * @psalm-suppress MixedArgument
3458 */
3459 public static function crypto_stream_xor(
3460 #[\SensitiveParameter]
3461 $message,
3462 $nonce,
3463 #[\SensitiveParameter]
3464 $key
3465 ) {
3466 /* Type checks: */
3467 ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
3468 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
3469 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);
3470
3471 /* Input validation: */
3472 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_STREAM_NONCEBYTES) {
3473 throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_NONCEBYTES long.');
3474 }
3475 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_STREAM_KEYBYTES) {
3476 throw new SodiumException('Argument 3 must be CRYPTO_SECRETBOX_KEYBYTES long.');
3477 }
3478
3479 if (self::useNewSodiumAPI()) {
3480 return sodium_crypto_stream_xor($message, $nonce, $key);
3481 }
3482 if (self::use_fallback('crypto_stream_xor')) {
3483 return (string) call_user_func('\\Sodium\\crypto_stream_xor', $message, $nonce, $key);
3484 }
3485 if (PHP_INT_SIZE === 4) {
3486 return ParagonIE_Sodium_Core32_XSalsa20::xsalsa20_xor($message, $nonce, $key);
3487 }
3488 return ParagonIE_Sodium_Core_XSalsa20::xsalsa20_xor($message, $nonce, $key);
3489 }
3490
3491 /**
3492 * Return a secure random key for use with crypto_stream
3493 *
3494 * @return string
3495 * @throws Exception
3496 * @throws Error
3497 */
3498 public static function crypto_stream_keygen()
3499 {
3500 return random_bytes(self::CRYPTO_STREAM_KEYBYTES);
3501 }
3502
3503
3504 /**
3505 * Expand a key and nonce into a keystream of pseudorandom bytes.
3506 *
3507 * @param int $len Number of bytes desired
3508 * @param string $nonce Number to be used Once; must be 24 bytes
3509 * @param string $key XChaCha20 key
3510 * @param bool $dontFallback
3511 * @return string Pseudorandom stream that can be XORed with messages
3512 * to provide encryption (but not authentication; see
3513 * Poly1305 or crypto_auth() for that, which is not
3514 * optional for security)
3515 * @throws SodiumException
3516 * @throws TypeError
3517 * @psalm-suppress MixedArgument
3518 */
3519 public static function crypto_stream_xchacha20(
3520 $len,
3521 $nonce,
3522 #[\SensitiveParameter]
3523 $key,
3524 $dontFallback = false
3525 ) {
3526 /* Type checks: */
3527 ParagonIE_Sodium_Core_Util::declareScalarType($len, 'int', 1);
3528 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
3529 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);
3530
3531 /* Input validation: */
3532 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_STREAM_XCHACHA20_NONCEBYTES) {
3533 throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_XCHACHA20_NONCEBYTES long.');
3534 }
3535 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_STREAM_XCHACHA20_KEYBYTES) {
3536 throw new SodiumException('Argument 3 must be CRYPTO_STREAM_XCHACHA20_KEYBYTES long.');
3537 }
3538
3539 if (self::useNewSodiumAPI() && !$dontFallback) {
3540 return sodium_crypto_stream_xchacha20($len, $nonce, $key);
3541 }
3542 if (PHP_INT_SIZE === 4) {
3543 return ParagonIE_Sodium_Core32_XChaCha20::stream($len, $nonce, $key);
3544 }
3545 return ParagonIE_Sodium_Core_XChaCha20::stream($len, $nonce, $key);
3546 }
3547
3548 /**
3549 * DANGER! UNAUTHENTICATED ENCRYPTION!
3550 *
3551 * Unless you are following expert advice, do not use this feature.
3552 *
3553 * Algorithm: XChaCha20
3554 *
3555 * This DOES NOT provide ciphertext integrity.
3556 *
3557 * @param string $message Plaintext message
3558 * @param string $nonce Number to be used Once; must be 24 bytes
3559 * @param string $key Encryption key
3560 * @return string Encrypted text which is vulnerable to chosen-
3561 * ciphertext attacks unless you implement some
3562 * other mitigation to the ciphertext (i.e.
3563 * Encrypt then MAC)
3564 * @param bool $dontFallback
3565 * @throws SodiumException
3566 * @throws TypeError
3567 * @psalm-suppress MixedArgument
3568 */
3569 public static function crypto_stream_xchacha20_xor(
3570 #[\SensitiveParameter]
3571 $message,
3572 $nonce,
3573 #[\SensitiveParameter]
3574 $key,
3575 $dontFallback = false
3576 ) {
3577 /* Type checks: */
3578 ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
3579 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
3580 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);
3581
3582 /* Input validation: */
3583 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_STREAM_XCHACHA20_NONCEBYTES) {
3584 throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_XCHACHA20_NONCEBYTES long.');
3585 }
3586 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_STREAM_XCHACHA20_KEYBYTES) {
3587 throw new SodiumException('Argument 3 must be CRYPTO_SECRETBOX_XCHACHA20_KEYBYTES long.');
3588 }
3589
3590 if (self::useNewSodiumAPI() && !$dontFallback) {
3591 return sodium_crypto_stream_xchacha20_xor($message, $nonce, $key);
3592 }
3593 if (PHP_INT_SIZE === 4) {
3594 return ParagonIE_Sodium_Core32_XChaCha20::streamXorIc($message, $nonce, $key);
3595 }
3596 return ParagonIE_Sodium_Core_XChaCha20::streamXorIc($message, $nonce, $key);
3597 }
3598
3599 /**
3600 * DANGER! UNAUTHENTICATED ENCRYPTION!
3601 *
3602 * Unless you are following expert advice, do not use this feature.
3603 *
3604 * Algorithm: XChaCha20
3605 *
3606 * This DOES NOT provide ciphertext integrity.
3607 *
3608 * @param string $message Plaintext message
3609 * @param string $nonce Number to be used Once; must be 24 bytes
3610 * @param int $counter
3611 * @param string $key Encryption key
3612 * @return string Encrypted text which is vulnerable to chosen-
3613 * ciphertext attacks unless you implement some
3614 * other mitigation to the ciphertext (i.e.
3615 * Encrypt then MAC)
3616 * @param bool $dontFallback
3617 * @throws SodiumException
3618 * @throws TypeError
3619 * @psalm-suppress MixedArgument
3620 */
3621 public static function crypto_stream_xchacha20_xor_ic(
3622 #[\SensitiveParameter]
3623 $message,
3624 $nonce,
3625 $counter,
3626 #[\SensitiveParameter]
3627 $key,
3628 $dontFallback = false
3629 ) {
3630 /* Type checks: */
3631 ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
3632 ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
3633 ParagonIE_Sodium_Core_Util::declareScalarType($counter, 'int', 3);
3634 ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);
3635
3636 /* Input validation: */
3637 if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_STREAM_XCHACHA20_NONCEBYTES) {
3638 throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_XCHACHA20_NONCEBYTES long.');
3639 }
3640 if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_STREAM_XCHACHA20_KEYBYTES) {
3641 throw new SodiumException('Argument 3 must be CRYPTO_SECRETBOX_XCHACHA20_KEYBYTES long.');
3642 }
3643
3644 if (is_callable('sodium_crypto_stream_xchacha20_xor_ic') && !$dontFallback) {
3645 return sodium_crypto_stream_xchacha20_xor_ic($message, $nonce, $counter, $key);
3646 }
3647
3648 $ic = ParagonIE_Sodium_Core_Util::store64_le($counter);
3649 if (PHP_INT_SIZE === 4) {
3650 return ParagonIE_Sodium_Core32_XChaCha20::streamXorIc($message, $nonce, $key, $ic);
3651 }
3652 return ParagonIE_Sodium_Core_XChaCha20::streamXorIc($message, $nonce, $key, $ic);
3653 }
3654
3655 /**
3656 * Return a secure random key for use with crypto_stream_xchacha20
3657 *
3658 * @return string
3659 * @throws Exception
3660 * @throws Error
3661 */
3662 public static function crypto_stream_xchacha20_keygen()
3663 {
3664 return random_bytes(self::CRYPTO_STREAM_XCHACHA20_KEYBYTES);
3665 }
3666
3667 /**
3668 * Cache-timing-safe implementation of hex2bin().
3669 *
3670 * @param string $string Hexadecimal string
3671 * @param string $ignore List of characters to ignore; useful for whitespace
3672 * @return string Raw binary string
3673 * @throws SodiumException
3674 * @throws TypeError
3675 * @psalm-suppress TooFewArguments
3676 * @psalm-suppress MixedArgument
3677 */
3678 public static function hex2bin(
3679 #[\SensitiveParameter]
3680 $string,
3681 $ignore = ''
3682 ) {
3683 /* Type checks: */
3684 ParagonIE_Sodium_Core_Util::declareScalarType($string, 'string', 1);
3685 ParagonIE_Sodium_Core_Util::declareScalarType($ignore, 'string', 2);
3686
3687 if (self::useNewSodiumAPI()) {
3688 if (is_callable('sodium_hex2bin')) {
3689 return (string) sodium_hex2bin($string, $ignore);
3690 }
3691 }
3692 if (self::use_fallback('hex2bin')) {
3693 return (string) call_user_func('\\Sodium\\hex2bin', $string, $ignore);
3694 }
3695 return ParagonIE_Sodium_Core_Util::hex2bin($string, $ignore);
3696 }
3697
3698 /**
3699 * Increase a string (little endian)
3700 *
3701 * @param string $var
3702 *
3703 * @return void
3704 * @throws SodiumException
3705 * @throws TypeError
3706 * @psalm-suppress MixedArgument
3707 */
3708 public static function increment(
3709 #[\SensitiveParameter]
3710 &$var
3711 ) {
3712 /* Type checks: */
3713 ParagonIE_Sodium_Core_Util::declareScalarType($var, 'string', 1);
3714
3715 if (self::useNewSodiumAPI()) {
3716 sodium_increment($var);
3717 return;
3718 }
3719 if (self::use_fallback('increment')) {
3720 $func = '\\Sodium\\increment';
3721 $func($var);
3722 return;
3723 }
3724
3725 $len = ParagonIE_Sodium_Core_Util::strlen($var);
3726 if ($len < 1) {
3727 throw new SodiumException('Argument 1 cannot be empty');
3728 }
3729 $c = 1;
3730 $copy = '';
3731 for ($i = 0; $i < $len; ++$i) {
3732 $c += ParagonIE_Sodium_Core_Util::chrToInt(
3733 ParagonIE_Sodium_Core_Util::substr($var, $i, 1)
3734 );
3735 $copy .= ParagonIE_Sodium_Core_Util::intToChr($c);
3736 $c >>= 8;
3737 }
3738 $var = $copy;
3739 }
3740
3741 /**
3742 * @param string $str
3743 * @return bool
3744 *
3745 * @throws SodiumException
3746 */
3747 public static function is_zero(
3748 #[\SensitiveParameter]
3749 $str
3750 ) {
3751 $d = 0;
3752 for ($i = 0; $i < 32; ++$i) {
3753 $d |= ParagonIE_Sodium_Core_Util::chrToInt($str[$i]);
3754 }
3755 return ((($d - 1) >> 31) & 1) === 1;
3756 }
3757
3758 /**
3759 * The equivalent to the libsodium minor version we aim to be compatible
3760 * with (sans pwhash and memzero).
3761 *
3762 * @return int
3763 */
3764 public static function library_version_major()
3765 {
3766 if (self::useNewSodiumAPI() && defined('SODIUM_LIBRARY_MAJOR_VERSION')) {
3767 return SODIUM_LIBRARY_MAJOR_VERSION;
3768 }
3769 if (self::use_fallback('library_version_major')) {
3770 /** @psalm-suppress UndefinedFunction */
3771 return (int) call_user_func('\\Sodium\\library_version_major');
3772 }
3773 return self::LIBRARY_VERSION_MAJOR;
3774 }
3775
3776 /**
3777 * The equivalent to the libsodium minor version we aim to be compatible
3778 * with (sans pwhash and memzero).
3779 *
3780 * @return int
3781 */
3782 public static function library_version_minor()
3783 {
3784 if (self::useNewSodiumAPI() && defined('SODIUM_LIBRARY_MINOR_VERSION')) {
3785 return SODIUM_LIBRARY_MINOR_VERSION;
3786 }
3787 if (self::use_fallback('library_version_minor')) {
3788 /** @psalm-suppress UndefinedFunction */
3789 return (int) call_user_func('\\Sodium\\library_version_minor');
3790 }
3791 return self::LIBRARY_VERSION_MINOR;
3792 }
3793
3794 /**
3795 * Compare two strings.
3796 *
3797 * @param string $left
3798 * @param string $right
3799 * @return int
3800 * @throws SodiumException
3801 * @throws TypeError
3802 * @psalm-suppress MixedArgument
3803 */
3804 public static function memcmp(
3805 #[\SensitiveParameter]
3806 $left,
3807 #[\SensitiveParameter]
3808 $right
3809 ) {
3810 /* Type checks: */
3811 ParagonIE_Sodium_Core_Util::declareScalarType($left, 'string', 1);
3812 ParagonIE_Sodium_Core_Util::declareScalarType($right, 'string', 2);
3813
3814 if (self::useNewSodiumAPI()) {
3815 return sodium_memcmp($left, $right);
3816 }
3817 if (self::use_fallback('memcmp')) {
3818 return (int) call_user_func('\\Sodium\\memcmp', $left, $right);
3819 }
3820 /** @var string $left */
3821 /** @var string $right */
3822 return ParagonIE_Sodium_Core_Util::memcmp($left, $right);
3823 }
3824
3825 /**
3826 * It's actually not possible to zero memory buffers in PHP. You need the
3827 * native library for that.
3828 *
3829 * @param string|null $var
3830 * @param-out string|null $var
3831 *
3832 * @return void
3833 * @throws SodiumException (Unless libsodium is installed)
3834 * @throws TypeError
3835 * @psalm-suppress TooFewArguments
3836 */
3837 public static function memzero(
3838 #[\SensitiveParameter]
3839 &$var
3840 ) {
3841 /* Type checks: */
3842 ParagonIE_Sodium_Core_Util::declareScalarType($var, 'string', 1);
3843
3844 if (self::useNewSodiumAPI()) {
3845 /** @psalm-suppress MixedArgument */
3846 sodium_memzero($var);
3847 return;
3848 }
3849 if (self::use_fallback('memzero')) {
3850 $func = '\\Sodium\\memzero';
3851 $func($var);
3852 if ($var === null) {
3853 return;
3854 }
3855 }
3856 // This is the best we can do.
3857 throw new SodiumException(
3858 'This is not implemented in sodium_compat, as it is not possible to securely wipe memory from PHP. ' .
3859 'To fix this error, make sure libsodium is installed and the PHP extension is enabled.'
3860 );
3861 }
3862
3863 /**
3864 * @param string $unpadded
3865 * @param int $blockSize
3866 * @param bool $dontFallback
3867 * @return string
3868 * @throws SodiumException
3869 */
3870 public static function pad(
3871 #[\SensitiveParameter]
3872 $unpadded,
3873 $blockSize,
3874 $dontFallback = false
3875 ) {
3876 /* Type checks: */
3877 ParagonIE_Sodium_Core_Util::declareScalarType($unpadded, 'string', 1);
3878 ParagonIE_Sodium_Core_Util::declareScalarType($blockSize, 'int', 2);
3879
3880 $unpadded = (string) $unpadded;
3881 $blockSize = (int) $blockSize;
3882
3883 if (self::useNewSodiumAPI() && !$dontFallback) {
3884 return (string) sodium_pad($unpadded, $blockSize);
3885 }
3886
3887 if ($blockSize <= 0) {
3888 throw new SodiumException(
3889 'block size cannot be less than 1'
3890 );
3891 }
3892 $unpadded_len = ParagonIE_Sodium_Core_Util::strlen($unpadded);
3893 $xpadlen = ($blockSize - 1);
3894 if (($blockSize & ($blockSize - 1)) === 0) {
3895 $xpadlen -= $unpadded_len & ($blockSize - 1);
3896 } else {
3897 $xpadlen -= $unpadded_len % $blockSize;
3898 }
3899
3900 $xpadded_len = $unpadded_len + $xpadlen;
3901 $padded = str_repeat("\0", $xpadded_len - 1);
3902 if ($unpadded_len > 0) {
3903 $st = 1;
3904 $i = 0;
3905 $k = $unpadded_len;
3906 for ($j = 0; $j <= $xpadded_len; ++$j) {
3907 $i = (int) $i;
3908 $k = (int) $k;
3909 $st = (int) $st;
3910 if ($j >= $unpadded_len) {
3911 $padded[$j] = "\0";
3912 } else {
3913 $padded[$j] = $unpadded[$j];
3914 }
3915 /** @var int $k */
3916 $k -= $st;
3917 $st = (int) (~(
3918 (
3919 (
3920 ($k >> 48)
3921 |
3922 ($k >> 32)
3923 |
3924 ($k >> 16)
3925 |
3926 $k
3927 ) - 1
3928 ) >> 16
3929 )
3930 ) & 1;
3931 $i += $st;
3932 }
3933 }
3934
3935 $mask = 0;
3936 $tail = $xpadded_len;
3937 for ($i = 0; $i < $blockSize; ++$i) {
3938 # barrier_mask = (unsigned char)
3939 # (((i ^ xpadlen) - 1U) >> ((sizeof(size_t) - 1U) * CHAR_BIT));
3940 $barrier_mask = (($i ^ $xpadlen) -1) >> ((PHP_INT_SIZE << 3) - 1);
3941 # tail[-i] = (tail[-i] & mask) | (0x80 & barrier_mask);
3942 $padded[$tail - $i] = ParagonIE_Sodium_Core_Util::intToChr(
3943 (ParagonIE_Sodium_Core_Util::chrToInt($padded[$tail - $i]) & $mask)
3944 |
3945 (0x80 & $barrier_mask)
3946 );
3947 # mask |= barrier_mask;
3948 $mask |= $barrier_mask;
3949 }
3950 return $padded;
3951 }
3952
3953 /**
3954 * @param string $padded
3955 * @param int $blockSize
3956 * @param bool $dontFallback
3957 * @return string
3958 * @throws SodiumException
3959 */
3960 public static function unpad(
3961 #[\SensitiveParameter]
3962 $padded,
3963 $blockSize,
3964 $dontFallback = false
3965 ) {
3966 /* Type checks: */
3967 ParagonIE_Sodium_Core_Util::declareScalarType($padded, 'string', 1);
3968 ParagonIE_Sodium_Core_Util::declareScalarType($blockSize, 'int', 2);
3969
3970 $padded = (string) $padded;
3971 $blockSize = (int) $blockSize;
3972
3973 if (self::useNewSodiumAPI() && !$dontFallback) {
3974 return (string) sodium_unpad($padded, $blockSize);
3975 }
3976 if ($blockSize <= 0) {
3977 throw new SodiumException('block size cannot be less than 1');
3978 }
3979 $padded_len = ParagonIE_Sodium_Core_Util::strlen($padded);
3980 if ($padded_len < $blockSize) {
3981 throw new SodiumException('invalid padding');
3982 }
3983
3984 # tail = &padded[padded_len - 1U];
3985 $tail = $padded_len - 1;
3986
3987 $acc = 0;
3988 $valid = 0;
3989 $pad_len = 0;
3990
3991 $found = 0;
3992 for ($i = 0; $i < $blockSize; ++$i) {
3993 # c = tail[-i];
3994 $c = ParagonIE_Sodium_Core_Util::chrToInt($padded[$tail - $i]);
3995
3996 # is_barrier =
3997 # (( (acc - 1U) & (pad_len - 1U) & ((c ^ 0x80) - 1U) ) >> 8) & 1U;
3998 $is_barrier = (
3999 (
4000 ($acc - 1) & ($pad_len - 1) & (($c ^ 80) - 1)
4001 ) >> 7
4002 ) & 1;
4003 $is_barrier &= ~$found;
4004 $found |= $is_barrier;
4005
4006 # acc |= c;
4007 $acc |= $c;
4008
4009 # pad_len |= i & (1U + ~is_barrier);
4010 $pad_len |= $i & (1 + ~$is_barrier);
4011
4012 # valid |= (unsigned char) is_barrier;
4013 $valid |= ($is_barrier & 0xff);
4014 }
4015 # unpadded_len = padded_len - 1U - pad_len;
4016 $unpadded_len = $padded_len - 1 - $pad_len;
4017 if ($valid !== 1) {
4018 throw new SodiumException('invalid padding');
4019 }
4020 return ParagonIE_Sodium_Core_Util::substr($padded, 0, $unpadded_len);
4021 }
4022
4023 /**
4024 * Will sodium_compat run fast on the current hardware and PHP configuration?
4025 *
4026 * @return bool
4027 */
4028 public static function polyfill_is_fast()
4029 {
4030 if (extension_loaded('sodium')) {
4031 return true;
4032 }
4033 if (extension_loaded('libsodium')) {
4034 return true;
4035 }
4036 return PHP_INT_SIZE === 8;
4037 }
4038
4039 /**
4040 * Generate a string of bytes from the kernel's CSPRNG.
4041 * Proudly uses /dev/urandom (if getrandom(2) is not available).
4042 *
4043 * @param int $numBytes
4044 * @return string
4045 * @throws Exception
4046 * @throws TypeError
4047 */
4048 public static function randombytes_buf($numBytes)
4049 {
4050 /* Type checks: */
4051 if (!is_int($numBytes)) {
4052 if (is_numeric($numBytes)) {
4053 $numBytes = (int) $numBytes;
4054 } else {
4055 throw new TypeError(
4056 'Argument 1 must be an integer, ' . gettype($numBytes) . ' given.'
4057 );
4058 }
4059 }
4060 /** @var positive-int $numBytes */
4061 if (self::use_fallback('randombytes_buf')) {
4062 return (string) call_user_func('\\Sodium\\randombytes_buf', $numBytes);
4063 }
4064 if ($numBytes < 0) {
4065 throw new SodiumException("Number of bytes must be a positive integer");
4066 }
4067 return random_bytes($numBytes);
4068 }
4069
4070 /**
4071 * Generate an integer between 0 and $range (non-inclusive).
4072 *
4073 * @param int $range
4074 * @return int
4075 * @throws Exception
4076 * @throws Error
4077 * @throws TypeError
4078 */
4079 public static function randombytes_uniform($range)
4080 {
4081 /* Type checks: */
4082 if (!is_int($range)) {
4083 if (is_numeric($range)) {
4084 $range = (int) $range;
4085 } else {
4086 throw new TypeError(
4087 'Argument 1 must be an integer, ' . gettype($range) . ' given.'
4088 );
4089 }
4090 }
4091 if (self::use_fallback('randombytes_uniform')) {
4092 return (int) call_user_func('\\Sodium\\randombytes_uniform', $range);
4093 }
4094 return random_int(0, $range - 1);
4095 }
4096
4097 /**
4098 * Generate a random 16-bit integer.
4099 *
4100 * @return int
4101 * @throws Exception
4102 * @throws Error
4103 * @throws TypeError
4104 */
4105 public static function randombytes_random16()
4106 {
4107 if (self::use_fallback('randombytes_random16')) {
4108 return (int) call_user_func('\\Sodium\\randombytes_random16');
4109 }
4110 return random_int(0, 65535);
4111 }
4112
4113 /**
4114 * @param string $p
4115 * @param bool $dontFallback
4116 * @return bool
4117 * @throws SodiumException
4118 */
4119 public static function ristretto255_is_valid_point(
4120 #[\SensitiveParameter]
4121 $p,
4122 $dontFallback = false
4123 ) {
4124 if (self::useNewSodiumAPI() && !$dontFallback) {
4125 return sodium_crypto_core_ristretto255_is_valid_point($p);
4126 }
4127 try {
4128 $r = ParagonIE_Sodium_Core_Ristretto255::ristretto255_frombytes($p);
4129 return $r['res'] === 0 &&
4130 ParagonIE_Sodium_Core_Ristretto255::ristretto255_point_is_canonical($p) === 1;
4131 } catch (SodiumException $ex) {
4132 if ($ex->getMessage() === 'S is not canonical') {
4133 return false;
4134 }
4135 throw $ex;
4136 }
4137 }
4138
4139 /**
4140 * @param string $p
4141 * @param string $q
4142 * @param bool $dontFallback
4143 * @return string
4144 * @throws SodiumException
4145 */
4146 public static function ristretto255_add(
4147 #[\SensitiveParameter]
4148 $p,
4149 #[\SensitiveParameter]
4150 $q,
4151 $dontFallback = false
4152 ) {
4153 if (self::useNewSodiumAPI() && !$dontFallback) {
4154 return sodium_crypto_core_ristretto255_add($p, $q);
4155 }
4156 return ParagonIE_Sodium_Core_Ristretto255::ristretto255_add($p, $q);
4157 }
4158
4159 /**
4160 * @param string $p
4161 * @param string $q
4162 * @param bool $dontFallback
4163 * @return string
4164 * @throws SodiumException
4165 */
4166 public static function ristretto255_sub(
4167 #[\SensitiveParameter]
4168 $p,
4169 #[\SensitiveParameter]
4170 $q,
4171 $dontFallback = false
4172 ) {
4173 if (self::useNewSodiumAPI() && !$dontFallback) {
4174 return sodium_crypto_core_ristretto255_sub($p, $q);
4175 }
4176 return ParagonIE_Sodium_Core_Ristretto255::ristretto255_sub($p, $q);
4177 }
4178
4179 /**
4180 * @param string $r
4181 * @param bool $dontFallback
4182 * @return string
4183 *
4184 * @throws SodiumException
4185 */
4186 public static function ristretto255_from_hash(
4187 #[\SensitiveParameter]
4188 $r,
4189 $dontFallback = false
4190 ) {
4191 if (self::useNewSodiumAPI() && !$dontFallback) {
4192 return sodium_crypto_core_ristretto255_from_hash($r);
4193 }
4194 return ParagonIE_Sodium_Core_Ristretto255::ristretto255_from_hash($r);
4195 }
4196
4197 /**
4198 * @param bool $dontFallback
4199 * @return string
4200 *
4201 * @throws SodiumException
4202 */
4203 public static function ristretto255_random($dontFallback = false)
4204 {
4205 if (self::useNewSodiumAPI() && !$dontFallback) {
4206 return sodium_crypto_core_ristretto255_random();
4207 }
4208 return ParagonIE_Sodium_Core_Ristretto255::ristretto255_random();
4209 }
4210
4211 /**
4212 * @param bool $dontFallback
4213 * @return string
4214 *
4215 * @throws SodiumException
4216 */
4217 public static function ristretto255_scalar_random($dontFallback = false)
4218 {
4219 if (self::useNewSodiumAPI() && !$dontFallback) {
4220 return sodium_crypto_core_ristretto255_scalar_random();
4221 }
4222 return ParagonIE_Sodium_Core_Ristretto255::ristretto255_scalar_random();
4223 }
4224
4225 /**
4226 * @param string $s
4227 * @param bool $dontFallback
4228 * @return string
4229 * @throws SodiumException
4230 */
4231 public static function ristretto255_scalar_invert(
4232 #[\SensitiveParameter]
4233 $s,
4234 $dontFallback = false
4235 ) {
4236 if (self::useNewSodiumAPI() && !$dontFallback) {
4237 return sodium_crypto_core_ristretto255_scalar_invert($s);
4238 }
4239 return ParagonIE_Sodium_Core_Ristretto255::ristretto255_scalar_invert($s);
4240 }
4241 /**
4242 * @param string $s
4243 * @param bool $dontFallback
4244 * @return string
4245 * @throws SodiumException
4246 */
4247 public static function ristretto255_scalar_negate(
4248 #[\SensitiveParameter]
4249 $s,
4250 $dontFallback = false
4251 ) {
4252 if (self::useNewSodiumAPI() && !$dontFallback) {
4253 return sodium_crypto_core_ristretto255_scalar_negate($s);
4254 }
4255 return ParagonIE_Sodium_Core_Ristretto255::ristretto255_scalar_negate($s);
4256 }
4257
4258 /**
4259 * @param string $s
4260 * @param bool $dontFallback
4261 * @return string
4262 * @throws SodiumException
4263 */
4264 public static function ristretto255_scalar_complement(
4265 #[\SensitiveParameter]
4266 $s,
4267 $dontFallback = false
4268 ) {
4269 if (self::useNewSodiumAPI() && !$dontFallback) {
4270 return sodium_crypto_core_ristretto255_scalar_complement($s);
4271 }
4272 return ParagonIE_Sodium_Core_Ristretto255::ristretto255_scalar_complement($s);
4273 }
4274
4275 /**
4276 * @param string $x
4277 * @param string $y
4278 * @param bool $dontFallback
4279 * @return string
4280 * @throws SodiumException
4281 */
4282 public static function ristretto255_scalar_add(
4283 #[\SensitiveParameter]
4284 $x,
4285 #[\SensitiveParameter]
4286 $y,
4287 $dontFallback = false
4288 ) {
4289 if (self::useNewSodiumAPI() && !$dontFallback) {
4290 return sodium_crypto_core_ristretto255_scalar_add($x, $y);
4291 }
4292 return ParagonIE_Sodium_Core_Ristretto255::ristretto255_scalar_add($x, $y);
4293 }
4294
4295 /**
4296 * @param string $x
4297 * @param string $y
4298 * @param bool $dontFallback
4299 * @return string
4300 * @throws SodiumException
4301 */
4302 public static function ristretto255_scalar_sub(
4303 #[\SensitiveParameter]
4304 $x,
4305 #[\SensitiveParameter]
4306 $y,
4307 $dontFallback = false
4308 ) {
4309 if (self::useNewSodiumAPI() && !$dontFallback) {
4310 return sodium_crypto_core_ristretto255_scalar_sub($x, $y);
4311 }
4312 return ParagonIE_Sodium_Core_Ristretto255::ristretto255_scalar_sub($x, $y);
4313 }
4314
4315 /**
4316 * @param string $x
4317 * @param string $y
4318 * @param bool $dontFallback
4319 * @return string
4320 * @throws SodiumException
4321 */
4322 public static function ristretto255_scalar_mul(
4323 #[\SensitiveParameter]
4324 $x,
4325 #[\SensitiveParameter]
4326 $y,
4327 $dontFallback = false
4328 ) {
4329 if (self::useNewSodiumAPI() && !$dontFallback) {
4330 return sodium_crypto_core_ristretto255_scalar_mul($x, $y);
4331 }
4332 return ParagonIE_Sodium_Core_Ristretto255::ristretto255_scalar_mul($x, $y);
4333 }
4334
4335 /**
4336 * @param string $n
4337 * @param string $p
4338 * @param bool $dontFallback
4339 * @return string
4340 * @throws SodiumException
4341 */
4342 public static function scalarmult_ristretto255(
4343 #[\SensitiveParameter]
4344 $n,
4345 #[\SensitiveParameter]
4346 $p,
4347 $dontFallback = false
4348 ) {
4349 if (self::useNewSodiumAPI() && !$dontFallback) {
4350 return sodium_crypto_scalarmult_ristretto255($n, $p);
4351 }
4352 return ParagonIE_Sodium_Core_Ristretto255::scalarmult_ristretto255($n, $p);
4353 }
4354
4355 /**
4356 * @param string $n
4357 * @param string $p
4358 * @param bool $dontFallback
4359 * @return string
4360 * @throws SodiumException
4361 */
4362 public static function scalarmult_ristretto255_base(
4363 #[\SensitiveParameter]
4364 $n,
4365 $dontFallback = false
4366 ) {
4367 if (self::useNewSodiumAPI() && !$dontFallback) {
4368 return sodium_crypto_scalarmult_ristretto255_base($n);
4369 }
4370 return ParagonIE_Sodium_Core_Ristretto255::scalarmult_ristretto255_base($n);
4371 }
4372
4373 /**
4374 * @param string $s
4375 * @param bool $dontFallback
4376 * @return string
4377 * @throws SodiumException
4378 */
4379 public static function ristretto255_scalar_reduce(
4380 #[\SensitiveParameter]
4381 $s,
4382 $dontFallback = false
4383 ) {
4384 if (self::useNewSodiumAPI() && !$dontFallback) {
4385 return sodium_crypto_core_ristretto255_scalar_reduce($s);
4386 }
4387 return ParagonIE_Sodium_Core_Ristretto255::sc_reduce($s);
4388 }
4389
4390 /**
4391 * Runtime testing method for 32-bit platforms.
4392 *
4393 * Usage: If runtime_speed_test() returns FALSE, then our 32-bit
4394 * implementation is to slow to use safely without risking timeouts.
4395 * If this happens, install sodium from PECL to get acceptable
4396 * performance.
4397 *
4398 * @param int $iterations Number of multiplications to attempt
4399 * @param int $maxTimeout Milliseconds
4400 * @return bool TRUE if we're fast enough, FALSE is not
4401 * @throws SodiumException
4402 */
4403 public static function runtime_speed_test($iterations, $maxTimeout)
4404 {
4405 if (self::polyfill_is_fast()) {
4406 return true;
4407 }
4408 /** @var float $end */
4409 $end = 0.0;
4410 /** @var float $start */
4411 $start = microtime(true);
4412 /** @var ParagonIE_Sodium_Core32_Int64 $a */
4413 $a = ParagonIE_Sodium_Core32_Int64::fromInt(random_int(3, 1 << 16));
4414 for ($i = 0; $i < $iterations; ++$i) {
4415 /** @var ParagonIE_Sodium_Core32_Int64 $b */
4416 $b = ParagonIE_Sodium_Core32_Int64::fromInt(random_int(3, 1 << 16));
4417 $a->mulInt64($b);
4418 }
4419 /** @var float $end */
4420 $end = microtime(true);
4421 /** @var int $diff */
4422 $diff = (int) ceil(($end - $start) * 1000);
4423 return $diff < $maxTimeout;
4424 }
4425
4426 /**
4427 * Add two numbers (little-endian unsigned), storing the value in the first
4428 * parameter.
4429 *
4430 * This mutates $val.
4431 *
4432 * @param string $val
4433 * @param string $addv
4434 * @return void
4435 * @throws SodiumException
4436 */
4437 public static function sub(
4438 #[\SensitiveParameter]
4439 &$val,
4440 #[\SensitiveParameter]
4441 $addv
4442 ) {
4443 $val_len = ParagonIE_Sodium_Core_Util::strlen($val);
4444 $addv_len = ParagonIE_Sodium_Core_Util::strlen($addv);
4445 if ($val_len !== $addv_len) {
4446 throw new SodiumException('values must have the same length');
4447 }
4448 $A = ParagonIE_Sodium_Core_Util::stringToIntArray($val);
4449 $B = ParagonIE_Sodium_Core_Util::stringToIntArray($addv);
4450
4451 $c = 0;
4452 for ($i = 0; $i < $val_len; $i++) {
4453 $c = ($A[$i] - $B[$i] - $c);
4454 $A[$i] = ($c & 0xff);
4455 $c = ($c >> 8) & 1;
4456 }
4457 $val = ParagonIE_Sodium_Core_Util::intArrayToString($A);
4458 }
4459
4460 /**
4461 * This emulates libsodium's version_string() function, except ours is
4462 * prefixed with 'polyfill-'.
4463 *
4464 * @return string
4465 * @psalm-suppress MixedInferredReturnType
4466 * @psalm-suppress UndefinedFunction
4467 */
4468 public static function version_string()
4469 {
4470 if (self::useNewSodiumAPI()) {
4471 return (string) sodium_version_string();
4472 }
4473 if (self::use_fallback('version_string')) {
4474 return (string) call_user_func('\\Sodium\\version_string');
4475 }
4476 return (string) self::VERSION_STRING;
4477 }
4478
4479 /**
4480 * Should we use the libsodium core function instead?
4481 * This is always a good idea, if it's available. (Unless we're in the
4482 * middle of running our unit test suite.)
4483 *
4484 * If ext/libsodium is available, use it. Return TRUE.
4485 * Otherwise, we have to use the code provided herein. Return FALSE.
4486 *
4487 * @param string $sodium_func_name
4488 *
4489 * @return bool
4490 */
4491 protected static function use_fallback($sodium_func_name = '')
4492 {
4493 static $res = null;
4494 if ($res === null) {
4495 $res = extension_loaded('libsodium') && PHP_VERSION_ID >= 50300;
4496 }
4497 if ($res === false) {
4498 // No libsodium installed
4499 return false;
4500 }
4501 if (self::$disableFallbackForUnitTests) {
4502 // Don't fallback. Use the PHP implementation.
4503 return false;
4504 }
4505 if (!empty($sodium_func_name)) {
4506 return is_callable('\\Sodium\\' . $sodium_func_name);
4507 }
4508 return true;
4509 }
4510
4511 /**
4512 * Libsodium as implemented in PHP 7.2
4513 * and/or ext/sodium (via PECL)
4514 *
4515 * @ref https://wiki.php.net/rfc/libsodium
4516 * @return bool
4517 */
4518 protected static function useNewSodiumAPI()
4519 {
4520 static $res = null;
4521 if ($res === null) {
4522 $res = PHP_VERSION_ID >= 70000 && extension_loaded('sodium');
4523 }
4524 if (self::$disableFallbackForUnitTests) {
4525 // Don't fallback. Use the PHP implementation.
4526 return false;
4527 }
4528 return (bool) $res;
4529 }
4530}
4531