-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRandom.php
137 lines (116 loc) · 3.08 KB
/
Random.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<?php
namespace g105b\drng;
class Random {
const BYTE_SIZE = 16;
private string $seedBytes;
private int $aesCounter;
/**
* Must be constructed with a random seed of 16*n bytes, as required
* by openssl to generate the randomness.
*/
public function __construct(string $seedBytes = null) {
if(is_null($seedBytes)) {
$seedBytes = random_bytes(self::BYTE_SIZE);
}
// We are using OpenSSL in AES counter method, so need to retain a counter.
$this->aesCounter = 0;
$this->checkSeedSize($seedBytes);
$this->seedBytes = $seedBytes;
}
/**
* Return $size bytes from the random sequence determined by the seed.
*/
public function getBytes(int $size):string {
return openssl_encrypt(
str_repeat("\0", $size),
"aes-128-ctr",
$this->seedBytes,
OPENSSL_RAW_DATA,
$this->getIv($size)
);
}
/**
* Return an integer greater than or equal to $min, and less than or
* equal to $max.
*/
public function getInt(int $min, int $max):int {
if($min === $max) {
return $min;
}
if($min > $max) {
throw new MinMaxOutOfBoundsException();
}
$bitRegister = 0;
$numBytes = 0;
$bitMask = 0;
$range = $max - $min;
// Generate the $bitMask to remove from the $intValue when finding a valid int:
while($range > 0) {
if($bitRegister % PHP_INT_SIZE === 0) {
$numBytes++;
}
$bitRegister++;
// This bitwise operator is more efficient than: $range = floor($range / 2)
$range >>= 1;
// Shift the bitmask 1 bit left. The | 1 ensures the first iteration is set
// to 1 when the bitMask has no 1 bits in it.
$bitMask = $bitMask << 1 | 1;
}
$offset = $min;
// Brute-force find an integer value that falls within our requested range.
do {
$bytes = $this->getBytes($numBytes);
$intValue = 0;
for($i = 0; $i < $numBytes; $i++) {
$intValue |= ord($bytes[$i])
<< ($i * PHP_INT_SIZE);
}
$intValue &= $bitMask;
$intValue += $offset;
}
while($intValue > $max || $intValue < $min);
return $intValue;
}
/**
* Return a floating point value between 0 and $max.
*/
public function getScalar(float $max = 1.0):float {
$intScalar = $this->getInt(0, PHP_INT_MAX);
return ($max * $intScalar) / PHP_INT_MAX;
}
/** @throws SeedSizeOutOfBoundsException */
private function checkSeedSize(string $seed):void {
$strLen = strlen($seed);
if($strLen === 0 || $strLen % 16 !== 0) {
throw new SeedSizeOutOfBoundsException();
}
}
/**
* OpenSSL is used to generate random values, according to the
* initialisation vector (IV) provided. This function returns an IV
* that follows a set sequence, allowing for the generation of
* deterministic random number generation.
*/
private function getIv(int $size):string {
$iv = "";
$originalAesCounter = $this->aesCounter;
$numBytesToIncrement = ceil(($size + ($size % 16)) / 16);
$this->aesCounter += $numBytesToIncrement;
while($originalAesCounter > 0) {
$iv = pack(
"C",
$originalAesCounter & 0xFF
) . $iv;
$originalAesCounter >>= 8;
}
return str_pad(
$iv,
16,
"\0",
STR_PAD_LEFT
);
}
public function reset():void {
$this->aesCounter = 0;
}
}