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