Passed
Push — master ( 87ad4b...7a89d8 )
by Oliver
01:25
created

Shamir::recover()   B

Complexity

Conditions 11
Paths 23

Size

Total Lines 62
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 11.0699

Importance

Changes 3
Bugs 1 Features 1
Metric Value
cc 11
eloc 35
c 3
b 1
f 1
nc 23
nop 1
dl 0
loc 62
ccs 33
cts 36
cp 0.9167
crap 11.0699
rs 7.3166

How to fix   Long Method    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
namespace TQ\Shamir\Algorithm;
4
5
use OutOfRangeException;
6
use RuntimeException;
7
use TQ\Shamir\Random\Generator;
8
use TQ\Shamir\Random\PhpGenerator;
9
10
/**
11
 * Class Shamir
12
 *
13
 * Based on "Shamir's Secret Sharing class" from Kenny Millington
14
 *
15
 * @link    https://www.kennynet.co.uk/misc/shamir.class.txt
16
 *
17
 * @package TQ\Shamir\Algorithm
18
 */
19
class Shamir implements Algorithm, RandomGeneratorAware
20
{
21
    /**
22
     * Calculation base (decimal)
23
     *
24
     * Changing this will invalid all previously created keys.
25
     *
26
     * @const   string
27
     */
28
    protected const DECIMAL = '0123456789';
29
30
    /**
31
     * Target base characters to be used in passwords (shares)
32
     *
33
     * The more characters are used, the shorter the shares might get.
34
     * Changing this will invalid all previously created keys.
35
     *
36
     * @const   string
37
     */
38
    protected const CHARS = '0123456789abcdefghijklmnopqrstuvwxyz.,:;-+*#%';
39
40
    /**
41
     * Character to fill up the secret keys
42
     *
43
     * @const   string
44
     */
45
    protected const PAD_CHAR = '=';
46
47
    /**
48
     * Prime number has to be greater than the maximum number of shares possible
49
     *
50
     * @var float
51
     */
52
    protected $prime = 257;
53
54
    /**
55
     * Chunk size in bytes
56
     *
57
     * The secret will be divided equally. This value defines the chunk size and
58
     * how many bytes will get encoded at once.
59
     *
60
     * @var int
61
     */
62
    protected $chunkSize = 1;
63
64
    /**
65
     * The random generator
66
     *
67
     * @var Generator
68
     */
69
    protected $randomGenerator;
70
71
    /**
72
     * Maximum number of shares required
73
     *
74
     * @var float
75
     */
76
    protected $maxShares = 3;
77
78
    /**
79
     * @inheritdoc
80
     */
81 19
    public function getRandomGenerator(): Generator
82
    {
83 19
        if (!$this->randomGenerator) {
84 14
            $this->randomGenerator = new PhpGenerator();
85
        }
86
87 19
        return $this->randomGenerator;
88
    }
89
90
    /**
91
     * @inheritdoc
92
     */
93 63
    public function setRandomGenerator(Generator $generator): Shamir
94
    {
95 63
        $this->randomGenerator = $generator;
96
97 63
        return $this;
98
    }
99
100
    /**
101
     * Returns chunk size in bytes
102
     */
103 14
    public function getChunkSize(): int
104
    {
105 14
        return $this->chunkSize;
106
    }
107
108
    /**
109
     * Sets chunk size in bytes
110
     *
111
     * If maximum shares have been set already, the chunk
112
     * size might have been set with it. It is not possible
113
     * to set a smaller size than required by shares.
114
     *
115
     * @see
116
     * @param  int  $chunkSize  Size in number of bytes
117
     * @throws OutOfRangeException
118
     */
119 36
    public function setChunkSize(int $chunkSize): Shamir
120
    {
121 36
        $primeNumber = [1 => 257, 65537, 16777259, 4294967311, 1099511627791, 281474976710677, 72057594037928017];
122
123 36
        if (!isset($primeNumber[$chunkSize])) {
124 4
            throw new OutOfRangeException(
125 4
                'Chunk size with '.$chunkSize.' bytes is not allowed. Use 1 to '.count($primeNumber).'.'
126 4
            );
127
        }
128
129 32
        $this->chunkSize = $chunkSize;
130
        // if chunk size has been set already, we will only increase it, if necessary
131 32
        $this->prime = $primeNumber[$chunkSize];
132
133 32
        return $this;
134
    }
135
136
    /**
137
     * Configure encoding parameters
138
     *
139
     * Depending on the number of required shares, we need to change
140
     * prime number, key length, chunk size and more.
141
     *
142
     * If the chunk size has been set already, it will be changed, if
143
     * it is smaller than the necessary size.
144
     *
145
     * @see    setChunkSize()
146
     * @param  int  $max  Maximum number of keys needed
147
     * @throws OutOfRangeException
148
     */
149 19
    protected function setMaxShares(int $max): Shamir
150
    {
151
        // the prime number has to be larger, than the maximum number
152
        // representable by the number of bytes. so we always need one
153
        // byte more for encryption. if someone wants to use 256 shares,
154
        // we could encrypt 256 with a single byte, but due to encrypting
155
        // with a bigger prime number, we will need to use 2 bytes.
156
157
        // max possible number of shares is the maximum number of bytes
158
        // possible to be represented with max integer, but we always need
159
        // to save one byte for encryption.
160 19
        $maxPossible = 1 << (PHP_INT_SIZE - 1) * 8;
161
162 19
        if ($max > $maxPossible) {
163
            // we are unable to provide more bytes-1 as supported by OS
164
            // because the prime number need to be higher than that, but
165
            // this would exceed OS integer range.
166
            throw new OutOfRangeException(
167
                'Number of required keys has to be below '.number_format($maxPossible).'.'
168
            );
169
        }
170
171
        // calculate how many bytes we need to represent the number of shares.
172
        // e.g., everything less than 256 needs only a single byte.
173 19
        $chunkSize = (int)ceil(log($max, 2) / 8);
174
        // if chunk size has been set already, we will only increase it, if necessary
175 19
        $chunkSize = max($chunkSize, $this->chunkSize);
176
177 19
        if ($chunkSize > $this->chunkSize) {
178 2
            $this->setChunkSize($chunkSize);
179
        }
180
181 19
        $this->maxShares = $max;
182
183 19
        return $this;
184
    }
185
186
    /**
187
     * Calculate modulo of any given number using prime
188
     *
189
     * @return int     Module of number
190
     */
191 18
    protected function modulo(string $number): int
192
    {
193 18
        $modulo = bcmod($number, $this->prime);
194
195 18
        return ($modulo < 0) ? bcadd($modulo, $this->prime) : $modulo;
196
    }
197
198
    /**
199
     * Returns decomposition of the greatest common divisor of a and b
200
     */
201 18
    protected function gcdD(int $a, string $b): array
202
    {
203 18
        if ($b === '0') {
204 18
            return [$a, 1, 0];
205
        }
206
207 18
        $div    = floor(bcdiv($a, $b));
0 ignored issues
show
Bug introduced by
bcdiv($a, $b) of type null|string is incompatible with the type double|integer expected by parameter $num of floor(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

207
        $div    = floor(/** @scrutinizer ignore-type */ bcdiv($a, $b));
Loading history...
208 18
        $mod    = bcmod($a, $b);
209 18
        $decomp = $this->gcdD($b, $mod);
0 ignored issues
show
Bug introduced by
$b of type string is incompatible with the type integer expected by parameter $a of TQ\Shamir\Algorithm\Shamir::gcdD(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

209
        $decomp = $this->gcdD(/** @scrutinizer ignore-type */ $b, $mod);
Loading history...
210
211 18
        return [$decomp[0], $decomp[2], $decomp[1] - $decomp[2] * $div];
212
    }
213
214
    /**
215
     * Calculates the inverse modulo
216
     */
217 18
    protected function inverseModulo(int $number): string
218
    {
219 18
        $mod = bcmod($number, $this->prime);
220 18
        $r   = $this->gcdD($this->prime, abs($mod));
0 ignored issues
show
Bug introduced by
$mod of type null|string is incompatible with the type double|integer expected by parameter $num of abs(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

220
        $r   = $this->gcdD($this->prime, abs(/** @scrutinizer ignore-type */ $mod));
Loading history...
Bug introduced by
$this->prime of type double is incompatible with the type integer expected by parameter $a of TQ\Shamir\Algorithm\Shamir::gcdD(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

220
        $r   = $this->gcdD(/** @scrutinizer ignore-type */ $this->prime, abs($mod));
Loading history...
221 18
        $r   = ($mod < 0) ? -$r[2] : $r[2];
222
223 18
        return bcmod(bcadd($this->prime, $r), $this->prime);
224
    }
225
226
    /**
227
     * Calculates the reverse coefficients
228
     *
229
     * @throws RuntimeException
230
     */
231 18
    protected function reverseCoefficients(array $keyX, int $threshold): array
232
    {
233 18
        $coefficients = [];
234
235 18
        for ($i = 0; $i < $threshold; $i++) {
236 18
            $temp = 1;
237 18
            for ($j = 0; $j < $threshold; $j++) {
238 18
                if ($i !== $j) {
239 18
                    $temp = $this->modulo(
240 18
                        bcmul(bcmul(-$temp, $keyX[$j]), $this->inverseModulo($keyX[$i] - $keyX[$j]))
241 18
                    );
242
                }
243
            }
244
245 18
            if ($temp === 0) {
246
                /* Repeated share */
247
                throw new RuntimeException('Repeated share detected - cannot compute reverse-coefficients');
248
            }
249
250 18
            $coefficients[] = $temp;
251
        }
252
253 18
        return $coefficients;
254
    }
255
256
    /**
257
     * Generate random coefficients
258
     *
259
     * @param  int  $threshold  Number of coefficients needed
260
     */
261 18
    protected function generateCoefficients(int $threshold): array
262
    {
263 18
        $coefficients = [];
264 18
        for ($i = 0; $i < $threshold - 1; $i++) {
265
            do {
266
                // the random number has to be positive integer != 0
267 18
                $random = abs($this->getRandomGenerator()->getRandomInt());
268 18
            } while ($random < 1);
269 18
            $coefficients[] = $this->modulo($random);
270
        }
271
272 18
        return $coefficients;
273
    }
274
275
    /**
276
     * Calculate y values of polynomial curve using horner's method
277
     *
278
     * Horner converts a polynomial formula like
279
     * 11 + 7x - 5x^2 - 4x^3 + 2x^4
280
     * into a more efficient formula
281
     * 11 + x * ( 7 + x * ( -5 + x * ( -4 + x * 2 ) ) )
282
     *
283
     * @see    https://en.wikipedia.org/wiki/Horner%27s_method
284
     * @param  int    $xCoordinate   X coordinate
285
     * @param  array  $coefficients  Polynomial coefficients
286
     * @return int                   Y coordinate
287
     */
288 18
    protected function hornerMethod(int $xCoordinate, array $coefficients): int
289
    {
290 18
        $yCoordinate = 0;
291 18
        foreach ($coefficients as $coefficient) {
292 18
            $yCoordinate = $this->modulo($xCoordinate * $yCoordinate + $coefficient);
293
        }
294
295 18
        return $yCoordinate;
296
    }
297
298
    /**
299
     * Converts from $fromBaseInput to $toBaseInput
300
     */
301 39
    protected static function convBase(string $numberInput, string $fromBaseInput, string $toBaseInput): string
302
    {
303 39
        if ($fromBaseInput === $toBaseInput) {
304 3
            return $numberInput;
305
        }
306 36
        $fromBase  = str_split($fromBaseInput, 1);
307 36
        $toBase    = str_split($toBaseInput, 1);
308 36
        $number    = str_split($numberInput, 1);
309 36
        $fromLen   = strlen($fromBaseInput);
310 36
        $toLen     = strlen($toBaseInput);
311 36
        $numberLen = strlen($numberInput);
312 36
        $retVal    = '';
313 36
        if ($toBaseInput === '0123456789') {
314 27
            $retVal = 0;
315 27
            for ($i = 1; $i <= $numberLen; $i++) {
316 27
                $retVal = bcadd(
317 27
                    $retVal,
318 27
                    bcmul(array_search($number[$i - 1], $fromBase, true), bcpow($fromLen, $numberLen - $i))
0 ignored issues
show
Bug introduced by
It seems like $fromBase can also be of type true; however, parameter $haystack of array_search() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

318
                    bcmul(array_search($number[$i - 1], /** @scrutinizer ignore-type */ $fromBase, true), bcpow($fromLen, $numberLen - $i))
Loading history...
319 27
                );
320
            }
321
322 27
            return $retVal;
323
        }
324 27
        if ($fromBaseInput !== '0123456789') {
325
            $base10 = self::convBase($numberInput, $fromBaseInput, '0123456789');
326
        } else {
327 27
            $base10 = $numberInput;
328
        }
329 27
        if ($base10 < strlen($toBaseInput)) {
330 21
            return $toBase[$base10];
331
        }
332 24
        while ($base10 !== '0') {
333 24
            $retVal = $toBase[bcmod($base10, $toLen)].$retVal;
334 24
            $base10 = bcdiv($base10, $toLen, 0);
335
        }
336
337 24
        return $retVal;
338
    }
339
340
    /**
341
     * Unpack a binary string and convert it into decimals
342
     *
343
     * Convert each chunk of a binary data into decimal numbers.
344
     *
345
     * @param  string  $string  Binary string
346
     * @return array            Array with decimal converted numbers
347
     */
348 18
    protected function unpack(string $string): array
349
    {
350 18
        $chunk  = 0;
351 18
        $int    = '';
352 18
        $return = [];
353 18
        foreach (unpack('C*', $string) as $byte) {
354 18
            $int = bcadd($int, bcmul($byte, bcpow(2, $chunk * 8)));
355 18
            if (++$chunk === $this->chunkSize) {
356 18
                $return[] = $int;
357 18
                $chunk    = 0;
358 18
                $int      = '';
359
            }
360
        }
361 18
        if ($chunk > 0) {
362 7
            $return[] = $int;
363
        }
364
365 18
        return $return;
366
    }
367
368
    /**
369
     * Returns maximum length of converted string to new base
370
     *
371
     * Calculate the maximum length of a string, which can be
372
     * represented with the number of given bytes and convert
373
     * its base.
374
     *
375
     * @param  int  $bytes  Bytes used to represent a string
376
     * @return int          Number of chars
377
     */
378 18
    protected function maxKeyLength(int $bytes): int
379
    {
380 18
        $maxInt    = bcpow(2, $bytes * 8);
381 18
        $converted = self::convBase($maxInt, self::DECIMAL, self::CHARS);
382
383 18
        return strlen($converted);
384
    }
385
386
    /**
387
     * Divide secret into chunks and calculate coordinates
388
     *
389
     * @param  string  $secret     Secret
390
     * @param  int     $shares     Number of parts to share
391
     * @param  int     $threshold  Minimum number of shares required for decryption
392
     */
393 18
    protected function divideSecret(string $secret, int $shares, int $threshold): array
394
    {
395
        // divide secret into chunks, which we encrypt one by one
396 18
        $result = [];
397
398 18
        foreach ($this->unpack($secret) as $bytes) {
399 18
            $coeffs   = $this->generateCoefficients($threshold);
400 18
            $coeffs[] = $bytes;
401
402
            // go through x coordinates and calculate y value
403 18
            for ($x = 1; $x <= $shares; $x++) {
404
                // use horner method to calculate y value
405 18
                $result[] = $this->hornerMethod($x, $coeffs);
406
            }
407
        }
408
409 18
        return $result;
410
    }
411
412
    /**
413
     * @inheritdoc
414
     */
415 19
    public function share($secret, $shares, $threshold = 2): array
416
    {
417 19
        $this->setMaxShares($shares);
418
419
        // check if number of shares is less than our prime, otherwise we have a security problem
420 19
        if ($shares >= $this->prime || $shares < 1) {
421
            throw new OutOfRangeException('Number of shares has to be between 1 and '.$this->prime.'.');
422
        }
423
424 19
        if ($shares < $threshold) {
425 1
            throw new OutOfRangeException('Threshold has to be between 1 and '.$shares.'.');
426
        }
427
428 18
        if (strpos(self::CHARS, self::PAD_CHAR) !== false) {
429
            throw new OutOfRangeException('Padding character must not be part of possible encryption chars.');
430
        }
431
432
        // divide secret into chunks, which we encrypt one by one
433 18
        $result = $this->divideSecret($secret, $shares, $threshold);
434
435
        // encode number of bytes and threshold
436
437
        // calculate the maximum length of key sequence number and threshold
438 18
        $maxBaseLength = $this->maxKeyLength($this->chunkSize);
439
        // in order to do a correct padding to the converted base, we need to use the first char of the base
440 18
        $paddingChar = substr(self::CHARS, 0, 1);
441
        // define prefix number using the number of bytes (hex), and a left padded string used for threshold (base converted)
442 18
        $fixPrefixFormat = '%x%'.$paddingChar.$maxBaseLength.'s';
443
        // prefix is going to be the same for all keys
444 18
        $prefix = sprintf($fixPrefixFormat, $this->chunkSize, self::convBase($threshold, self::DECIMAL, self::CHARS));
445
446
        // convert y coordinates into hexadecimals shares
447 18
        $passwords = [];
448 18
        $secretLen = strlen($secret);
449
        // calculate how many bytes, we need to cut off during recovery
450 18
        if ($secretLen % $this->chunkSize > 0) {
451 7
            $tail = str_repeat(self::PAD_CHAR, $this->chunkSize - $secretLen % $this->chunkSize);
452
        } else {
453 12
            $tail = '';
454
        }
455
456 18
        $chunks = ceil($secretLen / $this->chunkSize);
457 18
        for ($i = 0; $i < $shares; ++$i) {
458 18
            $sequence = self::convBase(($i + 1), self::DECIMAL, self::CHARS);
459 18
            $key      = sprintf($prefix.'%'.$paddingChar.$maxBaseLength.'s', $sequence);
460
461 18
            for ($j = 0; $j < $chunks; ++$j) {
462 18
                $key .= str_pad(
463 18
                    self::convBase($result[$j * $shares + $i], self::DECIMAL, self::CHARS),
464 18
                    $maxBaseLength,
465 18
                    $paddingChar,
466 18
                    STR_PAD_LEFT
467 18
                );
468
            }
469 18
            $passwords[] = $key.$tail;
470
        }
471
472 18
        return $passwords;
473
    }
474
475
    /**
476
     * Decode and merge secret chunks
477
     *
478
     * @param  array  $keyX       Keys for X coordinates
479
     * @param  array  $keyY       Keys for Y coordinates
480
     * @param  int    $bytes      Chunk size in bytes
481
     * @param  int    $keyLen     Key length in chunks
482
     * @param  int    $threshold  Minimum number of shares required for decryption
483
     */
484 18
    protected function joinSecret(array $keyX, array $keyY, int $bytes, int $keyLen, int $threshold): string
485
    {
486 18
        $coefficients = $this->reverseCoefficients($keyX, $threshold);
487
488 18
        $secret = '';
489 18
        for ($i = 0; $i < $keyLen; $i++) {
490 18
            $temp = 0;
491 18
            for ($j = 0; $j < $threshold; $j++) {
492 18
                $temp = $this->modulo(
493 18
                    bcadd($temp, bcmul($keyY[$j * $keyLen + $i], $coefficients[$j]))
494 18
                );
495
            }
496
            // convert each byte back into char
497 18
            for ($byte = 1; $byte <= $bytes; ++$byte) {
498 18
                $char   = bcmod($temp, 256);
499 18
                $secret .= chr($char);
0 ignored issues
show
Bug introduced by
$char of type null|string is incompatible with the type integer expected by parameter $codepoint of chr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

499
                $secret .= chr(/** @scrutinizer ignore-type */ $char);
Loading history...
500 18
                $temp   = bcdiv(bcsub($temp, $char), 256);
501
            }
502
        }
503
504 18
        return $secret;
505
    }
506
507
    /**
508
     * @inheritdoc
509
     */
510 18
    public function recover(array $keys): string
511
    {
512 18
        if (!count($keys)) {
513
            throw new RuntimeException('No keys given.');
514
        }
515
516 18
        $keyX      = [];
517 18
        $keyY      = [];
518 18
        $keyLen    = null;
519 18
        $threshold = null;
520
521
        // analyse first key
522 18
        $key = reset($keys);
523
        // first we need to find out the bytes to predict threshold and sequence length
524 18
        $bytes = hexdec(substr($key, 0, 1));
525 18
        $this->setChunkSize($bytes);
0 ignored issues
show
Bug introduced by
It seems like $bytes can also be of type double; however, parameter $chunkSize of TQ\Shamir\Algorithm\Shamir::setChunkSize() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

525
        $this->setChunkSize(/** @scrutinizer ignore-type */ $bytes);
Loading history...
526
        // calculate the maximum length of key sequence number and threshold
527 18
        $maxBaseLength = $this->maxKeyLength($bytes);
528
        // define key format: bytes (hex), threshold, sequence, and key (except of bytes, all is base converted)
529 18
        $keyFormat = '%1x%'.$maxBaseLength.'s%'.$maxBaseLength.'s%s';
530
531 18
        foreach ($keys as $key) {
532
            // remove trailing padding characters
533 18
            $key = str_replace(self::PAD_CHAR, '', $key);
534
535
            // extract "public" information of key: bytes, threshold, sequence
536
537 18
            [$keyBytes, $keyThreshold, $keySequence, $key] = sscanf($key, $keyFormat);
538 18
            $keyThreshold = (int)self::convBase($keyThreshold, self::CHARS, self::DECIMAL);
539 18
            $keySequence  = (int)self::convBase($keySequence, self::CHARS, self::DECIMAL);
540
541 18
            if ($threshold === null) {
542 18
                $threshold = $keyThreshold;
543
544 18
                if ($threshold > count($keys)) {
545 18
                    throw new RuntimeException('Not enough keys to disclose secret.');
546
                }
547 18
            } elseif ($threshold !== $keyThreshold || $bytes != hexdec($keyBytes)) {
548
                throw new RuntimeException('Given keys are incompatible.');
549
            }
550
551 18
            $keyX[] = $keySequence;
552 18
            if ($keyLen === null) {
553 18
                $keyLen = strlen($key);
554 18
            } elseif ($keyLen !== strlen($key)) {
555
                throw new RuntimeException('Given keys vary in key length.');
556
            }
557 18
            for ($i = 0; $i < $keyLen; $i += $maxBaseLength) {
558 18
                $keyY[] = self::convBase(substr($key, $i, $maxBaseLength), self::CHARS, self::DECIMAL);
559
            }
560
        }
561
562 18
        $keyLen /= $maxBaseLength;
563 18
        $secret = $this->joinSecret($keyX, $keyY, $bytes, $keyLen, $threshold);
0 ignored issues
show
Bug introduced by
It seems like $threshold can also be of type null; however, parameter $threshold of TQ\Shamir\Algorithm\Shamir::joinSecret() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

563
        $secret = $this->joinSecret($keyX, $keyY, $bytes, $keyLen, /** @scrutinizer ignore-type */ $threshold);
Loading history...
Bug introduced by
It seems like $keyLen can also be of type null; however, parameter $keyLen of TQ\Shamir\Algorithm\Shamir::joinSecret() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

563
        $secret = $this->joinSecret($keyX, $keyY, $bytes, /** @scrutinizer ignore-type */ $keyLen, $threshold);
Loading history...
564
565
        // remove padding from secret (NULL bytes);
566 18
        $padCount = substr_count(reset($keys), self::PAD_CHAR);
567 18
        if ($padCount) {
568 7
            $secret = substr($secret, 0, -1 * $padCount);
569
        }
570
571 18
        return $secret;
572
    }
573
}
574