Completed
Push — develop ( f0d100...266ee2 )
by J.D.
04:14
created

ParagonIE_Sodium_File::box()   C

Complexity

Conditions 10
Paths 10

Size

Total Lines 48
Code Lines 27

Duplication

Lines 48
Ratio 100 %

Importance

Changes 0
Metric Value
cc 10
eloc 27
nc 10
nop 4
dl 48
loc 48
rs 5.3454
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Error
26
     * @throws TypeError
27
     */
28 View Code Duplication
    public static function box($inputFile, $outputFile, $nonce, $keyPair)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 Error('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 Error('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 Error('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 Error
92
     * @throws TypeError
93
     */
94 View Code Duplication
    public static function box_open($inputFile, $outputFile, $nonce, $keypair)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 Error('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 Error('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 Error('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 (Error $ex) {
0 ignored issues
show
Bug introduced by
The class Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
144
            unset($ephKeypair);
145
        }
146
        return $res;
147
    }
148
149
    /**
150
     * Seal a file (rather than a string). Uses less memory than
151
     * ParagonIE_Sodium_Compat::crypto_box_seal(), but produces
152
     * the same result.
153
     *
154
     * @param string $inputFile  Absolute path to a file on the filesystem
155
     * @param string $outputFile Absolute path to a file on the filesystem
156
     * @param string $publicKey  ECDH public key
157
     *
158
     * @return bool
159
     * @throws Error
160
     * @throws TypeError
161
     */
162
    public static function box_seal($inputFile, $outputFile, $publicKey)
163
    {
164
        /* Type checks: */
165
        if (!is_string($inputFile)) {
166
            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
167
        }
168
        if (!is_string($outputFile)) {
169
            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
170
        }
171
        if (!is_string($publicKey)) {
172
            throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
173
        }
174
175
        /* Input validation: */
176
        if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
177
            throw new TypeError('Argument 3 must be CRYPTO_BOX_PUBLICKEYBYTES bytes');
178
        }
179
180
        /** @var int $size */
181
        $size = filesize($inputFile);
182
        if (!is_int($size)) {
183
            throw new Error('Could not obtain the file size');
184
        }
185
186
        /** @var resource $ifp */
187
        $ifp = fopen($inputFile, 'rb');
188
        if (!is_resource($ifp)) {
189
            throw new Error('Could not open input file for reading');
190
        }
191
192
        /** @var resource $ofp */
193
        $ofp = fopen($outputFile, 'wb');
194
        if (!is_resource($ofp)) {
195
            fclose($ifp);
196
            throw new Error('Could not open output file for writing');
197
        }
198
199
        /** @var string $ephKeypair */
200
        $ephKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair();
201
202
        /** @var string $msgKeypair */
203
        $msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
204
            ParagonIE_Sodium_Compat::crypto_box_secretkey($ephKeypair),
205
            $publicKey
206
        );
207
208
        /** @var string $ephemeralPK */
209
        $ephemeralPK = ParagonIE_Sodium_Compat::crypto_box_publickey($ephKeypair);
210
211
        /** @var string $nonce */
212
        $nonce = ParagonIE_Sodium_Compat::crypto_generichash(
213
            $ephemeralPK . $publicKey,
214
            '',
215
            24
216
        );
217
218
        /** @var int $firstWrite */
219
        $firstWrite = fwrite(
220
            $ofp,
221
            $ephemeralPK,
222
            ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES
223
        );
224 View Code Duplication
        if (!is_int($firstWrite)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
225
            fclose($ifp);
226
            fclose($ofp);
227
            ParagonIE_Sodium_Compat::memzero($ephKeypair);
228
            throw new Error('Could not write to output file');
229
        }
230 View Code Duplication
        if ($firstWrite !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
231
            ParagonIE_Sodium_Compat::memzero($ephKeypair);
232
            fclose($ifp);
233
            fclose($ofp);
234
            throw new Error('Error writing public key to output file');
235
        }
236
237
        $res = self::box_encrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
238
        fclose($ifp);
239
        fclose($ofp);
240
        try {
241
            ParagonIE_Sodium_Compat::memzero($nonce);
242
            ParagonIE_Sodium_Compat::memzero($ephKeypair);
243
        } catch (Error $ex) {
0 ignored issues
show
Bug introduced by
The class Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
244
            unset($ephKeypair);
245
        }
246
        return $res;
247
    }
