Commit | Line | Data |
---|---|---|
14d4f286 S |
1 | <?php |
2 | /** | |
3 | * The Random Number Generator Class | |
4 | * | |
5 | * Use this factory to generate cryptographic quality random numbers (strings) | |
6 | * | |
7 | * PHP version 5.3 | |
8 | * | |
9 | * @category PHPCryptLib | |
10 | * @package Random | |
11 | * @author Anthony Ferrara <ircmaxell@ircmaxell.com> | |
12 | * @author Timo Hamina | |
13 | * @copyright 2011 The Authors | |
14 | * @license http://www.opensource.org/licenses/mit-license.html MIT License | |
15 | * @version Build @@version@@ | |
16 | */ | |
17 | ||
18 | namespace CryptLib\Random; | |
19 | ||
20 | use CryptLib\Core\BaseConverter; | |
21 | ||
22 | /** | |
23 | * The Random Number Generator Class | |
24 | * | |
25 | * Use this factory to generate cryptographic quality random numbers (strings) | |
26 | * | |
27 | * @category PHPCryptLib | |
28 | * @package Random | |
29 | * @author Anthony Ferrara <ircmaxell@ircmaxell.com> | |
30 | * @author Timo Hamina | |
31 | */ | |
32 | class Generator { | |
33 | ||
34 | /** | |
35 | * @var Mixer The mixing strategy to use for this generator instance | |
36 | */ | |
37 | protected $mixer = null; | |
38 | ||
39 | /** | |
40 | * @var array An array of random number sources to use for this generator | |
41 | */ | |
42 | protected $sources = array(); | |
43 | ||
44 | /** | |
45 | * Build a new instance of the generator | |
46 | * | |
47 | * @param array $sources An array of random data sources to use | |
48 | * @param Mixer $mixer The mixing strategy to use for this generator | |
49 | */ | |
50 | public function __construct(array $sources, Mixer $mixer) { | |
51 | foreach ($sources as $source) { | |
52 | $this->addSource($source); | |
53 | } | |
54 | $this->mixer = $mixer; | |
55 | } | |
56 | ||
57 | /** | |
58 | * Add a random number source to the generator | |
59 | * | |
60 | * @param Source $source The random number source to add | |
61 | * | |
62 | * @return Generator $this The current generator instance | |
63 | */ | |
64 | public function addSource(Source $source) { | |
65 | $this->sources[] = $source; | |
66 | return $this; | |
67 | } | |
68 | ||
69 | /** | |
70 | * Generate a random number (string) of the requested size | |
71 | * | |
72 | * @param int $size The size of the requested random number | |
73 | * | |
74 | * @return string The generated random number (string) | |
75 | */ | |
76 | public function generate($size) { | |
77 | $seeds = array(); | |
78 | foreach ($this->sources as $source) { | |
79 | $seeds[] = $source->generate($size); | |
80 | } | |
81 | return $this->mixer->mix($seeds); | |
82 | } | |
83 | ||
84 | /** | |
85 | * Generate a random integer with the given range | |
86 | * | |
87 | * @param int $min The lower bound of the range to generate | |
88 | * @param int $max The upper bound of the range to generate | |
89 | * | |
90 | * @return int The generated random number within the range | |
91 | */ | |
92 | public function generateInt($min = 0, $max = PHP_INT_MAX) { | |
93 | $tmp = (int) max($max, $min); | |
94 | $min = (int) min($max, $min); | |
95 | $max = $tmp; | |
96 | $range = $max - $min; | |
97 | if ($range == 0) { | |
98 | return $max; | |
99 | } elseif ($range > PHP_INT_MAX || is_float($range)) { | |
100 | /** | |
101 | * This works, because PHP will auto-convert it to a float at this point, | |
102 | * But on 64 bit systems, the float won't have enough precision to | |
103 | * actually store the difference, so we need to check if it's a float | |
104 | * and hence auto-converted... | |
105 | */ | |
106 | throw new \RangeException( | |
107 | 'The supplied range is too great to generate' | |
108 | ); | |
109 | } | |
110 | ||
111 | $bits = (int) floor(log($range, 2) + 1); | |
112 | $bytes = (int) max(ceil($bits / 8), 1); | |
113 | $mask = (int) (pow(2, $bits) - 1); | |
114 | /** | |
115 | * The mask is a better way of dropping unused bits. Basically what it does | |
116 | * is to set all the bits in the mask to 1 that we may need. Since the max | |
117 | * range is PHP_INT_MAX, we will never need negative numbers (which would | |
118 | * have the MSB set on the max int possible to generate). Therefore we | |
119 | * can just mask that away. Since pow returns a float, we need to cast | |
120 | * it back to an int so the mask will work. | |
121 | * | |
122 | * On a 64 bit platform, that means that PHP_INT_MAX is 2^63 - 1. Which | |
123 | * is also the mask if 63 bits are needed (by the log(range, 2) call). | |
124 | * So if the computed result is negative (meaning the 64th bit is set), the | |
125 | * mask will correct that. | |
126 | * | |
127 | * This turns out to be slightly better than the shift as we don't need to | |
128 | * worry about "fixing" negative values. | |
129 | */ | |
130 | do { | |
131 | $test = $this->generate($bytes); | |
132 | $result = hexdec(bin2hex($test)) & $mask; | |
133 | } while ($result > $range); | |
134 | return $result + $min; | |
135 | } | |
136 | ||
137 | /** | |
138 | * Generate a random string of specified length. | |
139 | * | |
140 | * This uses the supplied character list for generating the new result | |
141 | * string. | |
142 | * | |
143 | * @param int $length The length of the generated string | |
144 | * @param string $characters An optional list of characters to use | |
145 | * | |
146 | * @return string The generated random string | |
147 | */ | |
148 | public function generateString($length, $characters = '') { | |
149 | if ($length == 0 || strlen($characters) == 1) { | |
150 | return ''; | |
151 | } elseif (empty($characters)) { | |
152 | // Default to base 64 | |
153 | $characters = '0123456789abcdefghijklmnopqrstuvwxyz' . | |
154 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZ./'; | |
155 | } | |
156 | //determine how many bytes to generate | |
157 | $bytes = ceil($length * floor(log(strlen($characters), 2) + 1.01) / 8); | |
158 | $rand = $this->generate($bytes); | |
159 | $result = BaseConverter::convertFromBinary($rand, $characters); | |
160 | if (strlen($result) < $length) { | |
161 | $result = str_pad($result, $length, $characters[0], STR_PAD_LEFT); | |
162 | } else { | |
163 | $result = substr($result, 0, $length); | |
164 | } | |
165 | return $result; | |
166 | } | |
167 | ||
168 | /** | |
169 | * Get the Mixer used for this instance | |
170 | * | |
171 | * @return Mixer the current mixer | |
172 | */ | |
173 | public function getMixer() { | |
174 | return $this->mixer; | |
175 | } | |
176 | ||
177 | /** | |
178 | * Get the Sources used for this instance | |
179 | * | |
180 | * @return Source[] the current mixer | |
181 | */ | |
182 | public function getSources() { | |
183 | return $this->sources; | |
184 | } | |
185 | ||
186 | } |