Completed
Push — master ( e7528b...1e4ef4 )
by Vasily
09:08 queued 05:19
created

Crypt::stringIdx()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
rs 9.2
cc 4
eloc 10
nc 3
nop 2
1
<?php
2
namespace PHPDaemon\Utils;
3
use PHPDaemon\Core\Daemon;
4
use PHPDaemon\Core\Debug;
5
use PHPDaemon\FS\FileSystem;
6
7
/**
8
 * Crypt
9
 * @package PHPDaemon\Utils
10
 * @author  Vasily Zorin <[email protected]>
11
 */
12
class Crypt {
13
	use \PHPDaemon\Traits\ClassWatchdog;
14
	
15
	/**
16
	 * Generate keccak hash for string with salt
17
	 * @param  string  $str   Data
18
	 * @param  string  $salt  Salt
19
	 * @param  boolean $plain Is plain text?
20
	 * @return string
21
	 */
22
	public static function hash($str, $salt = '', $plain = false) {
23
		$size = 512;
24
		$rounds = 1;
25
		if (strncmp($salt, '$', 1) === 0) {
26
			$e = explode('$', $salt, 3);
27
			$ee = explode('=', $e[1]);
28
			if (ctype_digit($ee[0])) {
29
				$size = (int) $e[1];
30
			}
31
			if (isset($ee[1]) && ctype_digit($e[1])) {
32
				$size = (int)$e[1];
33
			}
34
		}
35
		$hash = $str . $salt;
36
		if ($rounds < 1) {
37
			$rounds = 1;
38
		}
39
		elseif ($rounds > 128) {
40
			$rounds = 128;
41
		}
42
		for ($i = 0; $i < $rounds; ++$i) {
43
			$hash = \keccak_hash($hash, $size);
44
		}
45
		if ($plain) {
46
			return $hash;
47
		}
48
		return base64_encode($hash);
49
	}
50
51
	/**
52
	 * Returns string of pseudo random characters
53
	 * @param  integer  $len   Length of desired string
0 ignored issues
show
Documentation introduced by
Should the type for parameter $len not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
54
	 * @param  string   $chars String of allowed characters
0 ignored issues
show
Documentation introduced by
Should the type for parameter $chars not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
55
	 * @param  callable $cb    Callback
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cb not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
56
	 * @param  integer  $pri   Priority of EIO operation
57
	 * @param  boolean  $hang  If true, we shall use /dev/random instead of /dev/urandom and it may cause a delay
58
	 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
59
	 */
60
	public static function randomString($len = null, $chars = null, $cb = null, $pri = 0, $hang = false) {
61
		if ($len === null) {
62
			$len = 64;
63
		}
64
		if ($chars === null) {
65
			$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.';
66
		}
67
		if ($cb === null) {
68
			Daemon::log('[CODE WARN] \\PHPDaemon\\Utils\\Crypt::randomString: non-callback way is not secure.'
69
					.' Please rewrite your code with callback function in third argument' . PHP_EOL . Debug::backtrace());
70
71
			$r = '';
72
			$m = strlen($chars) - 1;
73
			for ($i = 0; $i < $len; ++$i) {
74
				$r .= $chars[mt_rand(0, $m)];
75
			}
76
			return $r;
77
		}
78
		$charsLen = strlen($chars);
79
		$mask = static::getMinimalBitMask($charsLen - 1);
80
		$iterLimit = max($len, $len * 64);
81
		static::randomInts(2 * $len, function($ints) use ($cb, $chars, $charsLen, $len, $mask, &$iterLimit) {
82
			if ($ints === false) {
83
				$cb(false);
84
				return;
85
			}
86
			$r = '';
87
			for ($i = 0, $s = sizeof($ints); $i < $s; ++$i) {
88
				// This is wasteful, but RNGs are fast and doing otherwise adds complexity and bias
89
				$c = $ints[$i] & $mask;
90
				// Only use the random number if it is in range, otherwise try another (next iteration)
91
				if ($c < $charsLen) {
92
					$r .= static::stringIdx($chars, $c);
93
				}
94
				// Guarantee termination
95
				if (--$iterLimit <= 0) {
96
					return false;
97
				}
98
			}
99
			$d = $len - strlen($r);
100
			if ($d > 0) {
101
				static::randomString($d, $chars, function($r2) use ($r, $cb) {
102
					$cb($r . $r2);
103
				});
104
				return;
105
			}
106
			$cb($r);
107
		}, $pri, $hang);
108
	}
109
110
	/**
111
	 * Returns the character at index $idx in $str in constant time
112
	 * @param  string  $str String
113
	 * @param  integer $idx Index
114
	 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be false|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
115
	 */
116
	public static function stringIdx($str, $idx) {
117
		// FIXME: Make the const-time hack below work for all integer sizes, or
0 ignored issues
show
Coding Style introduced by
Comment refers to a FIXME task "Make the const-time hack below work for all integer sizes, or"
Loading history...
118
		// check it properly
119
		$l = strlen($str);
120
		if ($l > 65535 || $idx > $l) {
121
			return false;
122
		}
123
		$r = 0;
124
		for ($i = 0; $i < $l; ++$i) {
125
			$x = $i ^ $idx;
126
			$mask = (((($x | ($x >> 16)) & 0xFFFF) + 0xFFFF) >> 16) - 1;
127
			$r |= ord($str[$i]) & $mask;
128
		}
129
		return chr($r);
130
	}
131
132
	/**
133
	 * Returns string of pseudo random bytes
134
	 * @param  integer  $len  Length of desired string
135
	 * @param  callable $cb   Callback
136
	 * @param  integer  $pri  Priority of EIO operation
137
	 * @param  boolean  $hang If true, we shall use /dev/random instead of /dev/urandom and it may cause a delay
138
	 * @return integer
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
139
	 */
140
	public static function randomBytes($len, $cb, $pri = 0, $hang = false) {
141
		$cb = CallbackWrapper::wrap($cb);
142
		FileSystem::open('/dev/' . ($hang ? '' : 'u') . 'random', 'r', function ($file) use ($len, $cb, $pri) {
143
			if (!$file) {
144
				$cb(false);
145
				return;
146
			}
147
			$file->read($len, 0, function($file, $data) use ($cb) {
148
				$cb($data);
149
			}, $pri);
150
		}, null, $pri);
151
	}
152
153
	/**
154
	 * Returns array of pseudo random integers of machine-dependent size
155
	 * @param  integer  $numInts Number of integers
156
	 * @param  callable $cb      Callback
157
	 * @param  integer  $pri     Priority of EIO operation
158
	 * @param  boolean  $hang    If true, we shall use /dev/random instead of /dev/urandom and it may cause a delay
159
	 * @return integer
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
160
	 */
161
	public static function randomInts($numInts, $cb, $pri = 0, $hang = false) {
162
		static::randomBytes(PHP_INT_SIZE * $numInts, function($bytes) use ($cb, $numInts) {
163
			if ($bytes === false) {
164
				$cb(false);
165
				return;
166
			}
167
			$ints = [];
168
			for ($i = 0; $i < $numInts; ++$i) {
169
				$thisInt = 0;
170
				for ($j = 0; $j < PHP_INT_SIZE; ++$j) {
171
					$thisInt = ($thisInt << 8) | (ord($bytes[$i * PHP_INT_SIZE + $j]) & 0xFF);
172
				}
173
				// Absolute value in two's compliment (with min int going to zero)
174
				$thisInt = $thisInt & PHP_INT_MAX;
175
				$ints[] = $thisInt;
176
			}
177
			$cb($ints);
178
		}, $pri, $hang);
179
	}
180
181
	/**
182
	 * Returns array of pseudo random 32-bit integers
183
	 * @param  integer  $numInts Number of integers
184
	 * @param  callable $cb      Callback
185
	 * @param  integer  $pri     Priority of EIO operation
186
	 * @param  boolean  $hang    If true, we shall use /dev/random instead of /dev/urandom and it may cause a delay
187
	 * @return integer
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
188
	 */
189
	public static function randomInts32($numInts, $cb, $pri = 0, $hang = false) {
190
		static::randomBytes(4 * $numInts, function($bytes) use ($cb, $numInts) {
191
			if ($bytes === false) {
192
				$cb(false);
193
				return;
194
			}
195
			$ints = [];
196
			for ($i = 0; $i < $numInts; ++$i) {
197
				$thisInt = 0;
198
				for ($j = 0; $j < 4; ++$j) {
199
					$thisInt = ($thisInt << 8) | (ord($bytes[$i * 4 + $j]) & 0xFF);
200
				}
201
				// Absolute value in two's compliment (with min int going to zero)
202
				$thisInt = $thisInt & 0xFFFFFFFF;
203
				$ints[] = $thisInt;
204
			}
205
			$cb($ints);
206
		}, $pri, $hang);
207
	}
208
209
	/**
210
	 * Returns the smallest bit mask of all 1s such that ($toRepresent & mask) = $toRepresent
211
	 * @param  integer $toRepresent must be an integer greater than or equal to 1
212
	 * @return integer
0 ignored issues
show
Documentation introduced by
Should the return type not be false|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
213
	 */
214
	protected static function getMinimalBitMask($toRepresent) {
215
		if ($toRepresent < 1) {
216
			return false;
217
		}
218
		$mask = 0x1;
219
		while ($mask < $toRepresent) {
220
			$mask = ($mask << 1) | 1;
221
		}
222
		return $mask;
223
	}
224
225
	/**
226
	 * Compare strings
227
	 * @param  string  $a String 1
228
	 * @param  string  $b String 2
229
	 * @return boolean    Equal?
230
	 */
231
	public static function compareStrings($a, $b) {
232
		$al = strlen($a);
233
		$bl = strlen($b);
234
		if ($al !== $bl) {
235
			return false;
236
		}
237
		$d = 0;
238
		for ($i = 0; $i < $al; ++$i) {
239
			$d |= ord($a[$i]) ^ ord($b[$i]);
240
		}
241
		return $d === 0;
242
	}
243
}
244