248
249
    /**
250
     * Open a sealed file (rather than a string). Uses less memory than
251
     * ParagonIE_Sodium_Compat::crypto_box_seal_open(), but produces
252
     * the same result.
253
     *
254
     * Warning: Does not protect against TOCTOU attacks. You should
255
     * just load the file into memory and use crypto_box_seal_open() if
256
     * you are worried about those.
257
     *
258
     * @param string $inputFile
259
     * @param string $outputFile
260
     * @param string $ecdhKeypair
261
     * @return bool
262
     * @throws Error
263
     * @throws TypeError
264
     */
265
    public static function box_seal_open($inputFile, $outputFile, $ecdhKeypair)
266
    {
267
        /* Type checks: */
268
        if (!is_string($inputFile)) {
269
            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
270
        }
271
        if (!is_string($outputFile)) {
272
            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
273
        }
274
        if (!is_string($ecdhKeypair)) {
275
            throw new TypeError('Argument 3 must be a string, ' . gettype($ecdhKeypair) . ' given.');
276
        }
277
278
        /* Input validation: */
279
        if (self::strlen($ecdhKeypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
280
            throw new TypeError('Argument 3 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
281
        }
282
283
        $publicKey = ParagonIE_Sodium_Compat::crypto_box_publickey($ecdhKeypair);
284
285
        /** @var int $size */
286
        $size = filesize($inputFile);
287
        if (!is_int($size)) {
288
            throw new Error('Could not obtain the file size');
289
        }
290
291
        /** @var resource $ifp */
292
        $ifp = fopen($inputFile, 'rb');
293
        if (!is_resource($ifp)) {
294
            throw new Error('Could not open input file for reading');
295
        }
296
297
        /** @var resource $ofp */
298
        $ofp = fopen($outputFile, 'wb');
299
        if (!is_resource($ofp)) {
300
            fclose($ifp);
301
            throw new Error('Could not open output file for writing');
302
        }
303
304
        $ephemeralPK = fread($ifp, ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES);
305
        if (!is_string($ephemeralPK)) {
306
            throw new Error('Could not read input file');
307
        }
308
        if (self::strlen($ephemeralPK) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
309
            fclose($ifp);
310
            fclose($ofp);
311
            throw new Error('Could not read public key from sealed file');
312
        }
313
314
        $nonce = ParagonIE_Sodium_Compat::crypto_generichash(
315
            $ephemeralPK . $publicKey,
316
            '',
317
            24
318
        );
319
        $msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
320
            ParagonIE_Sodium_Compat::crypto_box_secretkey($ecdhKeypair),
321
            $ephemeralPK
322
        );
323
324
        $res = self::box_decrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
325
        fclose($ifp);
326
        fclose($ofp);
327
        try {
328
            ParagonIE_Sodium_Compat::memzero($nonce);
329
            ParagonIE_Sodium_Compat::memzero($ephKeypair);
330
        } catch (Error $ex) {
0 ignored issues
show
Bug introduced by
The class Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
331
            unset($ephKeypair);
332
        }
333
        return $res;
334
    }
335
336
    /**
337
     * Calculate the BLAKE2b hash of a file.
338
     *
339
     * @param string      $filePath     Absolute path to a file on the filesystem
340
     * @param string|null $key          BLAKE2b key
341
     * @param int         $outputLength Length of hash output
342
     *
343
     * @return string                   BLAKE2b hash
344
     * @throws Error
345
     * @throws TypeError
346
     * @psalm-suppress FailedTypeResolution
347
     */
348
    public static function generichash($filePath, $key = '', $outputLength = 32)
349
    {
350
        /* Type checks: */
351
        if (!is_string($filePath)) {
352
            throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
353
        }
354
        if (!is_string($key)) {
355
            if (is_null($key)) {
356
                $key = '';
357
            } else {
358
                throw new TypeError('Argument 2 must be a string, ' . gettype($key) . ' given.');
359
            }
360
        }
361
        if (!is_int($outputLength)) {
362
            if (!is_numeric($outputLength)) {
363
                throw new TypeError('Argument 3 must be an integer, ' . gettype($outputLength) . ' given.');
364
            }
365
            $outputLength = (int) $outputLength;
366
        }
367
368
        /* Input validation: */
369
        if (!empty($key)) {
370
            if (self::strlen($key) < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MIN) {
371
                throw new TypeError('Argument 2 must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes');
372
            }
373
            if (self::strlen($key) > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MAX) {
374
                throw new TypeError('Argument 2 must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes');
375
            }
376
        }
377
        if ($outputLength < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MIN) {
378
            throw new Error('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MIN');
379
        }
380
        if ($outputLength > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MAX) {
381
            throw new Error('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MAX');
382
        }
383
384
        /** @var int $size */
385
        $size = filesize($filePath);
386
        if (!is_int($size)) {
387
            throw new Error('Could not obtain the file size');
388
        }
389
390
        /** @var resource $fp */
391
        $fp = fopen($filePath, 'rb');
392
        if (!is_resource($fp)) {
393
            throw new Error('Could not open input file for reading');
394
        }
395
        $ctx = ParagonIE_Sodium_Compat::crypto_generichash_init($key, $outputLength);
396
        while ($size > 0) {
397
            $blockSize = $size > 64
398
                ? 64
399
                : $size;
400
            $read = fread($fp, $blockSize);
401
            if (!is_string($read)) {
402
                throw new Error('Could not read input file');
403
            }
404
            ParagonIE_Sodium_Compat::crypto_generichash_update($ctx, $read);
405
            $size -= $blockSize;
406
        }
407
408
        fclose($fp);
409
        return ParagonIE_Sodium_Compat::crypto_generichash_final($ctx, $outputLength);
410
    }
411
412
    /**
413
     * Encrypt a file (rather than a string). Uses less memory than
414
     * ParagonIE_Sodium_Compat::crypto_secretbox(), but produces
415
     * the same result.
416
     *
417
     * @param string $inputFile  Absolute path to a file on the filesystem
418
     * @param string $outputFile Absolute path to a file on the filesystem
419
     * @param string $nonce      Number to be used only once
420
     * @param string $key        Encryption key
421
     *
422
     * @return bool
423
     * @throws Error
424
     * @throws TypeError
425
     */
426 View Code Duplication
    public static function secretbox($inputFile, $outputFile, $nonce, $key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
427
    {
428
        /* Type checks: */
429
        if (!is_string($inputFile)) {
430
            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given..');
431
        }
432
        if (!is_string($outputFile)) {
433
            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
434
        }
435
        if (!is_string($nonce)) {
436
            throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
437
        }
438
439
        /* Input validation: */
440
        if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
441
            throw new TypeError('Argument 3 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
442
        }
443
        if (!is_string($key)) {
444
            throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
445
        }
446
        if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
447
            throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_KEYBYTES bytes');
448
        }
449
450
        /** @var int $size */
451
        $size = filesize($inputFile);
452
        if (!is_int($size)) {
453
            throw new Error('Could not obtain the file size');
454
        }
455
456
        /** @var resource $ifp */
457
        $ifp = fopen($inputFile, 'rb');
458
        if (!is_resource($ifp)) {
459
            throw new Error('Could not open input file for reading');
460
        }
461
462
        /** @var resource $ofp */
463
        $ofp = fopen($outputFile, 'wb');
464
        if (!is_resource($ofp)) {
465
            fclose($ifp);
466
            throw new Error('Could not open output file for writing');
467
        }
468
469
        $res = self::secretbox_encrypt($ifp, $ofp, $size, $nonce, $key);
470
        fclose($ifp);
471
        fclose($ofp);
472
        return $res;
473
    }
474
    /**
475
     * Seal a file (rather than a string). Uses less memory than
476
     * ParagonIE_Sodium_Compat::crypto_secretbox_open(), but produces
477
     * the same result.
478
     *
479
     * Warning: Does not protect against TOCTOU attacks. You should
480
     * just load the file into memory and use crypto_secretbox_open() if
481
     * you are worried about those.
482
     *
483
     * @param string $inputFile
484
     * @param string $outputFile
485
     * @param string $nonce
486
     * @param string $key
487
     * @return bool
488
     * @throws Error
489
     * @throws TypeError
490
     */
491 View Code Duplication
    public static function secretbox_open($inputFile, $outputFile, $nonce, $key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
492
    {
493
        /* Type checks: */
494
        if (!is_string($inputFile)) {
495
            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
496
        }
497
        if (!is_string($outputFile)) {
498
            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
499
        }
500
        if (!is_string($nonce)) {
501
            throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
502
        }
503
        if (!is_string($key)) {
504
            throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
505
        }
506
507
        /* Input validation: */
508
        if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
509
            throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
510
        }
511
        if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
512
            throw new TypeError('Argument 4 must be CRYPTO_SECRETBOXBOX_KEYBYTES bytes');
513
        }
514
515
        /** @var int $size */
516
        $size = filesize($inputFile);
517
        if (!is_int($size)) {
518
            throw new Error('Could not obtain the file size');
519
        }
520
521
        /** @var resource $ifp */
522
        $ifp = fopen($inputFile, 'rb');
523
        if (!is_resource($ifp)) {
524
            throw new Error('Could not open input file for reading');
525
        }
526
527
        /** @var resource $ofp */
528
        $ofp = fopen($outputFile, 'wb');
529
        if (!is_resource($ofp)) {
530
            fclose($ifp);
531
            throw new Error('Could not open output file for writing');
532
        }
533
534
        $res = self::secretbox_decrypt($ifp, $ofp, $size, $nonce, $key);
535
        fclose($ifp);
536
        fclose($ofp);
537
        try {
538
            ParagonIE_Sodium_Compat::memzero($key);
539
        } catch (Error $ex) {
0 ignored issues
show
Bug introduced by
The class Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
540
            unset($key);
541
        }
542
        return $res;
543
    }
