Completed
Push — master ( 3701de...76010e )
by Michael
01:40
created

Random::bytes()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 5.9256

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 20
ccs 4
cts 6
cp 0.6667
rs 8.8571
cc 5
eloc 12
nc 7
nop 1
crap 5.9256
1
<?php
2
3
/**
4
 * Random.php
5
 * 
6
 * PHP version 5
7
 * 
8
 * @category Dcrypt
9
 * @package  Dcrypt
10
 * @author   Michael Meyer (mmeyer2k) <[email protected]>
11
 * @license  http://opensource.org/licenses/MIT The MIT License (MIT)
12
 * @link     https://github.com/mmeyer2k/dcrypt
13
 * @link     https://apigen.ci/github/mmeyer2k/dcrypt
14
 */
15
16
namespace Dcrypt;
17
18
/**
19
 * Fail-safe wrapper for mcrypt_create_iv (preferably) and
20
 * openssl_random_pseudo_bytes (fallback).
21
 *
22
 * @category Dcrypt
23
 * @package  Dcrypt
24
 * @author   Michael Meyer (mmeyer2k) <[email protected]>
25
 * @license  http://opensource.org/licenses/MIT The MIT License (MIT)
26
 * @link     https://github.com/mmeyer2k/dcrypt
27
 * @link     https://apigen.ci/github/mmeyer2k/dcrypt/class-Dcrypt.Random.html
28
 */
29
final class Random
30
{
31
    /**
32
     * Get random bytes from Mcrypt
33
     * 
34
     * @param int $bytes Number of bytes to get
35
     * 
36
     * @return string
37
     */
38 19
    private static function fromMcrypt($bytes)
39
    {
40 19
        $ret = \mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
41
42 19
        if ($ret === false) {
43
            self::toss(); // @codeCoverageIgnore
44
        }
45
46 19
        return $ret;
47
    }
48
49
    /**
50
     * Return securely generated random bytes.
51
     * 
52
     * @param int  $bytes  Number of bytes to get
53
     * 
54
     * @return string
55
     */
56 19
    public static function bytes($bytes)
57
    {
58 19
        if (!\is_int($bytes)) {
59
            throw new \exception('Number of random bytes must be an integer');
60 19
        }
61 19
            
62
        if (\function_exists('random_bytes')) {
63
            $ret = \random_bytes($bytes);
64
        } elseif (\function_exists('mcrypt_create_iv')) {
65
            $ret = self::fromMcrypt($bytes);
66
        } else {
67
            self::toss(); // @codeCoverageIgnore
68
        }
69
        
70
        if (Str::strlen($ret) !== $bytes) {
0 ignored issues
show
Bug introduced by
The variable $ret does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
71
            self::toss(); // @codeCoverageIgnore
72
        }
73
        
74
        return $ret;
75
    }
76
77
    /**
78
     * Throw an error when a failure occurs.
79
     * 
80
     * @codeCoverageIgnore
81
     */
82
    private static function toss()
83
    {
84
        $e = 'Dcrypt failed to generate a random number';
85
        throw new \exception($e);
86
    }
87
    
88 1
89
    /**
90 1
     * Deterministic seeded array shuffle function. Does not keep keys.
91
     *
92 1
     * @param array  $array  Array to shuffle
93
     * @param string $seed   Seed to use 
94
     * @param bool   $secure Whether to use secure RNG in PHP 7.1+. Use false to fall back to broken version for BC.
95 1
     *
96
     * @return array
97
     */
98 1
    public static function shuffle($array, $seed, $secure = true)
99
    {
100 1
        $count = \count($array);
101
102
        $range = \range(0, $count - 1);
103
104 1
        // Hash the seed and extract bytes to make integer with
105
        $seed = Str::substr(\hash('sha256', $seed, true), 0, PHP_INT_SIZE);
106
107
        // Convert bytes to an int
108 1
        $seed = \unpack("L", $seed);
109 1
110
        if (\version_compare(PHP_VERSION, '7.1.0') >= 0 && $secure === false) {
111 1
            // Handle PHP 7.1+ calls requiring the old implementation which has broken implementation
112
            \mt_srand($seed[1], MT_RAND_PHP);
113 1
        } else {
114
            \mt_srand($seed[1]);
115 1
        }
116 1
117
        // Swap array values randomly
118 1
        foreach ($range as $a) {
119
            $b = \mt_rand(0, $count - 1);
120
121
            $v = $array[$a];
122
123
            $array[$a] = $array[$b];
124
125
            $array[$b] = $v;
126
        }
127
128
        return $array;
129
    }
130
}
131