Browse code

Added README.md appinfo/info.xml appinfo/signature.json lib/Controller/AuthorApiController.php and the providers directory

DoubleBastionAdmin authored on 20/08/2022 16:33:00
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,1547 @@
1
+<?php
2
+
3
+if (class_exists('ParagonIE_Sodium_File', false)) {
4
+    return;
5
+}
6
+/**
7
+ * Class ParagonIE_Sodium_File
8
+ */
9
+class ParagonIE_Sodium_File extends ParagonIE_Sodium_Core_Util
10
+{
11
+    /* PHP's default buffer size is 8192 for fread()/fwrite(). */
12
+    const BUFFER_SIZE = 8192;
13
+
14
+    /**
15
+     * Box a file (rather than a string). Uses less memory than
16
+     * ParagonIE_Sodium_Compat::crypto_box(), but produces
17
+     * the same result.
18
+     *
19
+     * @param string $inputFile  Absolute path to a file on the filesystem
20
+     * @param string $outputFile Absolute path to a file on the filesystem
21
+     * @param string $nonce      Number to be used only once
22
+     * @param string $keyPair    ECDH secret key and ECDH public key concatenated
23
+     *
24
+     * @return bool
25
+     * @throws SodiumException
26
+     * @throws TypeError
27
+     */
28
+    public static function box($inputFile, $outputFile, $nonce, $keyPair)
29
+    {
30
+        /* Type checks: */
31
+        if (!is_string($inputFile)) {
32
+            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
33
+        }
34
+        if (!is_string($outputFile)) {
35
+            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
36
+        }
37
+        if (!is_string($nonce)) {
38
+            throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
39
+        }
40
+
41
+        /* Input validation: */
42
+        if (!is_string($keyPair)) {
43
+            throw new TypeError('Argument 4 must be a string, ' . gettype($keyPair) . ' given.');
44
+        }
45
+        if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES) {
46
+            throw new TypeError('Argument 3 must be CRYPTO_BOX_NONCEBYTES bytes');
47
+        }
48
+        if (self::strlen($keyPair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
49
+            throw new TypeError('Argument 4 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
50
+        }
51
+
52
+        /** @var int $size */
53
+        $size = filesize($inputFile);
54
+        if (!is_int($size)) {
55
+            throw new SodiumException('Could not obtain the file size');
56
+        }
57
+
58
+        /** @var resource $ifp */
59
+        $ifp = fopen($inputFile, 'rb');
60
+        if (!is_resource($ifp)) {
61
+            throw new SodiumException('Could not open input file for reading');
62
+        }
63
+
64
+        /** @var resource $ofp */
65
+        $ofp = fopen($outputFile, 'wb');
66
+        if (!is_resource($ofp)) {
67
+            fclose($ifp);
68
+            throw new SodiumException('Could not open output file for writing');
69
+        }
70
+
71
+        $res = self::box_encrypt($ifp, $ofp, $size, $nonce, $keyPair);
72
+        fclose($ifp);
73
+        fclose($ofp);
74
+        return $res;
75
+    }
76
+
77
+    /**
78
+     * Open a boxed file (rather than a string). Uses less memory than
79
+     * ParagonIE_Sodium_Compat::crypto_box_open(), but produces
80
+     * the same result.
81
+     *
82
+     * Warning: Does not protect against TOCTOU attacks. You should
83
+     * just load the file into memory and use crypto_box_open() if
84
+     * you are worried about those.
85
+     *
86
+     * @param string $inputFile
87
+     * @param string $outputFile
88
+     * @param string $nonce
89
+     * @param string $keypair
90
+     * @return bool
91
+     * @throws SodiumException
92
+     * @throws TypeError
93
+     */
94
+    public static function box_open($inputFile, $outputFile, $nonce, $keypair)
95
+    {
96
+        /* Type checks: */
97
+        if (!is_string($inputFile)) {
98
+            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
99
+        }
100
+        if (!is_string($outputFile)) {
101
+            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
102
+        }
103
+        if (!is_string($nonce)) {
104
+            throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
105
+        }
106
+        if (!is_string($keypair)) {
107
+            throw new TypeError('Argument 4 must be a string, ' . gettype($keypair) . ' given.');
108
+        }
109
+
110
+        /* Input validation: */
111
+        if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES) {
112
+            throw new TypeError('Argument 4 must be CRYPTO_BOX_NONCEBYTES bytes');
113
+        }
114
+        if (self::strlen($keypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
115
+            throw new TypeError('Argument 4 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
116
+        }
117
+
118
+        /** @var int $size */
119
+        $size = filesize($inputFile);
120
+        if (!is_int($size)) {
121
+            throw new SodiumException('Could not obtain the file size');
122
+        }
123
+
124
+        /** @var resource $ifp */
125
+        $ifp = fopen($inputFile, 'rb');
126
+        if (!is_resource($ifp)) {
127
+            throw new SodiumException('Could not open input file for reading');
128
+        }
129
+
130
+        /** @var resource $ofp */
131
+        $ofp = fopen($outputFile, 'wb');
132
+        if (!is_resource($ofp)) {
133
+            fclose($ifp);
134
+            throw new SodiumException('Could not open output file for writing');
135
+        }
136
+
137
+        $res = self::box_decrypt($ifp, $ofp, $size, $nonce, $keypair);
138
+        fclose($ifp);
139
+        fclose($ofp);
140
+        try {
141
+            ParagonIE_Sodium_Compat::memzero($nonce);
142
+            ParagonIE_Sodium_Compat::memzero($ephKeypair);
143
+        } catch (SodiumException $ex) {
144
+            if (isset($ephKeypair)) {
145
+                unset($ephKeypair);
146
+            }
147
+        }
148
+        return $res;
149
+    }
150
+
151
+    /**
152
+     * Seal a file (rather than a string). Uses less memory than
153
+     * ParagonIE_Sodium_Compat::crypto_box_seal(), but produces
154
+     * the same result.
155
+     *
156
+     * @param string $inputFile  Absolute path to a file on the filesystem
157
+     * @param string $outputFile Absolute path to a file on the filesystem
158
+     * @param string $publicKey  ECDH public key
159
+     *
160
+     * @return bool
161
+     * @throws SodiumException
162
+     * @throws TypeError
163
+     */
164
+    public static function box_seal($inputFile, $outputFile, $publicKey)
165
+    {
166
+        /* Type checks: */
167
+        if (!is_string($inputFile)) {
168
+            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
169
+        }
170
+        if (!is_string($outputFile)) {
171
+            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
172
+        }
173
+        if (!is_string($publicKey)) {
174
+            throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
175
+        }
176
+
177
+        /* Input validation: */
178
+        if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
179
+            throw new TypeError('Argument 3 must be CRYPTO_BOX_PUBLICKEYBYTES bytes');
180
+        }
181
+
182
+        /** @var int $size */
183
+        $size = filesize($inputFile);
184
+        if (!is_int($size)) {
185
+            throw new SodiumException('Could not obtain the file size');
186
+        }
187
+
188
+        /** @var resource $ifp */
189
+        $ifp = fopen($inputFile, 'rb');
190
+        if (!is_resource($ifp)) {
191
+            throw new SodiumException('Could not open input file for reading');
192
+        }
193
+
194
+        /** @var resource $ofp */
195
+        $ofp = fopen($outputFile, 'wb');
196
+        if (!is_resource($ofp)) {
197
+            fclose($ifp);
198
+            throw new SodiumException('Could not open output file for writing');
199
+        }
200
+
201
+        /** @var string $ephKeypair */
202
+        $ephKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair();
203
+
204
+        /** @var string $msgKeypair */
205
+        $msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
206
+            ParagonIE_Sodium_Compat::crypto_box_secretkey($ephKeypair),
207
+            $publicKey
208
+        );
209
+
210
+        /** @var string $ephemeralPK */
211
+        $ephemeralPK = ParagonIE_Sodium_Compat::crypto_box_publickey($ephKeypair);
212
+
213
+        /** @var string $nonce */
214
+        $nonce = ParagonIE_Sodium_Compat::crypto_generichash(
215
+            $ephemeralPK . $publicKey,
216
+            '',
217
+            24
218
+        );
219
+
220
+        /** @var int $firstWrite */
221
+        $firstWrite = fwrite(
222
+            $ofp,
223
+            $ephemeralPK,
224
+            ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES
225
+        );
226
+        if (!is_int($firstWrite)) {
227
+            fclose($ifp);
228
+            fclose($ofp);
229
+            ParagonIE_Sodium_Compat::memzero($ephKeypair);
230
+            throw new SodiumException('Could not write to output file');
231
+        }
232
+        if ($firstWrite !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
233
+            ParagonIE_Sodium_Compat::memzero($ephKeypair);
234
+            fclose($ifp);
235
+            fclose($ofp);
236
+            throw new SodiumException('Error writing public key to output file');
237
+        }
238
+
239
+        $res = self::box_encrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
240
+        fclose($ifp);
241
+        fclose($ofp);
242
+        try {
243
+            ParagonIE_Sodium_Compat::memzero($nonce);
244
+            ParagonIE_Sodium_Compat::memzero($ephKeypair);
245
+        } catch (SodiumException $ex) {
246
+            /** @psalm-suppress PossiblyUndefinedVariable */
247
+            unset($ephKeypair);
248
+        }
249
+        return $res;
250
+    }
251
+
252
+    /**
253
+     * Open a sealed file (rather than a string). Uses less memory than
254
+     * ParagonIE_Sodium_Compat::crypto_box_seal_open(), but produces
255
+     * the same result.
256
+     *
257
+     * Warning: Does not protect against TOCTOU attacks. You should
258
+     * just load the file into memory and use crypto_box_seal_open() if
259
+     * you are worried about those.
260
+     *
261
+     * @param string $inputFile
262
+     * @param string $outputFile
263
+     * @param string $ecdhKeypair
264
+     * @return bool
265
+     * @throws SodiumException
266
+     * @throws TypeError
267
+     */
268
+    public static function box_seal_open($inputFile, $outputFile, $ecdhKeypair)
269
+    {
270
+        /* Type checks: */
271
+        if (!is_string($inputFile)) {
272
+            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
273
+        }
274
+        if (!is_string($outputFile)) {
275
+            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
276
+        }
277
+        if (!is_string($ecdhKeypair)) {
278
+            throw new TypeError('Argument 3 must be a string, ' . gettype($ecdhKeypair) . ' given.');
279
+        }
280
+
281
+        /* Input validation: */
282
+        if (self::strlen($ecdhKeypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
283
+            throw new TypeError('Argument 3 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
284
+        }
285
+
286
+        $publicKey = ParagonIE_Sodium_Compat::crypto_box_publickey($ecdhKeypair);
287
+
288
+        /** @var int $size */
289
+        $size = filesize($inputFile);
290
+        if (!is_int($size)) {
291
+            throw new SodiumException('Could not obtain the file size');
292
+        }
293
+
294
+        /** @var resource $ifp */
295
+        $ifp = fopen($inputFile, 'rb');
296
+        if (!is_resource($ifp)) {
297
+            throw new SodiumException('Could not open input file for reading');
298
+        }
299
+
300
+        /** @var resource $ofp */
301
+        $ofp = fopen($outputFile, 'wb');
302
+        if (!is_resource($ofp)) {
303
+            fclose($ifp);
304
+            throw new SodiumException('Could not open output file for writing');
305
+        }
306
+
307
+        $ephemeralPK = fread($ifp, ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES);
308
+        if (!is_string($ephemeralPK)) {
309
+            throw new SodiumException('Could not read input file');
310
+        }
311
+        if (self::strlen($ephemeralPK) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
312
+            fclose($ifp);
313
+            fclose($ofp);
314
+            throw new SodiumException('Could not read public key from sealed file');
315
+        }
316
+
317
+        $nonce = ParagonIE_Sodium_Compat::crypto_generichash(
318
+            $ephemeralPK . $publicKey,
319
+            '',
320
+            24
321
+        );
322
+        $msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
323
+            ParagonIE_Sodium_Compat::crypto_box_secretkey($ecdhKeypair),
324
+            $ephemeralPK
325
+        );
326
+
327
+        $res = self::box_decrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
328
+        fclose($ifp);
329
+        fclose($ofp);
330
+        try {
331
+            ParagonIE_Sodium_Compat::memzero($nonce);
332
+            ParagonIE_Sodium_Compat::memzero($ephKeypair);
333
+        } catch (SodiumException $ex) {
334
+            if (isset($ephKeypair)) {
335
+                unset($ephKeypair);
336
+            }
337
+        }
338
+        return $res;
339
+    }
340
+
341
+    /**
342
+     * Calculate the BLAKE2b hash of a file.
343
+     *
344
+     * @param string      $filePath     Absolute path to a file on the filesystem
345
+     * @param string|null $key          BLAKE2b key
346
+     * @param int         $outputLength Length of hash output
347
+     *
348
+     * @return string                   BLAKE2b hash
349
+     * @throws SodiumException
350
+     * @throws TypeError
351
+     * @psalm-suppress FailedTypeResolution
352
+     */
353
+    public static function generichash($filePath, $key = '', $outputLength = 32)
354
+    {
355
+        /* Type checks: */
356
+        if (!is_string($filePath)) {
357
+            throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
358
+        }
359
+        if (!is_string($key)) {
360
+            if (is_null($key)) {
361
+                $key = '';
362
+            } else {
363
+                throw new TypeError('Argument 2 must be a string, ' . gettype($key) . ' given.');
364
+            }
365
+        }
366
+        if (!is_int($outputLength)) {
367
+            if (!is_numeric($outputLength)) {
368
+                throw new TypeError('Argument 3 must be an integer, ' . gettype($outputLength) . ' given.');
369
+            }
370
+            $outputLength = (int) $outputLength;
371
+        }
372
+
373
+        /* Input validation: */
374
+        if (!empty($key)) {
375
+            if (self::strlen($key) < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MIN) {
376
+                throw new TypeError('Argument 2 must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes');
377
+            }
378
+            if (self::strlen($key) > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MAX) {
379
+                throw new TypeError('Argument 2 must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes');
380
+            }
381
+        }
382
+        if ($outputLength < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MIN) {
383
+            throw new SodiumException('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MIN');
384
+        }
385
+        if ($outputLength > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MAX) {
386
+            throw new SodiumException('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MAX');
387
+        }
388
+
389
+        /** @var int $size */
390
+        $size = filesize($filePath);
391
+        if (!is_int($size)) {
392
+            throw new SodiumException('Could not obtain the file size');
393
+        }
394
+
395
+        /** @var resource $fp */
396
+        $fp = fopen($filePath, 'rb');
397
+        if (!is_resource($fp)) {
398
+            throw new SodiumException('Could not open input file for reading');
399
+        }
400
+        $ctx = ParagonIE_Sodium_Compat::crypto_generichash_init($key, $outputLength);
401
+        while ($size > 0) {
402
+            $blockSize = $size > 64
403
+                ? 64
404
+                : $size;
405
+            $read = fread($fp, $blockSize);
406
+            if (!is_string($read)) {
407
+                throw new SodiumException('Could not read input file');
408
+            }
409
+            ParagonIE_Sodium_Compat::crypto_generichash_update($ctx, $read);
410
+            $size -= $blockSize;
411
+        }
412
+
413
+        fclose($fp);
414
+        return ParagonIE_Sodium_Compat::crypto_generichash_final($ctx, $outputLength);
415
+    }
416
+
417
+    /**
418
+     * Encrypt a file (rather than a string). Uses less memory than
419
+     * ParagonIE_Sodium_Compat::crypto_secretbox(), but produces
420
+     * the same result.
421
+     *
422
+     * @param string $inputFile  Absolute path to a file on the filesystem
423
+     * @param string $outputFile Absolute path to a file on the filesystem
424
+     * @param string $nonce      Number to be used only once
425
+     * @param string $key        Encryption key
426
+     *
427
+     * @return bool
428
+     * @throws SodiumException
429
+     * @throws TypeError
430
+     */
431
+    public static function secretbox($inputFile, $outputFile, $nonce, $key)
432
+    {
433
+        /* Type checks: */
434
+        if (!is_string($inputFile)) {
435
+            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given..');
436
+        }
437
+        if (!is_string($outputFile)) {
438
+            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
439
+        }
440
+        if (!is_string($nonce)) {
441
+            throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
442
+        }
443
+
444
+        /* Input validation: */
445
+        if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
446
+            throw new TypeError('Argument 3 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
447
+        }
448
+        if (!is_string($key)) {
449
+            throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
450
+        }
451
+        if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
452
+            throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_KEYBYTES bytes');
453
+        }
454
+
455
+        /** @var int $size */
456
+        $size = filesize($inputFile);
457
+        if (!is_int($size)) {
458
+            throw new SodiumException('Could not obtain the file size');
459
+        }
460
+
461
+        /** @var resource $ifp */
462
+        $ifp = fopen($inputFile, 'rb');
463
+        if (!is_resource($ifp)) {
464
+            throw new SodiumException('Could not open input file for reading');
465
+        }
466
+
467
+        /** @var resource $ofp */
468
+        $ofp = fopen($outputFile, 'wb');
469
+        if (!is_resource($ofp)) {
470
+            fclose($ifp);
471
+            throw new SodiumException('Could not open output file for writing');
472
+        }
473
+
474
+        $res = self::secretbox_encrypt($ifp, $ofp, $size, $nonce, $key);
475
+        fclose($ifp);
476
+        fclose($ofp);
477
+        return $res;
478
+    }
479
+    /**
480
+     * Seal a file (rather than a string). Uses less memory than
481
+     * ParagonIE_Sodium_Compat::crypto_secretbox_open(), but produces
482
+     * the same result.
483
+     *
484
+     * Warning: Does not protect against TOCTOU attacks. You should
485
+     * just load the file into memory and use crypto_secretbox_open() if
486
+     * you are worried about those.
487
+     *
488
+     * @param string $inputFile
489
+     * @param string $outputFile
490
+     * @param string $nonce
491
+     * @param string $key
492
+     * @return bool
493
+     * @throws SodiumException
494
+     * @throws TypeError
495
+     */
496
+    public static function secretbox_open($inputFile, $outputFile, $nonce, $key)
497
+    {
498
+        /* Type checks: */
499
+        if (!is_string($inputFile)) {
500
+            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
501
+        }
502
+        if (!is_string($outputFile)) {
503
+            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
504
+        }
505
+        if (!is_string($nonce)) {
506
+            throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
507
+        }
508
+        if (!is_string($key)) {
509
+            throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
510
+        }
511
+
512
+        /* Input validation: */
513
+        if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
514
+            throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
515
+        }
516
+        if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
517
+            throw new TypeError('Argument 4 must be CRYPTO_SECRETBOXBOX_KEYBYTES bytes');
518
+        }
519
+
520
+        /** @var int $size */
521
+        $size = filesize($inputFile);
522
+        if (!is_int($size)) {
523
+            throw new SodiumException('Could not obtain the file size');
524
+        }
525
+
526
+        /** @var resource $ifp */
527
+        $ifp = fopen($inputFile, 'rb');
528
+        if (!is_resource($ifp)) {
529
+            throw new SodiumException('Could not open input file for reading');
530
+        }
531
+
532
+        /** @var resource $ofp */
533
+        $ofp = fopen($outputFile, 'wb');
534
+        if (!is_resource($ofp)) {
535
+            fclose($ifp);
536
+            throw new SodiumException('Could not open output file for writing');
537
+        }
538
+
539
+        $res = self::secretbox_decrypt($ifp, $ofp, $size, $nonce, $key);
540
+        fclose($ifp);
541
+        fclose($ofp);
542
+        try {
543
+            ParagonIE_Sodium_Compat::memzero($key);
544
+        } catch (SodiumException $ex) {
545
+            /** @psalm-suppress PossiblyUndefinedVariable */
546
+            unset($key);
547
+        }
548
+        return $res;
549
+    }
550
+
551
+    /**
552
+     * Sign a file (rather than a string). Uses less memory than
553
+     * ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
554
+     * the same result.
555
+     *
556
+     * @param string $filePath  Absolute path to a file on the filesystem
557
+     * @param string $secretKey Secret signing key
558
+     *
559
+     * @return string           Ed25519 signature
560
+     * @throws SodiumException
561
+     * @throws TypeError
562
+     */
563
+    public static function sign($filePath, $secretKey)
564
+    {
565
+        /* Type checks: */
566
+        if (!is_string($filePath)) {
567
+            throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
568
+        }
569
+        if (!is_string($secretKey)) {
570
+            throw new TypeError('Argument 2 must be a string, ' . gettype($secretKey) . ' given.');
571
+        }
572
+
573
+        /* Input validation: */
574
+        if (self::strlen($secretKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_SECRETKEYBYTES) {
575
+            throw new TypeError('Argument 2 must be CRYPTO_SIGN_SECRETKEYBYTES bytes');
576
+        }
577
+        if (PHP_INT_SIZE === 4) {
578
+            return self::sign_core32($filePath, $secretKey);
579
+        }
580
+
581
+        /** @var int $size */
582
+        $size = filesize($filePath);
583
+        if (!is_int($size)) {
584
+            throw new SodiumException('Could not obtain the file size');
585
+        }
586
+
587
+        /** @var resource $fp */
588
+        $fp = fopen($filePath, 'rb');
589
+        if (!is_resource($fp)) {
590
+            throw new SodiumException('Could not open input file for reading');
591
+        }
592
+
593
+        /** @var string $az */
594
+        $az = hash('sha512', self::substr($secretKey, 0, 32), true);
595
+
596
+        $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
597
+        $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
598
+
599
+        $hs = hash_init('sha512');
600
+        self::hash_update($hs, self::substr($az, 32, 32));
601
+        /** @var resource $hs */
602
+        $hs = self::updateHashWithFile($hs, $fp, $size);
603
+
604
+        /** @var string $nonceHash */
605
+        $nonceHash = hash_final($hs, true);
606
+
607
+        /** @var string $pk */
608
+        $pk = self::substr($secretKey, 32, 32);
609
+
610
+        /** @var string $nonce */
611
+        $nonce = ParagonIE_Sodium_Core_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
612
+
613
+        /** @var string $sig */
614
+        $sig = ParagonIE_Sodium_Core_Ed25519::ge_p3_tobytes(
615
+            ParagonIE_Sodium_Core_Ed25519::ge_scalarmult_base($nonce)
616
+        );
617
+
618
+        $hs = hash_init('sha512');
619
+        self::hash_update($hs, self::substr($sig, 0, 32));
620
+        self::hash_update($hs, self::substr($pk, 0, 32));
621
+        /** @var resource $hs */
622
+        $hs = self::updateHashWithFile($hs, $fp, $size);
623
+
624
+        /** @var string $hramHash */
625
+        $hramHash = hash_final($hs, true);
626
+
627
+        /** @var string $hram */
628
+        $hram = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hramHash);
629
+
630
+        /** @var string $sigAfter */
631
+        $sigAfter = ParagonIE_Sodium_Core_Ed25519::sc_muladd($hram, $az, $nonce);
632
+
633
+        /** @var string $sig */
634
+        $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
635
+
636
+        try {
637
+            ParagonIE_Sodium_Compat::memzero($az);
638
+        } catch (SodiumException $ex) {
639
+            $az = null;
640
+        }
641
+        fclose($fp);
642
+        return $sig;
643
+    }
644
+
645
+    /**
646
+     * Verify a file (rather than a string). Uses less memory than
647
+     * ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
648
+     * produces the same result.
649
+     *
650
+     * @param string $sig       Ed25519 signature
651
+     * @param string $filePath  Absolute path to a file on the filesystem
652
+     * @param string $publicKey Signing public key
653
+     *
654
+     * @return bool
655
+     * @throws SodiumException
656
+     * @throws TypeError
657
+     * @throws Exception
658
+     */
659
+    public static function verify($sig, $filePath, $publicKey)
660
+    {
661
+        /* Type checks: */
662
+        if (!is_string($sig)) {
663
+            throw new TypeError('Argument 1 must be a string, ' . gettype($sig) . ' given.');
664
+        }
665
+        if (!is_string($filePath)) {
666
+            throw new TypeError('Argument 2 must be a string, ' . gettype($filePath) . ' given.');
667
+        }
668
+        if (!is_string($publicKey)) {
669
+            throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
670
+        }
671
+
672
+        /* Input validation: */
673
+        if (self::strlen($sig) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_BYTES) {
674
+            throw new TypeError('Argument 1 must be CRYPTO_SIGN_BYTES bytes');
675
+        }
676
+        if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_PUBLICKEYBYTES) {
677
+            throw new TypeError('Argument 3 must be CRYPTO_SIGN_PUBLICKEYBYTES bytes');
678
+        }
679
+        if (self::strlen($sig) < 64) {
680
+            throw new SodiumException('Signature is too short');
681
+        }
682
+
683
+        if (PHP_INT_SIZE === 4) {
684
+            return self::verify_core32($sig, $filePath, $publicKey);
685
+        }
686
+
687
+        /* Security checks */
688
+        if (
689
+            (ParagonIE_Sodium_Core_Ed25519::chrToInt($sig[63]) & 240)
690
+                &&
691
+            ParagonIE_Sodium_Core_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))
692
+        ) {
693
+            throw new SodiumException('S < L - Invalid signature');
694
+        }
695
+        if (ParagonIE_Sodium_Core_Ed25519::small_order($sig)) {
696
+            throw new SodiumException('Signature is on too small of an order');
697
+        }
698
+        if ((self::chrToInt($sig[63]) & 224) !== 0) {
699
+            throw new SodiumException('Invalid signature');
700
+        }
701
+        $d = 0;
702
+        for ($i = 0; $i < 32; ++$i) {
703
+            $d |= self::chrToInt($publicKey[$i]);
704
+        }
705
+        if ($d === 0) {
706
+            throw new SodiumException('All zero public key');
707
+        }
708
+
709
+        /** @var int $size */
710
+        $size = filesize($filePath);
711
+        if (!is_int($size)) {
712
+            throw new SodiumException('Could not obtain the file size');
713
+        }
714
+
715
+        /** @var resource $fp */
716
+        $fp = fopen($filePath, 'rb');
717
+        if (!is_resource($fp)) {
718
+            throw new SodiumException('Could not open input file for reading');
719
+        }
720
+
721
+        /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
722
+        $orig = ParagonIE_Sodium_Compat::$fastMult;
723
+
724
+        // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
725
+        ParagonIE_Sodium_Compat::$fastMult = true;
726
+
727
+        /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A */
728
+        $A = ParagonIE_Sodium_Core_Ed25519::ge_frombytes_negate_vartime($publicKey);
729
+
730
+        $hs = hash_init('sha512');
731
+        self::hash_update($hs, self::substr($sig, 0, 32));
732
+        self::hash_update($hs, self::substr($publicKey, 0, 32));
733
+        /** @var resource $hs */
734
+        $hs = self::updateHashWithFile($hs, $fp, $size);
735
+        /** @var string $hDigest */
736
+        $hDigest = hash_final($hs, true);
737
+
738
+        /** @var string $h */
739
+        $h = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
740
+
741
+        /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P2 $R */
742
+        $R = ParagonIE_Sodium_Core_Ed25519::ge_double_scalarmult_vartime(
743
+            $h,
744
+            $A,
745
+            self::substr($sig, 32)
746
+        );
747
+
748
+        /** @var string $rcheck */
749
+        $rcheck = ParagonIE_Sodium_Core_Ed25519::ge_tobytes($R);
750
+
751
+        // Close the file handle
752
+        fclose($fp);
753
+
754
+        // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
755
+        ParagonIE_Sodium_Compat::$fastMult = $orig;
756
+        return self::verify_32($rcheck, self::substr($sig, 0, 32));
757
+    }
758
+
759
+    /**
760
+     * @param resource $ifp
761
+     * @param resource $ofp
762
+     * @param int      $mlen
763
+     * @param string   $nonce
764
+     * @param string   $boxKeypair
765
+     * @return bool
766
+     * @throws SodiumException
767
+     * @throws TypeError
768
+     */
769
+    protected static function box_encrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
770
+    {
771
+        if (PHP_INT_SIZE === 4) {
772
+            return self::secretbox_encrypt(
773
+                $ifp,
774
+                $ofp,
775
+                $mlen,
776
+                $nonce,
777
+                ParagonIE_Sodium_Crypto32::box_beforenm(
778
+                    ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
779
+                    ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
780
+                )
781
+            );
782
+        }
783
+        return self::secretbox_encrypt(
784
+            $ifp,
785
+            $ofp,
786
+            $mlen,
787
+            $nonce,
788
+            ParagonIE_Sodium_Crypto::box_beforenm(
789
+                ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
790
+                ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
791
+            )
792
+        );
793
+    }
794
+
795
+
796
+    /**
797
+     * @param resource $ifp
798
+     * @param resource $ofp
799
+     * @param int      $mlen
800
+     * @param string   $nonce
801
+     * @param string   $boxKeypair
802
+     * @return bool
803
+     * @throws SodiumException
804
+     * @throws TypeError
805
+     */
806
+    protected static function box_decrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
807
+    {
808
+        if (PHP_INT_SIZE === 4) {
809
+            return self::secretbox_decrypt(
810
+                $ifp,
811
+                $ofp,
812
+                $mlen,
813
+                $nonce,
814
+                ParagonIE_Sodium_Crypto32::box_beforenm(
815
+                    ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
816
+                    ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
817
+                )
818
+            );
819
+        }
820
+        return self::secretbox_decrypt(
821
+            $ifp,
822
+            $ofp,
823
+            $mlen,
824
+            $nonce,
825
+            ParagonIE_Sodium_Crypto::box_beforenm(
826
+                ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
827
+                ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
828
+            )
829
+        );
830
+    }
831
+
832
+    /**
833
+     * Encrypt a file
834
+     *
835
+     * @param resource $ifp
836
+     * @param resource $ofp
837
+     * @param int $mlen
838
+     * @param string $nonce
839
+     * @param string $key
840
+     * @return bool
841
+     * @throws SodiumException
842
+     * @throws TypeError
843
+     */
844
+    protected static function secretbox_encrypt($ifp, $ofp, $mlen, $nonce, $key)
845
+    {
846
+        if (PHP_INT_SIZE === 4) {
847
+            return self::secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
848
+        }
849
+
850
+        $plaintext = fread($ifp, 32);
851
+        if (!is_string($plaintext)) {
852
+            throw new SodiumException('Could not read input file');
853
+        }
854
+        $first32 = self::ftell($ifp);
855
+
856
+        /** @var string $subkey */
857
+        $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
858
+
859
+        /** @var string $realNonce */
860
+        $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
861
+
862
+        /** @var string $block0 */
863
+        $block0 = str_repeat("\x00", 32);
864
+
865
+        /** @var int $mlen - Length of the plaintext message */
866
+        $mlen0 = $mlen;
867
+        if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
868
+            $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
869
+        }
870
+        $block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);
871
+
872
+        /** @var string $block0 */
873
+        $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20_xor(
874
+            $block0,
875
+            $realNonce,
876
+            $subkey
877
+        );
878
+
879
+        $state = new ParagonIE_Sodium_Core_Poly1305_State(
880
+            ParagonIE_Sodium_Core_Util::substr(
881
+                $block0,
882
+                0,
883
+                ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
884
+            )
885
+        );
886
+
887
+        // Pre-write 16 blank bytes for the Poly1305 tag
888
+        $start = self::ftell($ofp);
889
+        fwrite($ofp, str_repeat("\x00", 16));
890
+
891
+        /** @var string $c */
892
+        $cBlock = ParagonIE_Sodium_Core_Util::substr(
893
+            $block0,
894
+            ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
895
+        );
896
+        $state->update($cBlock);
897
+        fwrite($ofp, $cBlock);
898
+        $mlen -= 32;
899
+
900
+        /** @var int $iter */
901
+        $iter = 1;
902
+
903
+        /** @var int $incr */
904
+        $incr = self::BUFFER_SIZE >> 6;
905
+
906
+        /*
907
+         * Set the cursor to the end of the first half-block. All future bytes will
908
+         * generated from salsa20_xor_ic, starting from 1 (second block).
909
+         */
910
+        fseek($ifp, $first32, SEEK_SET);
911
+
912
+        while ($mlen > 0) {
913
+            $blockSize = $mlen > self::BUFFER_SIZE
914
+                ? self::BUFFER_SIZE
915
+                : $mlen;
916
+            $plaintext = fread($ifp, $blockSize);
917
+            if (!is_string($plaintext)) {
918
+                throw new SodiumException('Could not read input file');
919
+            }
920
+            $cBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
921
+                $plaintext,
922
+                $realNonce,
923
+                $iter,
924
+                $subkey
925
+            );
926
+            fwrite($ofp, $cBlock, $blockSize);
927
+            $state->update($cBlock);
928
+
929
+            $mlen -= $blockSize;
930
+            $iter += $incr;
931
+        }
932
+        try {
933
+            ParagonIE_Sodium_Compat::memzero($block0);
934
+            ParagonIE_Sodium_Compat::memzero($subkey);
935
+        } catch (SodiumException $ex) {
936
+            $block0 = null;
937
+            $subkey = null;
938
+        }
939
+        $end = self::ftell($ofp);
940
+
941
+        /*
942
+         * Write the Poly1305 authentication tag that provides integrity
943
+         * over the ciphertext (encrypt-then-MAC)
944
+         */
945
+        fseek($ofp, $start, SEEK_SET);
946
+        fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
947
+        fseek($ofp, $end, SEEK_SET);
948
+        unset($state);
949
+
950
+        return true;
951
+    }
952
+
953
+    /**
954
+     * Decrypt a file
955
+     *
956
+     * @param resource $ifp
957
+     * @param resource $ofp
958
+     * @param int $mlen
959
+     * @param string $nonce
960
+     * @param string $key
961
+     * @return bool
962
+     * @throws SodiumException
963
+     * @throws TypeError
964
+     */
965
+    protected static function secretbox_decrypt($ifp, $ofp, $mlen, $nonce, $key)
966
+    {
967
+        if (PHP_INT_SIZE === 4) {
968
+            return self::secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
969
+        }
970
+        $tag = fread($ifp, 16);
971
+        if (!is_string($tag)) {
972
+            throw new SodiumException('Could not read input file');
973
+        }
974
+
975
+        /** @var string $subkey */
976
+        $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
977
+
978
+        /** @var string $realNonce */
979
+        $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
980
+
981
+        /** @var string $block0 */
982
+        $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20(
983
+            64,
984
+            ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
985
+            $subkey
986
+        );
987
+
988
+        /* Verify the Poly1305 MAC -before- attempting to decrypt! */
989
+        $state = new ParagonIE_Sodium_Core_Poly1305_State(self::substr($block0, 0, 32));
990
+        if (!self::onetimeauth_verify($state, $ifp, $tag, $mlen)) {
991
+            throw new SodiumException('Invalid MAC');
992
+        }
993
+
994
+        /*
995
+         * Set the cursor to the end of the first half-block. All future bytes will
996
+         * generated from salsa20_xor_ic, starting from 1 (second block).
997
+         */
998
+        $first32 = fread($ifp, 32);
999
+        if (!is_string($first32)) {
1000
+            throw new SodiumException('Could not read input file');
1001
+        }
1002
+        $first32len = self::strlen($first32);
1003
+        fwrite(
1004
+            $ofp,
1005
+            self::xorStrings(
1006
+                self::substr($block0, 32, $first32len),
1007
+                self::substr($first32, 0, $first32len)
1008
+            )
1009
+        );
1010
+        $mlen -= 32;
1011
+
1012
+        /** @var int $iter */
1013
+        $iter = 1;
1014
+
1015
+        /** @var int $incr */
1016
+        $incr = self::BUFFER_SIZE >> 6;
1017
+
1018
+        /* Decrypts ciphertext, writes to output file. */
1019
+        while ($mlen > 0) {
1020
+            $blockSize = $mlen > self::BUFFER_SIZE
1021
+                ? self::BUFFER_SIZE
1022
+                : $mlen;
1023
+            $ciphertext = fread($ifp, $blockSize);
1024
+            if (!is_string($ciphertext)) {
1025
+                throw new SodiumException('Could not read input file');
1026
+            }
1027
+            $pBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
1028
+                $ciphertext,
1029
+                $realNonce,
1030
+                $iter,
1031
+                $subkey
1032
+            );
1033
+            fwrite($ofp, $pBlock, $blockSize);
1034
+            $mlen -= $blockSize;
1035
+            $iter += $incr;
1036
+        }
1037
+        return true;
1038
+    }
1039
+
1040
+    /**
1041
+     * @param ParagonIE_Sodium_Core_Poly1305_State $state
1042
+     * @param resource $ifp
1043
+     * @param string $tag
1044
+     * @param int $mlen
1045
+     * @return bool
1046
+     * @throws SodiumException
1047
+     * @throws TypeError
1048
+     */
1049
+    protected static function onetimeauth_verify(
1050
+        ParagonIE_Sodium_Core_Poly1305_State $state,
1051
+        $ifp,
1052
+        $tag = '',
1053
+        $mlen = 0
1054
+    ) {
1055
+        /** @var int $pos */
1056
+        $pos = self::ftell($ifp);
1057
+
1058
+        /** @var int $iter */
1059
+        $iter = 1;
1060
+
1061
+        /** @var int $incr */
1062
+        $incr = self::BUFFER_SIZE >> 6;
1063
+
1064
+        while ($mlen > 0) {
1065
+            $blockSize = $mlen > self::BUFFER_SIZE
1066
+                ? self::BUFFER_SIZE
1067
+                : $mlen;
1068
+            $ciphertext = fread($ifp, $blockSize);
1069
+            if (!is_string($ciphertext)) {
1070
+                throw new SodiumException('Could not read input file');
1071
+            }
1072
+            $state->update($ciphertext);
1073
+            $mlen -= $blockSize;
1074
+            $iter += $incr;
1075
+        }
1076
+        $res = ParagonIE_Sodium_Core_Util::verify_16($tag, $state->finish());
1077
+
1078
+        fseek($ifp, $pos, SEEK_SET);
1079
+        return $res;
1080
+    }
1081
+
1082
+    /**
1083
+     * Update a hash context with the contents of a file, without
1084
+     * loading the entire file into memory.
1085
+     *
1086
+     * @param resource|HashContext $hash
1087
+     * @param resource $fp
1088
+     * @param int $size
1089
+     * @return resource|object Resource on PHP < 7.2, HashContext object on PHP >= 7.2
1090
+     * @throws SodiumException
1091
+     * @throws TypeError
1092
+     * @psalm-suppress PossiblyInvalidArgument
1093
+     *                 PHP 7.2 changes from a resource to an object,
1094
+     *                 which causes Psalm to complain about an error.
1095
+     * @psalm-suppress TypeCoercion
1096
+     *                 Ditto.
1097
+     */
1098
+    public static function updateHashWithFile($hash, $fp, $size = 0)
1099
+    {
1100
+        /* Type checks: */
1101
+        if (PHP_VERSION_ID < 70200) {
1102
+            if (!is_resource($hash)) {
1103
+                throw new TypeError('Argument 1 must be a resource, ' . gettype($hash) . ' given.');
1104
+            }
1105
+        } else {
1106
+            if (!is_object($hash)) {
1107
+                throw new TypeError('Argument 1 must be an object (PHP 7.2+), ' . gettype($hash) . ' given.');
1108
+            }
1109
+        }
1110
+
1111
+        if (!is_resource($fp)) {
1112
+            throw new TypeError('Argument 2 must be a resource, ' . gettype($fp) . ' given.');
1113
+        }
1114
+        if (!is_int($size)) {
1115
+            throw new TypeError('Argument 3 must be an integer, ' . gettype($size) . ' given.');
1116
+        }
1117
+
1118
+        /** @var int $originalPosition */
1119
+        $originalPosition = self::ftell($fp);
1120
+
1121
+        // Move file pointer to beginning of file
1122
+        fseek($fp, 0, SEEK_SET);
1123
+        for ($i = 0; $i < $size; $i += self::BUFFER_SIZE) {
1124
+            /** @var string|bool $message */
1125
+            $message = fread(
1126
+                $fp,
1127
+                ($size - $i) > self::BUFFER_SIZE
1128
+                    ? $size - $i
1129
+                    : self::BUFFER_SIZE
1130
+            );
1131
+            if (!is_string($message)) {
1132
+                throw new SodiumException('Unexpected error reading from file.');
1133
+            }
1134
+            /** @var string $message */
1135
+            /** @psalm-suppress InvalidArgument */
1136
+            self::hash_update($hash, $message);
1137
+        }
1138
+        // Reset file pointer's position
1139
+        fseek($fp, $originalPosition, SEEK_SET);
1140
+        return $hash;
1141
+    }
1142
+
1143
+    /**
1144
+     * Sign a file (rather than a string). Uses less memory than
1145
+     * ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
1146
+     * the same result. (32-bit)
1147
+     *
1148
+     * @param string $filePath  Absolute path to a file on the filesystem
1149
+     * @param string $secretKey Secret signing key
1150
+     *
1151
+     * @return string           Ed25519 signature
1152
+     * @throws SodiumException
1153
+     * @throws TypeError
1154
+     */
1155
+    private static function sign_core32($filePath, $secretKey)
1156
+    {
1157
+        $size = filesize($filePath);
1158
+        if (!is_int($size)) {
1159
+            throw new SodiumException('Could not obtain the file size');
1160
+        }
1161
+
1162
+        $fp = fopen($filePath, 'rb');
1163
+        if (!is_resource($fp)) {
1164
+            throw new SodiumException('Could not open input file for reading');
1165
+        }
1166
+
1167
+        /** @var string $az */
1168
+        $az = hash('sha512', self::substr($secretKey, 0, 32), true);
1169
+
1170
+        $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
1171
+        $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
1172
+
1173
+        $hs = hash_init('sha512');
1174
+        self::hash_update($hs, self::substr($az, 32, 32));
1175
+        /** @var resource $hs */
1176
+        $hs = self::updateHashWithFile($hs, $fp, $size);
1177
+
1178
+        $nonceHash = hash_final($hs, true);
1179
+        $pk = self::substr($secretKey, 32, 32);
1180
+        $nonce = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
1181
+        $sig = ParagonIE_Sodium_Core32_Ed25519::ge_p3_tobytes(
1182
+            ParagonIE_Sodium_Core32_Ed25519::ge_scalarmult_base($nonce)
1183
+        );
1184
+
1185
+        $hs = hash_init('sha512');
1186
+        self::hash_update($hs, self::substr($sig, 0, 32));
1187
+        self::hash_update($hs, self::substr($pk, 0, 32));
1188
+        /** @var resource $hs */
1189
+        $hs = self::updateHashWithFile($hs, $fp, $size);
1190
+
1191
+        $hramHash = hash_final($hs, true);
1192
+
1193
+        $hram = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hramHash);
1194
+
1195
+        $sigAfter = ParagonIE_Sodium_Core32_Ed25519::sc_muladd($hram, $az, $nonce);
1196
+
1197
+        /** @var string $sig */
1198
+        $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
1199
+
1200
+        try {
1201
+            ParagonIE_Sodium_Compat::memzero($az);
1202
+        } catch (SodiumException $ex) {
1203
+            $az = null;
1204
+        }
1205
+        fclose($fp);
1206
+        return $sig;
1207
+    }
1208
+
1209
+    /**
1210
+     *
1211
+     * Verify a file (rather than a string). Uses less memory than
1212
+     * ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
1213
+     * produces the same result. (32-bit)
1214
+     *
1215
+     * @param string $sig       Ed25519 signature
1216
+     * @param string $filePath  Absolute path to a file on the filesystem
1217
+     * @param string $publicKey Signing public key
1218
+     *
1219
+     * @return bool
1220
+     * @throws SodiumException
1221
+     * @throws Exception
1222
+     */
1223
+    public static function verify_core32($sig, $filePath, $publicKey)
1224
+    {
1225
+        /* Security checks */
1226
+        if (ParagonIE_Sodium_Core32_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))) {
1227
+            throw new SodiumException('S < L - Invalid signature');
1228
+        }
1229
+        if (ParagonIE_Sodium_Core32_Ed25519::small_order($sig)) {
1230
+            throw new SodiumException('Signature is on too small of an order');
1231
+        }
1232
+
1233
+        if ((self::chrToInt($sig[63]) & 224) !== 0) {
1234
+            throw new SodiumException('Invalid signature');
1235
+        }
1236
+        $d = 0;
1237
+        for ($i = 0; $i < 32; ++$i) {
1238
+            $d |= self::chrToInt($publicKey[$i]);
1239
+        }
1240
+        if ($d === 0) {
1241
+            throw new SodiumException('All zero public key');
1242
+        }
1243
+
1244
+        /** @var int|bool $size */
1245
+        $size = filesize($filePath);
1246
+        if (!is_int($size)) {
1247
+            throw new SodiumException('Could not obtain the file size');
1248
+        }
1249
+        /** @var int $size */
1250
+
1251
+        /** @var resource|bool $fp */
1252
+        $fp = fopen($filePath, 'rb');
1253
+        if (!is_resource($fp)) {
1254
+            throw new SodiumException('Could not open input file for reading');
1255
+        }
1256
+        /** @var resource $fp */
1257
+
1258
+        /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
1259
+        $orig = ParagonIE_Sodium_Compat::$fastMult;
1260
+
1261
+        // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
1262
+        ParagonIE_Sodium_Compat::$fastMult = true;
1263
+
1264
+        /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A */
1265
+        $A = ParagonIE_Sodium_Core32_Ed25519::ge_frombytes_negate_vartime($publicKey);
1266
+
1267
+        $hs = hash_init('sha512');
1268
+        self::hash_update($hs, self::substr($sig, 0, 32));
1269
+        self::hash_update($hs, self::substr($publicKey, 0, 32));
1270
+        /** @var resource $hs */
1271
+        $hs = self::updateHashWithFile($hs, $fp, $size);
1272
+        /** @var string $hDigest */
1273
+        $hDigest = hash_final($hs, true);
1274
+
1275
+        /** @var string $h */
1276
+        $h = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
1277
+
1278
+        /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $R */
1279
+        $R = ParagonIE_Sodium_Core32_Ed25519::ge_double_scalarmult_vartime(
1280
+            $h,
1281
+            $A,
1282
+            self::substr($sig, 32)
1283
+        );
1284
+
1285
+        /** @var string $rcheck */
1286
+        $rcheck = ParagonIE_Sodium_Core32_Ed25519::ge_tobytes($R);
1287
+
1288
+        // Close the file handle
1289
+        fclose($fp);
1290
+
1291
+        // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
1292
+        ParagonIE_Sodium_Compat::$fastMult = $orig;
1293
+        return self::verify_32($rcheck, self::substr($sig, 0, 32));
1294
+    }
1295
+
1296
+    /**
1297
+     * Encrypt a file (32-bit)
1298
+     *
1299
+     * @param resource $ifp
1300
+     * @param resource $ofp
1301
+     * @param int $mlen
1302
+     * @param string $nonce
1303
+     * @param string $key
1304
+     * @return bool
1305
+     * @throws SodiumException
1306
+     * @throws TypeError
1307
+     */
1308
+    protected static function secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
1309
+    {
1310
+        $plaintext = fread($ifp, 32);
1311
+        if (!is_string($plaintext)) {
1312
+            throw new SodiumException('Could not read input file');
1313
+        }
1314
+        $first32 = self::ftell($ifp);
1315
+
1316
+        /** @var string $subkey */
1317
+        $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
1318
+
1319
+        /** @var string $realNonce */
1320
+        $realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
1321
+
1322
+        /** @var string $block0 */
1323
+        $block0 = str_repeat("\x00", 32);
1324
+
1325
+        /** @var int $mlen - Length of the plaintext message */
1326
+        $mlen0 = $mlen;
1327
+        if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
1328
+            $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
1329
+        }
1330
+        $block0 .= ParagonIE_Sodium_Core32_Util::substr($plaintext, 0, $mlen0);
1331
+
1332
+        /** @var string $block0 */
1333
+        $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor(
1334
+            $block0,
1335
+            $realNonce,
1336
+            $subkey
1337
+        );
1338
+
1339
+        $state = new ParagonIE_Sodium_Core32_Poly1305_State(
1340
+            ParagonIE_Sodium_Core32_Util::substr(
1341
+                $block0,
1342
+                0,
1343
+                ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
1344
+            )
1345
+        );
1346
+
1347
+        // Pre-write 16 blank bytes for the Poly1305 tag
1348
+        $start = self::ftell($ofp);
1349
+        fwrite($ofp, str_repeat("\x00", 16));
1350
+
1351
+        /** @var string $c */
1352
+        $cBlock = ParagonIE_Sodium_Core32_Util::substr(
1353
+            $block0,
1354
+            ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
1355
+        );
1356
+        $state->update($cBlock);
1357
+        fwrite($ofp, $cBlock);
1358
+        $mlen -= 32;
1359
+
1360
+        /** @var int $iter */
1361
+        $iter = 1;
1362
+
1363
+        /** @var int $incr */
1364
+        $incr = self::BUFFER_SIZE >> 6;
1365
+
1366
+        /*
1367
+         * Set the cursor to the end of the first half-block. All future bytes will
1368
+         * generated from salsa20_xor_ic, starting from 1 (second block).
1369
+         */
1370
+        fseek($ifp, $first32, SEEK_SET);
1371
+
1372
+        while ($mlen > 0) {
1373
+            $blockSize = $mlen > self::BUFFER_SIZE
1374
+                ? self::BUFFER_SIZE
1375
+                : $mlen;
1376
+            $plaintext = fread($ifp, $blockSize);
1377
+            if (!is_string($plaintext)) {
1378
+                throw new SodiumException('Could not read input file');
1379
+            }
1380
+            $cBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
1381
+                $plaintext,
1382
+                $realNonce,
1383
+                $iter,
1384
+                $subkey
1385
+            );
1386
+            fwrite($ofp, $cBlock, $blockSize);
1387
+            $state->update($cBlock);
1388
+
1389
+            $mlen -= $blockSize;
1390
+            $iter += $incr;
1391
+        }
1392
+        try {
1393
+            ParagonIE_Sodium_Compat::memzero($block0);
1394
+            ParagonIE_Sodium_Compat::memzero($subkey);
1395
+        } catch (SodiumException $ex) {
1396
+            $block0 = null;
1397
+            $subkey = null;
1398
+        }
1399
+        $end = self::ftell($ofp);
1400
+
1401
+        /*
1402
+         * Write the Poly1305 authentication tag that provides integrity
1403
+         * over the ciphertext (encrypt-then-MAC)
1404
+         */
1405
+        fseek($ofp, $start, SEEK_SET);
1406
+        fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
1407
+        fseek($ofp, $end, SEEK_SET);
1408
+        unset($state);
1409
+
1410
+        return true;
1411
+    }
1412
+
1413
+    /**
1414
+     * Decrypt a file (32-bit)
1415
+     *
1416
+     * @param resource $ifp
1417
+     * @param resource $ofp
1418
+     * @param int $mlen
1419
+     * @param string $nonce
1420
+     * @param string $key
1421
+     * @return bool
1422
+     * @throws SodiumException
1423
+     * @throws TypeError
1424
+     */
1425
+    protected static function secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
1426
+    {
1427
+        $tag = fread($ifp, 16);
1428
+        if (!is_string($tag)) {
1429
+            throw new SodiumException('Could not read input file');
1430
+        }
1431
+
1432
+        /** @var string $subkey */
1433
+        $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
1434
+
1435
+        /** @var string $realNonce */
1436
+        $realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
1437
+
1438
+        /** @var string $block0 */
1439
+        $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20(
1440
+            64,
1441
+            ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
1442
+            $subkey
1443
+        );
1444
+
1445
+        /* Verify the Poly1305 MAC -before- attempting to decrypt! */
1446
+        $state = new ParagonIE_Sodium_Core32_Poly1305_State(self::substr($block0, 0, 32));
1447
+        if (!self::onetimeauth_verify_core32($state, $ifp, $tag, $mlen)) {
1448
+            throw new SodiumException('Invalid MAC');
1449
+        }
1450
+
1451
+        /*
1452
+         * Set the cursor to the end of the first half-block. All future bytes will
1453
+         * generated from salsa20_xor_ic, starting from 1 (second block).
1454
+         */
1455
+        $first32 = fread($ifp, 32);
1456
+        if (!is_string($first32)) {
1457
+            throw new SodiumException('Could not read input file');
1458
+        }
1459
+        $first32len = self::strlen($first32);
1460
+        fwrite(
1461
+            $ofp,
1462
+            self::xorStrings(
1463
+                self::substr($block0, 32, $first32len),
1464
+                self::substr($first32, 0, $first32len)
1465
+            )
1466
+        );
1467
+        $mlen -= 32;
1468
+
1469
+        /** @var int $iter */
1470
+        $iter = 1;
1471
+
1472
+        /** @var int $incr */
1473
+        $incr = self::BUFFER_SIZE >> 6;
1474
+
1475
+        /* Decrypts ciphertext, writes to output file. */
1476
+        while ($mlen > 0) {
1477
+            $blockSize = $mlen > self::BUFFER_SIZE
1478
+                ? self::BUFFER_SIZE
1479
+                : $mlen;
1480
+            $ciphertext = fread($ifp, $blockSize);
1481
+            if (!is_string($ciphertext)) {
1482
+                throw new SodiumException('Could not read input file');
1483
+            }
1484
+            $pBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
1485
+                $ciphertext,
1486
+                $realNonce,
1487
+                $iter,
1488
+                $subkey
1489
+            );
1490
+            fwrite($ofp, $pBlock, $blockSize);
1491
+            $mlen -= $blockSize;
1492
+            $iter += $incr;
1493
+        }
1494
+        return true;
1495
+    }
1496
+
1497
+    /**
1498
+     * One-time message authentication for 32-bit systems
1499
+     *
1500
+     * @param ParagonIE_Sodium_Core32_Poly1305_State $state
1501
+     * @param resource $ifp
1502
+     * @param string $tag
1503
+     * @param int $mlen
1504
+     * @return bool
1505
+     * @throws SodiumException
1506
+     * @throws TypeError
1507
+     */
1508
+    protected static function onetimeauth_verify_core32(
1509
+        ParagonIE_Sodium_Core32_Poly1305_State $state,
1510
+        $ifp,
1511
+        $tag = '',
1512
+        $mlen = 0
1513
+    ) {
1514
+        /** @var int $pos */
1515
+        $pos = self::ftell($ifp);
1516
+
1517
+        while ($mlen > 0) {
1518
+            $blockSize = $mlen > self::BUFFER_SIZE
1519
+                ? self::BUFFER_SIZE
1520
+                : $mlen;
1521
+            $ciphertext = fread($ifp, $blockSize);
1522
+            if (!is_string($ciphertext)) {
1523
+                throw new SodiumException('Could not read input file');
1524
+            }
1525
+            $state->update($ciphertext);
1526
+            $mlen -= $blockSize;
1527
+        }
1528
+        $res = ParagonIE_Sodium_Core32_Util::verify_16($tag, $state->finish());
1529
+
1530
+        fseek($ifp, $pos, SEEK_SET);
1531
+        return $res;
1532
+    }
1533
+
1534
+    /**
1535
+     * @param resource $resource
1536
+     * @return int
1537
+     * @throws SodiumException
1538
+     */
1539
+    private static function ftell($resource)
1540
+    {
1541
+        $return = ftell($resource);
1542
+        if (!is_int($return)) {
1543
+            throw new SodiumException('ftell() returned false');
1544
+        }
1545
+        return (int) $return;
1546
+    }
1547
+}