544
545
    /**
546
     * Sign a file (rather than a string). Uses less memory than
547
     * ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
548
     * the same result.
549
     *
550
     * @param string $filePath  Absolute path to a file on the filesystem
551
     * @param string $secretKey Secret signing key
552
     *
553
     * @return string           Ed25519 signature
554
     * @throws Error
555
     * @throws TypeError
556
     */
557
    public static function sign($filePath, $secretKey)
558
    {
559
        /* Type checks: */
560
        if (!is_string($filePath)) {
561
            throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
562
        }
563
        if (!is_string($secretKey)) {
564
            throw new TypeError('Argument 2 must be a string, ' . gettype($secretKey) . ' given.');
565
        }
566
567
        /* Input validation: */
568
        if (self::strlen($secretKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_SECRETKEYBYTES) {
569
            throw new TypeError('Argument 2 must be CRYPTO_SIGN_SECRETKEYBYTES bytes');
570
        }
571
572
        /** @var int $size */
573
        $size = filesize($filePath);
574
        if (!is_int($size)) {
575
            throw new Error('Could not obtain the file size');
576
        }
577
578
        /** @var resource $fp */
579
        $fp = fopen($filePath, 'rb');
580
        if (!is_resource($fp)) {
581
            throw new Error('Could not open input file for reading');
582
        }
583
584
        /** @var string $az */
585
        $az = hash('sha512', self::substr($secretKey, 0, 32), true);
586
587
        $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
588
        $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
589
590
        /** @var resource $hs */
591
        $hs = hash_init('sha512');
592
        hash_update($hs, self::substr($az, 32, 32));
593
        $hs = self::updateHashWithFile($hs, $fp, $size);
594
595
        /** @var string $nonceHash */
596
        $nonceHash = hash_final($hs, true);
597
598
        /** @var string $pk */
599
        $pk = self::substr($secretKey, 32, 32);
600
601
        /** @var string $nonce */
602
        $nonce = ParagonIE_Sodium_Core_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
603
604
        /** @var string $sig */
605
        $sig = ParagonIE_Sodium_Core_Ed25519::ge_p3_tobytes(
606
            ParagonIE_Sodium_Core_Ed25519::ge_scalarmult_base($nonce)
607
        );
608
609
        /** @var resource $hs */
610
        $hs = hash_init('sha512');
611
        hash_update($hs, self::substr($sig, 0, 32));
612
        hash_update($hs, self::substr($pk, 0, 32));
613
        $hs = self::updateHashWithFile($hs, $fp, $size);
614
615
        /** @var string $hramHash */
616
        $hramHash = hash_final($hs, true);
617
618
        /** @var string $hram */
619
        $hram = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hramHash);
620
621
        /** @var string $sigAfter */
622
        $sigAfter = ParagonIE_Sodium_Core_Ed25519::sc_muladd($hram, $az, $nonce);
623
624
        /** @var string $sig */
625
        $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
626
627
        try {
628
            ParagonIE_Sodium_Compat::memzero($az);
629
        } catch (Error $ex) {
0 ignored issues
show
Bug introduced by
The class Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
630
            $az = null;
0 ignored issues
show
Unused Code introduced by
$az is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
631
        }
632
        fclose($fp);
633
        return $sig;
634
    }
