1 <?php |
1 <?php |
2 /** |
2 /** |
3 * Random_* Compatibility Library |
3 * Random_* Compatibility Library |
4 * for using the new PHP 7 random_* API in PHP 5 projects |
4 * for using the new PHP 7 random_* API in PHP 5 projects |
5 * |
5 * |
6 * The MIT License (MIT) |
6 * The MIT License (MIT) |
7 * |
7 * |
8 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises |
8 * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises |
9 * |
9 * |
10 * Permission is hereby granted, free of charge, to any person obtaining a copy |
10 * Permission is hereby granted, free of charge, to any person obtaining a copy |
11 * of this software and associated documentation files (the "Software"), to deal |
11 * of this software and associated documentation files (the "Software"), to deal |
12 * in the Software without restriction, including without limitation the rights |
12 * in the Software without restriction, including without limitation the rights |
13 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
13 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
14 * copies of the Software, and to permit persons to whom the Software is |
14 * copies of the Software, and to permit persons to whom the Software is |
15 * furnished to do so, subject to the following conditions: |
15 * furnished to do so, subject to the following conditions: |
16 * |
16 * |
17 * The above copyright notice and this permission notice shall be included in |
17 * The above copyright notice and this permission notice shall be included in |
18 * all copies or substantial portions of the Software. |
18 * all copies or substantial portions of the Software. |
19 * |
19 * |
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
34 /** |
34 /** |
35 * Unless open_basedir is enabled, use /dev/urandom for |
35 * Unless open_basedir is enabled, use /dev/urandom for |
36 * random numbers in accordance with best practices |
36 * random numbers in accordance with best practices |
37 * |
37 * |
38 * Why we use /dev/urandom and not /dev/random |
38 * Why we use /dev/urandom and not /dev/random |
|
39 * @ref https://www.2uo.de/myths-about-urandom |
39 * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers |
40 * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers |
40 * |
41 * |
41 * @param int $bytes |
42 * @param int $bytes |
42 * |
43 * |
43 * @throws Exception |
44 * @throws Exception |
44 * |
45 * |
45 * @return string |
46 * @return string |
46 */ |
47 */ |
47 function random_bytes($bytes) |
48 function random_bytes($bytes) |
48 { |
49 { |
|
50 /** @var resource $fp */ |
49 static $fp = null; |
51 static $fp = null; |
|
52 |
50 /** |
53 /** |
51 * This block should only be run once |
54 * This block should only be run once |
52 */ |
55 */ |
53 if (empty($fp)) { |
56 if (empty($fp)) { |
54 /** |
57 /** |
55 * We use /dev/urandom if it is a char device. |
58 * We don't want to ever read C:\dev\random, only /dev/urandom on |
56 * We never fall back to /dev/random |
59 * Unix-like operating systems. While we guard against this |
|
60 * condition in random.php, it doesn't hurt to be defensive in depth |
|
61 * here. |
|
62 * |
|
63 * To that end, we only try to open /dev/urandom if we're on a Unix- |
|
64 * like operating system (which means the directory separator is set |
|
65 * to "/" not "\". |
57 */ |
66 */ |
58 $fp = fopen('/dev/urandom', 'rb'); |
67 if (DIRECTORY_SEPARATOR === '/') { |
59 if (!empty($fp)) { |
68 if (!is_readable('/dev/urandom')) { |
60 $st = fstat($fp); |
69 throw new Exception( |
61 if (($st['mode'] & 0170000) !== 020000) { |
70 'Environment misconfiguration: ' . |
62 fclose($fp); |
71 '/dev/urandom cannot be read.' |
63 $fp = false; |
72 ); |
|
73 } |
|
74 /** |
|
75 * We use /dev/urandom if it is a char device. |
|
76 * We never fall back to /dev/random |
|
77 */ |
|
78 /** @var resource|bool $fp */ |
|
79 $fp = fopen('/dev/urandom', 'rb'); |
|
80 if (is_resource($fp)) { |
|
81 /** @var array<string, int> $st */ |
|
82 $st = fstat($fp); |
|
83 if (($st['mode'] & 0170000) !== 020000) { |
|
84 fclose($fp); |
|
85 $fp = false; |
|
86 } |
64 } |
87 } |
65 } |
88 } |
66 |
89 |
67 if (!empty($fp)) { |
90 if (is_resource($fp)) { |
68 /** |
91 /** |
69 * stream_set_read_buffer() does not exist in HHVM |
92 * stream_set_read_buffer() does not exist in HHVM |
70 * |
93 * |
71 * If we don't set the stream's read buffer to 0, PHP will |
94 * If we don't set the stream's read buffer to 0, PHP will |
72 * internally buffer 8192 bytes, which can waste entropy |
95 * internally buffer 8192 bytes, which can waste entropy |
121 /** |
145 /** |
122 * @var string|bool |
146 * @var string|bool |
123 */ |
147 */ |
124 $read = fread($fp, $remaining); |
148 $read = fread($fp, $remaining); |
125 if (!is_string($read)) { |
149 if (!is_string($read)) { |
126 if ($read === false) { |
150 /** |
127 /** |
151 * We cannot safely read from the file. Exit the |
128 * We cannot safely read from the file. Exit the |
152 * do-while loop and trigger the exception condition |
129 * do-while loop and trigger the exception condition |
153 * |
130 * |
154 * @var string|bool |
131 * @var string|bool |
155 */ |
132 */ |
156 $buf = false; |
133 $buf = false; |
157 break; |
134 break; |
|
135 } |
|
136 } |
158 } |
137 /** |
159 /** |
138 * Decrease the number of bytes returned from remaining |
160 * Decrease the number of bytes returned from remaining |
139 */ |
161 */ |
140 $remaining -= RandomCompat_strlen($read); |
162 $remaining -= RandomCompat_strlen($read); |
141 /** |
163 /** |
142 * @var string|bool |
164 * @var string $buf |
143 */ |
165 */ |
144 $buf = $buf . $read; |
166 $buf .= $read; |
145 } while ($remaining > 0); |
167 } while ($remaining > 0); |
146 |
168 |
147 /** |
169 /** |
148 * Is our result valid? |
170 * Is our result valid? |
|
171 * @var string|bool $buf |
149 */ |
172 */ |
150 if (is_string($buf)) { |
173 if (is_string($buf)) { |
151 if (RandomCompat_strlen($buf) === $bytes) { |
174 if (RandomCompat_strlen($buf) === $bytes) { |
152 /** |
175 /** |
153 * Return our random entropy buffer here: |
176 * Return our random entropy buffer here: |