635
636
    /**
637
     * Verify a file (rather than a string). Uses less memory than
638
     * ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
639
     * produces the same result.
640
     *
641
     * @param string $sig       Ed25519 signature
642
     * @param string $filePath  Absolute path to a file on the filesystem
643
     * @param string $publicKey Signing public key
644
     *
645
     * @return bool
646
     * @throws Error
647
     * @throws Exception
648
     */
649
    public static function verify($sig, $filePath, $publicKey)
650
    {
651
        /* Type checks: */
652
        if (!is_string($sig)) {
653
            throw new TypeError('Argument 1 must be a string, ' . gettype($sig) . ' given.');
654
        }
655
        if (!is_string($filePath)) {
656
            throw new TypeError('Argument 2 must be a string, ' . gettype($filePath) . ' given.');
657
        }
658
        if (!is_string($publicKey)) {
659
            throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
660
        }
661
662
        /* Input validation: */
663
        if (self::strlen($sig) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_BYTES) {
664
            throw new TypeError('Argument 1 must be CRYPTO_SIGN_BYTES bytes');
665
        }
666
        if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_PUBLICKEYBYTES) {
667
            throw new TypeError('Argument 3 must be CRYPTO_SIGN_PUBLICKEYBYTES bytes');
668
        }
669
        if (self::strlen($sig) < 64) {
670
            throw new Exception('Signature is too short');
671
        }
672
673
        /* Security checks */
674
        if (ParagonIE_Sodium_Core_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))) {
675
            throw new Exception('S < L - Invalid signature');
676
        }
677
        if (ParagonIE_Sodium_Core_Ed25519::small_order($sig)) {
678
            throw new Exception('Signature is on too small of an order');
679
        }
680
        if ((self::chrToInt($sig[63]) & 224) !== 0) {
681
            throw new Exception('Invalid signature');
682
        }
683
        $d = 0;
684
        for ($i = 0; $i < 32; ++$i) {
685
            $d |= self::chrToInt($publicKey[$i]);
686
        }
687
        if ($d === 0) {
688
            throw new Exception('All zero public key');
689
        }
690
691
        /** @var int $size */
692
        $size = filesize($filePath);
693
        if (!is_int($size)) {
694
            throw new Error('Could not obtain the file size');
695
        }
696
697
        /** @var resource $fp */
698
        $fp = fopen($filePath, 'rb');
699
        if (!is_resource($fp)) {
700
            throw new Error('Could not open input file for reading');
701
        }
702
703
        /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
704
        $orig = ParagonIE_Sodium_Compat::$fastMult;
705
706
        // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
707
        ParagonIE_Sodium_Compat::$fastMult = true;
708
709
        /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A */
710
        $A = ParagonIE_Sodium_Core_Ed25519::ge_frombytes_negate_vartime($publicKey);
711
712
        /** @var resource $hs */
713
        $hs = hash_init('sha512');
714
        hash_update($hs, self::substr($sig, 0, 32));
715
        hash_update($hs, self::substr($publicKey, 0, 32));
716
        $hs = self::updateHashWithFile($hs, $fp, $size);
717
        /** @var string $hDigest */
718
        $hDigest = hash_final($hs, true);
719
720
        /** @var string $h */
721
        $h = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
722
723
        /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P2 $R */
724
        $R = ParagonIE_Sodium_Core_Ed25519::ge_double_scalarmult_vartime(
725
            $h,
726
            $A,
727
            self::substr($sig, 32)
728
        );
729
730
        /** @var string $rcheck */
731
        $rcheck = ParagonIE_Sodium_Core_Ed25519::ge_tobytes($R);
732
733
        // Close the file handle
734
        fclose($fp);
735
736
        // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
737
        ParagonIE_Sodium_Compat::$fastMult = $orig;
738
        return self::verify_32($rcheck, self::substr($sig, 0, 32));
739
    }
740
741
    /**
742
     * @param resource $ifp
743
     * @param resource $ofp
744
     * @param int      $mlen
745
     * @param string   $nonce
746
     * @param string   $boxKeypair
747
     * @return bool
748
     */
749
    protected static function box_encrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
750
    {
751
        return self::secretbox_encrypt(
752
            $ifp,
753
            $ofp,
754
            $mlen,
755
            $nonce,
756
            ParagonIE_Sodium_Crypto::box_beforenm(
757
                ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
758
                ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
759
            )
760
        );
761
    }
762
763
764
    /**
765
     * @param resource $ifp
766
     * @param resource $ofp
767
     * @param int      $mlen
768
     * @param string   $nonce
769
     * @param string   $boxKeypair
770
     * @return bool
771
     */
772
    protected static function box_decrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
773
    {
774
        return self::secretbox_decrypt(
775
            $ifp,
776
            $ofp,
777
            $mlen,
778
            $nonce,
779
            ParagonIE_Sodium_Crypto::box_beforenm(
780
                ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
781
                ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
782
            )
783
        );
784
    }
785
786
    /**
787
     * Encrypt a file
788
     *
789
     * @param resource $ifp
790
     * @param resource $ofp
791
     * @param int $mlen
792
     * @param string $nonce
793
     * @param string $key
794
     * @return bool
795
     * @throws Error
796
     */
797
    protected static function secretbox_encrypt($ifp, $ofp, $mlen, $nonce, $key)
798
    {
799
        $plaintext = fread($ifp, 32);
800
        if (!is_string($plaintext)) {
801
            throw new Error('Could not read input file');
802
        }
803
        $first32 = ftell($ifp);
804
805
        /** @var string $subkey */
806
        $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
807
808
        /** @var string $realNonce */
809
        $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
810
811
        /** @var string $block0 */
812
        $block0 = str_repeat("\x00", 32);
813
814
        /** @var int $mlen - Length of the plaintext message */
815
        $mlen0 = $mlen;
816
        if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
817
            $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
818
        }
819
        $block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);
820
821
        /** @var string $block0 */
822
        $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20_xor(
823
            $block0,
824
            $realNonce,
825
            $subkey
826
        );
827
828
        $state = new ParagonIE_Sodium_Core_Poly1305_State(
829
            ParagonIE_Sodium_Core_Util::substr(
830
                $block0,
831
                0,
832
                ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
833
            )
834
        );
835
836
        // Pre-write 16 blank bytes for the Poly1305 tag
837
        $start = ftell($ofp);
838
        fwrite($ofp, str_repeat("\x00", 16));
839
840
        /** @var string $c */
841
        $cBlock = ParagonIE_Sodium_Core_Util::substr(
842
            $block0,
843
            ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
844
        );
845
        $state->update($cBlock);
846
        fwrite($ofp, $cBlock);
847
        $mlen -= 32;
848
849
        /** @var int $iter */
850
        $iter = 1;
851
852
        /** @var int $incr */
853
        $incr = self::BUFFER_SIZE >> 6;
854
855
        /*
856
         * Set the cursor to the end of the first half-block. All future bytes will
857
         * generated from salsa20_xor_ic, starting from 1 (second block).
858
         */
859
        fseek($ifp, $first32, SEEK_SET);
860
861 View Code Duplication
        while ($mlen > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
862
            $blockSize = $mlen > self::BUFFER_SIZE
863
                ? self::BUFFER_SIZE
864
                : $mlen;
865
            $plaintext = fread($ifp, $blockSize);
866
            if (!is_string($plaintext)) {
867
                throw new Error('Could not read input file');
868
            }
869
            $cBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
870
                $plaintext,
871
                $realNonce,
872
                $iter,
873
                $subkey
874
            );
875
            fwrite($ofp, $cBlock, $blockSize);
876
            $state->update($cBlock);
877
878
            $mlen -= $blockSize;
879
            $iter += $incr;
880
        }
881
        try {
882
            ParagonIE_Sodium_Compat::memzero($block0);
883
            ParagonIE_Sodium_Compat::memzero($subkey);
884
        } catch (Error $ex) {
0 ignored issues
show
Bug introduced by
The class Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
885
            $block0 = null;
0 ignored issues
show
Unused Code introduced by
$block0 is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
886
            $subkey = null;
0 ignored issues
show
Unused Code introduced by
$subkey is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
887
        }
888
        $end = ftell($ofp);
889
890
        /*
891
         * Write the Poly1305 authentication tag that provides integrity
892
         * over the ciphertext (encrypt-then-MAC)
893
         */
894
        fseek($ofp, $start, SEEK_SET);
895
        fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
896
        fseek($ofp, $end, SEEK_SET);
897
        unset($state);
898
899
        return true;
900
    }
901
902
    /**
903
     * Decrypt a file
904
     *
905
     * @param resource $ifp
906
     * @param resource $ofp
907
     * @param int $mlen
908
     * @param string $nonce
909
     * @param string $key
910
     * @return bool
911
     * @throws Error
912
     * @throws Exception
913
     */
914
    protected static function secretbox_decrypt($ifp, $ofp, $mlen, $nonce, $key)
915
    {
916
        $tag = fread($ifp, 16);
917
        if (!is_string($tag)) {
918
            throw new Error('Could not read input file');
919
        }
920
921
        /** @var string $subkey */
922
        $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
923
924
        /** @var string $realNonce */
925
        $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
926
927
        /** @var string $block0 */
928
        $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20(
929
            64,
930
            ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
931
            $subkey
932
        );
933
934
        /* Verify the Poly1305 MAC -before- attempting to decrypt! */
935
        $state = new ParagonIE_Sodium_Core_Poly1305_State(self::substr($block0, 0, 32));
936
        if (!self::onetimeauth_verify($state, $ifp, $tag, $mlen)) {
937
            throw new Exception('Invalid MAC');
938
        }
939
940
        /*
941
         * Set the cursor to the end of the first half-block. All future bytes will
942
         * generated from salsa20_xor_ic, starting from 1 (second block).
943
         */
944
        $first32 = fread($ifp, 32);
945
        if (!is_string($first32)) {
946
            throw new Error('Could not read input file');
947
        }
948
        $first32len = self::strlen($first32);
949
        fwrite(
950
            $ofp,
951
            self::xorStrings(
952
                self::substr($block0, 32, $first32len),
953
                self::substr($first32, 0, $first32len)
954
            )
955
        );
956
        $mlen -= 32;
957
958
        /** @var int $iter */
959
        $iter = 1;
960
961
        /** @var int $incr */
962
        $incr = self::BUFFER_SIZE >> 6;
963
964
        /* Decrypts ciphertext, writes to output file. */
965 View Code Duplication
        while ($mlen > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
966
            $blockSize = $mlen > self::BUFFER_SIZE
967
                ? self::BUFFER_SIZE
968
                : $mlen;
969
            $ciphertext = fread($ifp, $blockSize);
970
            if (!is_string($ciphertext)) {
971
                throw new Error('Could not read input file');
972
            }
973
            $pBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
974
                $ciphertext,
975
                $realNonce,
976
                $iter,
977
                $subkey
978
            );
979
            fwrite($ofp, $pBlock, $blockSize);
980
            $mlen -= $blockSize;
981
            $iter += $incr;
982
        }
983
        return true;
984
    }
985
986
    /**
987
     * @param ParagonIE_Sodium_Core_Poly1305_State $state
988
     * @param resource $ifp
989
     * @param string $tag
990
     * @param int $mlen
991
     * @return bool
992
     * @throws Error
993
     */
994
    protected static function onetimeauth_verify(ParagonIE_Sodium_Core_Poly1305_State $state, $ifp, $tag = '', $mlen = 0)
995
    {
996
        /** @var int $pos */
997
        $pos = ftell($ifp);
998
999
        /** @var int $iter */
1000
        $iter = 1;
1001
1002
        /** @var int $incr */
1003
        $incr = self::BUFFER_SIZE >> 6;
1004
1005
        while ($mlen > 0) {
1006
            $blockSize = $mlen > self::BUFFER_SIZE
1007
                ? self::BUFFER_SIZE
1008
                : $mlen;
1009
            $ciphertext = fread($ifp, $blockSize);
1010
            if (!is_string($ciphertext)) {
1011
                throw new Error('Could not read input file');
1012
            }
1013
            $state->update($ciphertext);
1014
            $mlen -= $blockSize;
1015
            $iter += $incr;
1016
        }
1017
        $res = ParagonIE_Sodium_Core_Util::verify_16($tag, $state->finish());
1018
1019
        fseek($ifp, $pos, SEEK_SET);
1020
        return $res;
1021
    }
1022
1023
    /**
1024
     * Update a hash context with the contents of a file, without
1025
     * loading the entire file into memory.
1026
     *
1027
     * @param resource|object $hash
1028
     * @param resource $fp
1029
     * @param int $size
1030
     * @return mixed (resource on PHP < 7.2, object on PHP >= 7.2)
1031
     * @throws Error
1032
     * @throws TypeError
1033
     * @psalm-suppress PossiblyInvalidArgument
1034
     *                 PHP 7.2 changes from a resource to an object,
1035
     *                 which causes Psalm to complain about an error.
1036
     */
1037
    public static function updateHashWithFile($hash, $fp, $size = 0)
1038
    {
1039
        /* Type checks: */
1040
        if (PHP_VERSION_ID < 70200) {
1041
            if (!is_resource($hash)) {
1042
                throw new TypeError('Argument 1 must be a resource, ' . gettype($hash) . ' given.');
1043
            }
1044
1045
        } else {
1046
            if (!is_object($hash)) {
1047
                throw new TypeError('Argument 1 must be an object (PHP 7.2+), ' . gettype($hash) . ' given.');
1048
            }
1049
        }
1050
        if (!is_resource($fp)) {
1051
            throw new TypeError('Argument 2 must be a resource, ' . gettype($fp) . ' given.');
1052
        }
1053
        if (!is_int($size)) {
1054
            throw new TypeError('Argument 3 must be an integer, ' . gettype($size) . ' given.');
1055
        }
1056
1057
        /** @var int $originalPosition */
1058
        $originalPosition = ftell($fp);
1059
1060
        // Move file pointer to beginning of file
1061
        fseek($fp, 0, SEEK_SET);
1062
        for ($i = 0; $i < $size; $i += self::BUFFER_SIZE) {
1063
            /** @var string $message */
1064
            $message = fread(
1065
                $fp,
1066
                ($size - $i) > self::BUFFER_SIZE
1067
                    ? $size - $i
1068
                    : self::BUFFER_SIZE
1069
            );
1070
            if (!is_string($message)) {
1071
                throw new Error('Unexpected error reading from file.');
1072
            }
1073
            /** @psalm-suppress InvalidArgument */
1074
            hash_update($hash, $message);
1075
        }
1076
        // Reset file pointer's position
1077
        fseek($fp, $originalPosition, SEEK_SET);
1078
        return $hash;
1079
    }
1080
}
1081