Completed
Push — master ( 366423...9bc576 )
by Oliver
09:19 queued 07:57
created
src/Algorithm/Shamir.php 3 patches
Doc Comments   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -224,7 +224,7 @@  discard block
 block discarded – undo
224 224
 	 * Calculate modulo of any given number using prime
225 225
 	 *
226 226
 	 * @param   integer     Number
227
-	 * @return  integer     Module of number
227
+	 * @return  string     Module of number
228 228
 	 */
229 229
 	protected function modulo( $number ) {
230 230
 
@@ -258,7 +258,7 @@  discard block
 block discarded – undo
258 258
 	 * Calculates the inverse modulo
259 259
 	 *
260 260
 	 * @param int $number
261
-	 * @return int
261
+	 * @return string
262 262
 	 */
263 263
 	protected function inverseModulo( $number ) {
264 264
 
@@ -275,7 +275,7 @@  discard block
 block discarded – undo
275 275
 	 *
276 276
 	 * @param   array $keyX
277 277
 	 * @param   int   $threshold
278
-	 * @return  array
278
+	 * @return  string
279 279
 	 * @throws  \RuntimeException
280 280
 	 */
281 281
 	protected function reverseCoefficients( array $keyX, $threshold ) {
Please login to merge, or discard this patch.
Spacing   +135 added lines, -135 removed lines patch added patch discarded remove patch
@@ -80,7 +80,7 @@  discard block
 block discarded – undo
80 80
 	 */
81 81
 	public function getRandomGenerator() {
82 82
 
83
-		if( !$this->randomGenerator ) {
83
+		if (!$this->randomGenerator) {
84 84
 			$this->randomGenerator = new PhpGenerator();
85 85
 		}
86 86
 
@@ -92,7 +92,7 @@  discard block
 block discarded – undo
92 92
 	 * @inheritdoc
93 93
 	 * @return  Shamir
94 94
 	 */
95
-	public function setRandomGenerator( Generator $generator ) {
95
+	public function setRandomGenerator(Generator $generator) {
96 96
 
97 97
 		$this->randomGenerator = $generator;
98 98
 
@@ -123,13 +123,13 @@  discard block
 block discarded – undo
123 123
 	 * @return  Shamir
124 124
 	 * @throws  \OutOfRangeException
125 125
 	 */
126
-	public function setChunkSize( $chunkSize ) {
126
+	public function setChunkSize($chunkSize) {
127 127
 
128 128
 		$chunkSize = (int)$chunkSize;
129
-		$primeNumber = array( 1 => 257, 65537, 16777259, 4294967311, 1099511627791, 281474976710677, 72057594037928017 );
129
+		$primeNumber = array(1 => 257, 65537, 16777259, 4294967311, 1099511627791, 281474976710677, 72057594037928017);
130 130
 
131
-		if( $chunkSize > 7 ) {
132
-			throw new \OutOfRangeException( 'Chunk sizes with that many bytes are not implemented yet.' );
131
+		if ($chunkSize > 7) {
132
+			throw new \OutOfRangeException('Chunk sizes with that many bytes are not implemented yet.');
133 133
 		}
134 134
 
135 135
 		$this->chunkSize = $chunkSize;
@@ -154,7 +154,7 @@  discard block
 block discarded – undo
154 154
 	 * @return  Shamir
155 155
 	 * @throws  \OutOfRangeException
156 156
 	 */
157
-	protected function setMaxShares( $max ) {
157
+	protected function setMaxShares($max) {
158 158
 
159 159
 		// the prime number has to be larger, than the maximum number
160 160
 		// representable by the number of bytes. so we always need one
@@ -165,25 +165,25 @@  discard block
 block discarded – undo
165 165
 		// max possible number of shares is the maximum number of bytes
166 166
 		// possible to be represented with max integer, but we always need
167 167
 		// to save one byte for encryption.
168
-		$maxPossible = 1 << ( PHP_INT_SIZE - 1 ) * 8;
168
+		$maxPossible = 1 << (PHP_INT_SIZE - 1) * 8;
169 169
 
170
-		if( $max > $maxPossible ) {
170
+		if ($max > $maxPossible) {
171 171
 			// we are unable to provide more bytes-1 as supported by OS
172 172
 			// because the prime number need to be higher than that, but
173 173
 			// this would exceed OS int range.
174 174
 			throw new \OutOfRangeException(
175
-				'Number of required keys has to be below ' . number_format( $maxPossible ) . '.'
175
+				'Number of required keys has to be below '.number_format($maxPossible).'.'
176 176
 			);
177 177
 		}
178 178
 
179 179
 		// calculate how many bytes we need to represent number of shares.
180 180
 		// e.g. everything less than 256 needs only a single byte.
181
-		$chunkSize = (int)ceil( log( $max, 2 ) / 8 );
181
+		$chunkSize = (int)ceil(log($max, 2) / 8);
182 182
 		// if chunk size has been set already, we will only increase it, if necessary
183
-		$chunkSize = max( $chunkSize, $this->chunkSize );
183
+		$chunkSize = max($chunkSize, $this->chunkSize);
184 184
 
185
-		if( $chunkSize > $this->chunkSize ) {
186
-			$this->setChunkSize( $chunkSize );
185
+		if ($chunkSize > $this->chunkSize) {
186
+			$this->setChunkSize($chunkSize);
187 187
 		}
188 188
 
189 189
 		$this->maxShares = $max;
@@ -198,11 +198,11 @@  discard block
 block discarded – undo
198 198
 	 * @param   integer     Number
199 199
 	 * @return  integer     Module of number
200 200
 	 */
201
-	protected function modulo( $number ) {
201
+	protected function modulo($number) {
202 202
 
203
-		$modulo = bcmod( $number, $this->prime );
203
+		$modulo = bcmod($number, $this->prime);
204 204
 
205
-		return ( $modulo < 0 ) ? bcadd( $modulo, $this->prime ) : $modulo;
205
+		return ($modulo < 0) ? bcadd($modulo, $this->prime) : $modulo;
206 206
 	}
207 207
 
208 208
 
@@ -213,15 +213,15 @@  discard block
 block discarded – undo
213 213
 	 * @param int $b
214 214
 	 * @return array
215 215
 	 */
216
-	protected function gcdD( $a, $b ) {
216
+	protected function gcdD($a, $b) {
217 217
 
218
-		if( $b == 0 ) {
219
-			return array( $a, 1, 0 );
218
+		if ($b == 0) {
219
+			return array($a, 1, 0);
220 220
 		} else {
221
-			$div = floor( bcdiv( $a, $b ) );
222
-			$mod = bcmod( $a, $b );
223
-			$decomp = $this->gcdD( $b, $mod );
224
-			return array( $decomp[0], $decomp[2], $decomp[1] - $decomp[2] * $div );
221
+			$div = floor(bcdiv($a, $b));
222
+			$mod = bcmod($a, $b);
223
+			$decomp = $this->gcdD($b, $mod);
224
+			return array($decomp[0], $decomp[2], $decomp[1] - $decomp[2] * $div);
225 225
 		}
226 226
 	}
227 227
 
@@ -232,13 +232,13 @@  discard block
 block discarded – undo
232 232
 	 * @param int $number
233 233
 	 * @return int
234 234
 	 */
235
-	protected function inverseModulo( $number ) {
235
+	protected function inverseModulo($number) {
236 236
 
237
-		$number = bcmod( $number, $this->prime );
238
-		$r = $this->gcdD( $this->prime, abs( $number ) );
239
-		$r = ( $number < 0 ) ? -$r[2] : $r[2];
237
+		$number = bcmod($number, $this->prime);
238
+		$r = $this->gcdD($this->prime, abs($number));
239
+		$r = ($number < 0) ? -$r[2] : $r[2];
240 240
 
241
-		return bcmod( bcadd( $this->prime, $r ), $this->prime );
241
+		return bcmod(bcadd($this->prime, $r), $this->prime);
242 242
 	}
243 243
 
244 244
 
@@ -250,23 +250,23 @@  discard block
 block discarded – undo
250 250
 	 * @return  array
251 251
 	 * @throws  \RuntimeException
252 252
 	 */
253
-	protected function reverseCoefficients( array $keyX, $threshold ) {
253
+	protected function reverseCoefficients(array $keyX, $threshold) {
254 254
 
255 255
 		$coefficients = array();
256 256
 
257
-		for( $i = 0; $i < $threshold; $i++ ) {
257
+		for ($i = 0; $i < $threshold; $i++) {
258 258
 			$temp = 1;
259
-			for( $j = 0; $j < $threshold; $j++ ) {
260
-				if( $i != $j ) {
259
+			for ($j = 0; $j < $threshold; $j++) {
260
+				if ($i != $j) {
261 261
 					$temp = $this->modulo(
262
-								 bcmul( bcmul( -$temp, $keyX[$j] ), $this->inverseModulo( $keyX[$i] - $keyX[$j] ) )
262
+								 bcmul(bcmul( -$temp, $keyX[$j] ), $this->inverseModulo($keyX[$i] - $keyX[$j]))
263 263
 					);
264 264
 				}
265 265
 			}
266 266
 
267
-			if( $temp == 0 ) {
267
+			if ($temp == 0) {
268 268
 				/* Repeated share */
269
-				throw new \RuntimeException( 'Repeated share detected - cannot compute reverse-coefficients' );
269
+				throw new \RuntimeException('Repeated share detected - cannot compute reverse-coefficients');
270 270
 			}
271 271
 
272 272
 			$coefficients[] = $temp;
@@ -282,15 +282,15 @@  discard block
 block discarded – undo
282 282
 	 * @param   integer $threshold Number of coefficients needed
283 283
 	 * @return  array
284 284
 	 */
285
-	protected function generateCoefficients( $threshold ) {
285
+	protected function generateCoefficients($threshold) {
286 286
 
287 287
 		$coefficients = array();
288
-		for( $i = 0; $i < $threshold - 1; $i++ ) {
288
+		for ($i = 0; $i < $threshold - 1; $i++) {
289 289
 			do {
290 290
 				// the random number has to be positive integer != 0
291
-				$random = abs( $this->getRandomGenerator()->getRandomInt() );
292
-			} while($random < 1);
293
-			$coefficients[] = $this->modulo( $random );
291
+				$random = abs($this->getRandomGenerator()->getRandomInt());
292
+			} while ($random < 1);
293
+			$coefficients[] = $this->modulo($random);
294 294
 		}
295 295
 
296 296
 		return $coefficients;
@@ -310,11 +310,11 @@  discard block
 block discarded – undo
310 310
 	 * @param   array   $coefficients Polynomial coefficients
311 311
 	 * @return  integer                     Y coordinate
312 312
 	 */
313
-	protected function hornerMethod( $xCoordinate, array $coefficients ) {
313
+	protected function hornerMethod($xCoordinate, array $coefficients) {
314 314
 
315 315
 		$yCoordinate = 0;
316
-		foreach( $coefficients as $coefficient ) {
317
-			$yCoordinate = $this->modulo( $xCoordinate * $yCoordinate + $coefficient );
316
+		foreach ($coefficients as $coefficient) {
317
+			$yCoordinate = $this->modulo($xCoordinate * $yCoordinate + $coefficient);
318 318
 		}
319 319
 
320 320
 		return $yCoordinate;
@@ -329,40 +329,40 @@  discard block
 block discarded – undo
329 329
 	 * @param   string $toBaseInput
330 330
 	 * @return  string
331 331
 	 */
332
-	protected static function convBase( $numberInput, $fromBaseInput, $toBaseInput ) {
332
+	protected static function convBase($numberInput, $fromBaseInput, $toBaseInput) {
333 333
 
334
-		if( $fromBaseInput == $toBaseInput ) {
334
+		if ($fromBaseInput == $toBaseInput) {
335 335
 			return $numberInput;
336 336
 		}
337
-		$fromBase = str_split( $fromBaseInput, 1 );
338
-		$toBase = str_split( $toBaseInput, 1 );
339
-		$number = str_split( $numberInput, 1 );
340
-		$fromLen = strlen( $fromBaseInput );
341
-		$toLen = strlen( $toBaseInput );
342
-		$numberLen = strlen( $numberInput );
337
+		$fromBase = str_split($fromBaseInput, 1);
338
+		$toBase = str_split($toBaseInput, 1);
339
+		$number = str_split($numberInput, 1);
340
+		$fromLen = strlen($fromBaseInput);
341
+		$toLen = strlen($toBaseInput);
342
+		$numberLen = strlen($numberInput);
343 343
 		$retVal = '';
344
-		if( $toBaseInput == '0123456789' ) {
344
+		if ($toBaseInput == '0123456789') {
345 345
 			$retVal = 0;
346
-			for( $i = 1; $i <= $numberLen; $i++ ) {
346
+			for ($i = 1; $i <= $numberLen; $i++) {
347 347
 				$retVal = bcadd(
348 348
 					$retVal,
349
-					bcmul( array_search( $number[$i - 1], $fromBase ), bcpow( $fromLen, $numberLen - $i ) )
349
+					bcmul(array_search($number[$i - 1], $fromBase), bcpow($fromLen, $numberLen - $i))
350 350
 				);
351 351
 			}
352 352
 
353 353
 			return $retVal;
354 354
 		}
355
-		if( $fromBaseInput != '0123456789' ) {
356
-			$base10 = self::convBase( $numberInput, $fromBaseInput, '0123456789' );
355
+		if ($fromBaseInput != '0123456789') {
356
+			$base10 = self::convBase($numberInput, $fromBaseInput, '0123456789');
357 357
 		} else {
358 358
 			$base10 = $numberInput;
359 359
 		}
360
-		if( $base10 < strlen( $toBaseInput ) ) {
360
+		if ($base10 < strlen($toBaseInput)) {
361 361
 			return $toBase[$base10];
362 362
 		}
363
-		while($base10 != '0') {
364
-			$retVal = $toBase[bcmod( $base10, $toLen )] . $retVal;
365
-			$base10 = bcdiv( $base10, $toLen, 0 );
363
+		while ($base10 != '0') {
364
+			$retVal = $toBase[bcmod($base10, $toLen)].$retVal;
365
+			$base10 = bcdiv($base10, $toLen, 0);
366 366
 		}
367 367
 
368 368
 		return $retVal;
@@ -377,20 +377,20 @@  discard block
 block discarded – undo
377 377
 	 * @param   string $string Binary string
378 378
 	 * @return  array           Array with decimal converted numbers
379 379
 	 */
380
-	protected function unpack( $string ) {
380
+	protected function unpack($string) {
381 381
 
382 382
 		$chunk = 0;
383 383
 		$int = null;
384 384
 		$return = array();
385
-		foreach( unpack( 'C*', $string ) as $byte ) {
386
-			$int = bcadd( $int, bcmul( $byte, bcpow( 2, $chunk * 8 ) ) );
387
-			if( ++$chunk == $this->chunkSize ) {
385
+		foreach (unpack('C*', $string) as $byte) {
386
+			$int = bcadd($int, bcmul($byte, bcpow(2, $chunk * 8)));
387
+			if ( ++$chunk == $this->chunkSize ) {
388 388
 				$return[] = $int;
389 389
 				$chunk = 0;
390 390
 				$int = null;
391 391
 			}
392 392
 		}
393
-		if( $chunk > 0 ) {
393
+		if ($chunk > 0) {
394 394
 			$return[] = $int;
395 395
 		}
396 396
 
@@ -408,11 +408,11 @@  discard block
 block discarded – undo
408 408
 	 * @param   integer $bytes Bytes used to represent a string
409 409
 	 * @return  integer Number of chars
410 410
 	 */
411
-	protected function maxKeyLength( $bytes ) {
411
+	protected function maxKeyLength($bytes) {
412 412
 
413
-		$maxInt = bcpow( 2, $bytes * 8 );
414
-		$converted = self::convBase( $maxInt, self::DECIMAL, self::CHARS );
415
-		return strlen( $converted );
413
+		$maxInt = bcpow(2, $bytes * 8);
414
+		$converted = self::convBase($maxInt, self::DECIMAL, self::CHARS);
415
+		return strlen($converted);
416 416
 	}
417 417
 
418 418
 
@@ -424,19 +424,19 @@  discard block
 block discarded – undo
424 424
 	 * @param   integer $threshold Minimum number of shares required for decryption
425 425
 	 * @return  array
426 426
 	 */
427
-	protected function divideSecret( $secret, $shares, $threshold ) {
427
+	protected function divideSecret($secret, $shares, $threshold) {
428 428
 
429 429
 		// divide secret into chunks, which we encrypt one by one
430 430
 		$result = array();
431 431
 
432
-		foreach( $this->unpack( $secret ) as $bytes ) {
433
-			$coeffs = $this->generateCoefficients( $threshold );
432
+		foreach ($this->unpack($secret) as $bytes) {
433
+			$coeffs = $this->generateCoefficients($threshold);
434 434
 			$coeffs[] = $bytes;
435 435
 
436 436
 			// go through x coordinates and calculate y value
437
-			for( $x = 1; $x <= $shares; $x++ ) {
437
+			for ($x = 1; $x <= $shares; $x++) {
438 438
 				// use horner method to calculate y value
439
-				$result[] = $this->hornerMethod( $x, $coeffs );
439
+				$result[] = $this->hornerMethod($x, $coeffs);
440 440
 			}
441 441
 		}
442 442
 
@@ -447,61 +447,61 @@  discard block
 block discarded – undo
447 447
 	/**
448 448
 	 * @inheritdoc
449 449
 	 */
450
-	public function share( $secret, $shares, $threshold = 2 ) {
450
+	public function share($secret, $shares, $threshold = 2) {
451 451
 
452
-		$this->setMaxShares( $shares );
452
+		$this->setMaxShares($shares);
453 453
 
454 454
 		// check if number of shares is less than our prime, otherwise we have a security problem
455
-		if( $shares >= $this->prime || $shares < 1 ) {
456
-			throw new \OutOfRangeException( 'Number of shares has to be between 1 and ' . $this->prime . '.' );
455
+		if ($shares >= $this->prime || $shares < 1) {
456
+			throw new \OutOfRangeException('Number of shares has to be between 1 and '.$this->prime.'.');
457 457
 		}
458 458
 
459
-		if( $shares < $threshold ) {
460
-			throw new \OutOfRangeException( 'Threshold has to be between 1 and ' . $shares . '.' );
459
+		if ($shares < $threshold) {
460
+			throw new \OutOfRangeException('Threshold has to be between 1 and '.$shares.'.');
461 461
 		}
462 462
 
463
-		if( strpos( self::CHARS, self::PAD_CHAR ) !== false ) {
464
-			throw new \OutOfRangeException( 'Padding character must not be part of possible encryption chars.' );
463
+		if (strpos(self::CHARS, self::PAD_CHAR) !== false) {
464
+			throw new \OutOfRangeException('Padding character must not be part of possible encryption chars.');
465 465
 		}
466 466
 
467 467
 		// divide secret into chunks, which we encrypt one by one
468
-		$result = $this->divideSecret( $secret, $shares, $threshold );
468
+		$result = $this->divideSecret($secret, $shares, $threshold);
469 469
 
470 470
 		// encode number of bytes and threshold
471 471
 
472 472
 		// calculate the maximum length of key sequence number and threshold
473
-		$maxBaseLength = $this->maxKeyLength( $this->chunkSize );
473
+		$maxBaseLength = $this->maxKeyLength($this->chunkSize);
474 474
 		// in order to do a correct padding to the converted base, we need to use the first char of the base
475
-		$paddingChar = substr( self::CHARS, 0, 1 );
475
+		$paddingChar = substr(self::CHARS, 0, 1);
476 476
 		// define prefix number using the number of bytes (hex), and a left padded string used for threshold (base converted)
477
-		$fixPrefixFormat = '%x%' . $paddingChar . $maxBaseLength . 's';
477
+		$fixPrefixFormat = '%x%'.$paddingChar.$maxBaseLength.'s';
478 478
 		// prefix is going to be the same for all keys
479
-		$prefix = sprintf( $fixPrefixFormat, $this->chunkSize, self::convBase( $threshold, self::DECIMAL, self::CHARS ) );
479
+		$prefix = sprintf($fixPrefixFormat, $this->chunkSize, self::convBase($threshold, self::DECIMAL, self::CHARS));
480 480
 
481 481
 		// convert y coordinates into hexadecimals shares
482 482
 		$passwords = array();
483
-		$secretLen = strlen( $secret );
483
+		$secretLen = strlen($secret);
484 484
 		// calculate how many bytes, we need to cut off during recovery
485
-		if( $secretLen % $this->chunkSize > 0 ) {
486
-			$tail = str_repeat( self::PAD_CHAR, $this->chunkSize - $secretLen % $this->chunkSize );
485
+		if ($secretLen % $this->chunkSize > 0) {
486
+			$tail = str_repeat(self::PAD_CHAR, $this->chunkSize - $secretLen % $this->chunkSize);
487 487
 		} else {
488 488
 			$tail = '';
489 489
 		}
490 490
 
491
-		$chunks = ceil( $secretLen / $this->chunkSize );
492
-		for( $i = 0; $i < $shares; ++$i ) {
493
-			$sequence = self::convBase( ( $i + 1 ), self::DECIMAL, self::CHARS );
494
-			$key = sprintf( $prefix . '%' . $paddingChar . $maxBaseLength . 's', $sequence );
491
+		$chunks = ceil($secretLen / $this->chunkSize);
492
+		for ($i = 0; $i < $shares; ++$i) {
493
+			$sequence = self::convBase(($i + 1), self::DECIMAL, self::CHARS);
494
+			$key = sprintf($prefix.'%'.$paddingChar.$maxBaseLength.'s', $sequence);
495 495
 
496
-			for( $j = 0; $j < $chunks; ++$j ) {
496
+			for ($j = 0; $j < $chunks; ++$j) {
497 497
 				$key .= str_pad(
498
-					self::convBase( $result[$j * $shares + $i], self::DECIMAL, self::CHARS ),
498
+					self::convBase($result[$j * $shares + $i], self::DECIMAL, self::CHARS),
499 499
 					$maxBaseLength,
500 500
 					$paddingChar,
501 501
 					STR_PAD_LEFT
502 502
 				);
503 503
 			}
504
-			$passwords[] = $key . $tail;
504
+			$passwords[] = $key.$tail;
505 505
 		}
506 506
 
507 507
 		return $passwords;
@@ -518,23 +518,23 @@  discard block
 block discarded – undo
518 518
 	 * @param integer $threshold Minimum number of shares required for decryption
519 519
 	 * @return string
520 520
 	 */
521
-	protected function joinSecret( $keyX, $keyY, $bytes, $keyLen, $threshold ) {
521
+	protected function joinSecret($keyX, $keyY, $bytes, $keyLen, $threshold) {
522 522
 
523
-		$coefficients = $this->reverseCoefficients( $keyX, $threshold );
523
+		$coefficients = $this->reverseCoefficients($keyX, $threshold);
524 524
 
525 525
 		$secret = '';
526
-		for( $i = 0; $i < $keyLen; $i++ ) {
526
+		for ($i = 0; $i < $keyLen; $i++) {
527 527
 			$temp = 0;
528
-			for( $j = 0; $j < $threshold; $j++ ) {
528
+			for ($j = 0; $j < $threshold; $j++) {
529 529
 				$temp = $this->modulo(
530
-							 bcadd( $temp, bcmul( $keyY[$j * $keyLen + $i], $coefficients[$j] ) )
530
+							 bcadd($temp, bcmul($keyY[$j * $keyLen + $i], $coefficients[$j]))
531 531
 				);
532 532
 			}
533 533
 			// convert each byte back into char
534
-			for( $byte = 1; $byte <= $bytes; ++$byte ) {
535
-				$char = bcmod( $temp, 256 );
536
-				$secret .= chr( $char );
537
-				$temp = bcdiv( bcsub( $temp, $char ), 256 );
534
+			for ($byte = 1; $byte <= $bytes; ++$byte) {
535
+				$char = bcmod($temp, 256);
536
+				$secret .= chr($char);
537
+				$temp = bcdiv(bcsub($temp, $char), 256);
538 538
 			}
539 539
 		}
540 540
 
@@ -545,10 +545,10 @@  discard block
 block discarded – undo
545 545
 	/**
546 546
 	 * @inheritdoc
547 547
 	 */
548
-	public function recover( array $keys ) {
548
+	public function recover(array $keys) {
549 549
 
550
-		if( !count( $keys ) ) {
551
-			throw new \RuntimeException( 'No keys given.' );
550
+		if (!count($keys)) {
551
+			throw new \RuntimeException('No keys given.');
552 552
 		}
553 553
 
554 554
 		$keyX = array();
@@ -557,53 +557,53 @@  discard block
 block discarded – undo
557 557
 		$threshold = null;
558 558
 
559 559
 		// analyse first key
560
-		$key = reset( $keys );
560
+		$key = reset($keys);
561 561
 		// first we need to find out the bytes to predict threshold and sequence length
562
-		$bytes = hexdec( substr( $key, 0, 1 ) );
563
-		$this->setChunkSize( $bytes );
562
+		$bytes = hexdec(substr($key, 0, 1));
563
+		$this->setChunkSize($bytes);
564 564
 		// calculate the maximum length of key sequence number and threshold
565
-		$maxBaseLength = $this->maxKeyLength( $bytes );
565
+		$maxBaseLength = $this->maxKeyLength($bytes);
566 566
 		// define key format: bytes (hex), threshold, sequence, and key (except of bytes, all is base converted)
567
-		$keyFormat = '%1x%' . $maxBaseLength . 's%' . $maxBaseLength . 's%s';
567
+		$keyFormat = '%1x%'.$maxBaseLength.'s%'.$maxBaseLength.'s%s';
568 568
 
569
-		foreach( $keys as $key ) {
569
+		foreach ($keys as $key) {
570 570
 			// remove trailing padding characters
571
-			$key = str_replace( self::PAD_CHAR, '', $key );
571
+			$key = str_replace(self::PAD_CHAR, '', $key);
572 572
 
573 573
 			// extract "public" information of key: bytes, threshold, sequence
574 574
 
575
-			list( $keyBytes, $keyThreshold, $keySequence, $key ) = sscanf( $key, $keyFormat );
576
-			$keyThreshold = (int)self::convBase( $keyThreshold, self::CHARS, self::DECIMAL );
577
-			$keySequence = (int)self::convBase( $keySequence, self::CHARS, self::DECIMAL );
575
+			list($keyBytes, $keyThreshold, $keySequence, $key) = sscanf($key, $keyFormat);
576
+			$keyThreshold = (int)self::convBase($keyThreshold, self::CHARS, self::DECIMAL);
577
+			$keySequence = (int)self::convBase($keySequence, self::CHARS, self::DECIMAL);
578 578
 
579
-			if( $threshold === null ) {
579
+			if ($threshold === null) {
580 580
 				$threshold = $keyThreshold;
581 581
 
582
-				if( $threshold > count( $keys ) ) {
583
-					throw new \RuntimeException( 'Not enough keys to disclose secret.' );
582
+				if ($threshold > count($keys)) {
583
+					throw new \RuntimeException('Not enough keys to disclose secret.');
584 584
 				}
585
-			} elseif( $threshold != $keyThreshold || $bytes != hexdec( $keyBytes ) ) {
586
-				throw new \RuntimeException( 'Given keys are incompatible.' );
585
+			} elseif ($threshold != $keyThreshold || $bytes != hexdec($keyBytes)) {
586
+				throw new \RuntimeException('Given keys are incompatible.');
587 587
 			}
588 588
 
589 589
 			$keyX[] = $keySequence;
590
-			if( $keyLen === null ) {
591
-				$keyLen = strlen( $key );
592
-			} elseif( $keyLen != strlen( $key ) ) {
593
-				throw new \RuntimeException( 'Given keys vary in key length.' );
590
+			if ($keyLen === null) {
591
+				$keyLen = strlen($key);
592
+			} elseif ($keyLen != strlen($key)) {
593
+				throw new \RuntimeException('Given keys vary in key length.');
594 594
 			}
595
-			for( $i = 0; $i < $keyLen; $i += $maxBaseLength ) {
596
-				$keyY[] = self::convBase( substr( $key, $i, $maxBaseLength ), self::CHARS, self::DECIMAL );
595
+			for ($i = 0; $i < $keyLen; $i += $maxBaseLength) {
596
+				$keyY[] = self::convBase(substr($key, $i, $maxBaseLength), self::CHARS, self::DECIMAL);
597 597
 			}
598 598
 		}
599 599
 
600 600
 		$keyLen /= $maxBaseLength;
601
-		$secret = $this->joinSecret( $keyX, $keyY, $bytes, $keyLen, $threshold );
601
+		$secret = $this->joinSecret($keyX, $keyY, $bytes, $keyLen, $threshold);
602 602
 
603 603
 		// remove padding from secret (NULL bytes);
604
-		$padCount = substr_count( reset( $keys ), self::PAD_CHAR );
605
-		if( $padCount ) {
606
-			$secret = substr( $secret, 0, -1 * $padCount );
604
+		$padCount = substr_count(reset($keys), self::PAD_CHAR);
605
+		if ($padCount) {
606
+			$secret = substr($secret, 0, -1 * $padCount);
607 607
 		}
608 608
 
609 609
 		return $secret;
Please login to merge, or discard this patch.
Indentation   +590 added lines, -590 removed lines patch added patch discarded remove patch
@@ -17,596 +17,596 @@
 block discarded – undo
17 17
  */
18 18
 class Shamir implements Algorithm, RandomGeneratorAware {
19 19
 
20
-	/**
20
+    /**
21 21
      * Calculation base (decimal)
22
-	 *
23
-	 * Changing this will invalid all previously created keys.
24
-	 *
25
-	 * @const   string
26
-	 */
27
-	const DECIMAL = '0123456789';
28
-
29
-	/**
30
-	 * Target base characters to be used in passwords (shares)
31
-	 *
32
-	 * The more characters are used, the shorter the shares might get.
33
-	 * Changing this will invalid all previously created keys.
34
-	 *
35
-	 * @const   string
36
-	 */
37
-	const CHARS = '0123456789abcdefghijklmnopqrstuvwxyz.,:;-+*#%';
38
-
39
-	/**
40
-	 * Character to fill up the secret keys
41
-	 *
42
-	 * @const   string
43
-	 */
44
-	const PAD_CHAR = '=';
45
-
46
-	/**
47
-	 * Prime number has to be greater than the maximum number of shares possible
48
-	 *
49
-	 * @var float
50
-	 */
51
-	protected $prime = 257;
52
-
53
-	/**
54
-	 * Chunk size in bytes
55
-	 *
56
-	 * The secret will be divided equally. This value defines the chunk size and
57
-	 * how many bytes will get encoded at once.
58
-	 *
59
-	 * @var int
60
-	 */
61
-	protected $chunkSize = 1;
62
-
63
-	/**
64
-	 * The random generator
65
-	 *
66
-	 * @var Generator
67
-	 */
68
-	protected $randomGenerator;
69
-
70
-	/**
71
-	 * Maximum number of shares required
72
-	 *
73
-	 * @var float
74
-	 */
75
-	protected $maxShares = 3;
76
-
77
-
78
-	/**
79
-	 * @inheritdoc
80
-	 */
81
-	public function getRandomGenerator() {
82
-
83
-		if( !$this->randomGenerator ) {
84
-			$this->randomGenerator = new PhpGenerator();
85
-		}
86
-
87
-		return $this->randomGenerator;
88
-	}
89
-
90
-
91
-	/**
92
-	 * @inheritdoc
93
-	 * @return  Shamir
94
-	 */
95
-	public function setRandomGenerator( Generator $generator ) {
96
-
97
-		$this->randomGenerator = $generator;
98
-
99
-		return $this;
100
-	}
101
-
102
-
103
-	/**
104
-	 * Returns chunk size in bytes
105
-	 *
106
-	 * @return int
107
-	 */
108
-	public function getChunkSize() {
109
-
110
-		return $this->chunkSize;
111
-	}
112
-
113
-
114
-	/**
115
-	 * Sets chunk size in bytes
116
-	 *
117
-	 * If maximum shares have been set already, the chunk
118
-	 * size might have been set with it. It is not possible
119
-	 * to set a smaller size than required by shares.
120
-	 *
121
-	 * @see
122
-	 * @param   int $chunkSize Size in number of bytes
123
-	 * @return  Shamir
124
-	 * @throws  \OutOfRangeException
125
-	 */
126
-	public function setChunkSize( $chunkSize ) {
127
-
128
-		$chunkSize = (int)$chunkSize;
129
-		$primeNumber = array( 1 => 257, 65537, 16777259, 4294967311, 1099511627791, 281474976710677, 72057594037928017 );
130
-
131
-		if( $chunkSize > 7 ) {
132
-			throw new \OutOfRangeException( 'Chunk sizes with that many bytes are not implemented yet.' );
133
-		}
134
-
135
-		$this->chunkSize = $chunkSize;
136
-		// if chunk size has been set already, we will only increase it, if necessary
137
-		$this->prime = $primeNumber[$chunkSize];
138
-
139
-		return $this;
140
-	}
141
-
142
-
143
-	/**
144
-	 * Configure encoding parameters
145
-	 *
146
-	 * Depending on the number of required shares, we need to change
147
-	 * prime number, key length, chunk size and more.
148
-	 *
149
-	 * If the chunk size has been set already, it will be changed, if
150
-	 * it is smaller than the necessary size.
151
-	 *
152
-	 * @see     setChunkSize()
153
-	 * @param   int $max Maximum number of keys needed
154
-	 * @return  Shamir
155
-	 * @throws  \OutOfRangeException
156
-	 */
157
-	protected function setMaxShares( $max ) {
158
-
159
-		// the prime number has to be larger, than the maximum number
160
-		// representable by the number of bytes. so we always need one
161
-		// byte more for encryption. if someone wants to use 256 shares,
162
-		// we could encrypt 256 with a single byte, but due to encrypting
163
-		// with a bigger prime number, we will need to use 2 bytes.
164
-
165
-		// max possible number of shares is the maximum number of bytes
166
-		// possible to be represented with max integer, but we always need
167
-		// to save one byte for encryption.
168
-		$maxPossible = 1 << ( PHP_INT_SIZE - 1 ) * 8;
169
-
170
-		if( $max > $maxPossible ) {
171
-			// we are unable to provide more bytes-1 as supported by OS
172
-			// because the prime number need to be higher than that, but
173
-			// this would exceed OS int range.
174
-			throw new \OutOfRangeException(
175
-				'Number of required keys has to be below ' . number_format( $maxPossible ) . '.'
176
-			);
177
-		}
178
-
179
-		// calculate how many bytes we need to represent number of shares.
180
-		// e.g. everything less than 256 needs only a single byte.
181
-		$chunkSize = (int)ceil( log( $max, 2 ) / 8 );
182
-		// if chunk size has been set already, we will only increase it, if necessary
183
-		$chunkSize = max( $chunkSize, $this->chunkSize );
184
-
185
-		if( $chunkSize > $this->chunkSize ) {
186
-			$this->setChunkSize( $chunkSize );
187
-		}
188
-
189
-		$this->maxShares = $max;
190
-
191
-		return $this;
192
-	}
193
-
194
-
195
-	/**
196
-	 * Calculate modulo of any given number using prime
197
-	 *
198
-	 * @param   integer     Number
199
-	 * @return  integer     Module of number
200
-	 */
201
-	protected function modulo( $number ) {
202
-
203
-		$modulo = bcmod( $number, $this->prime );
204
-
205
-		return ( $modulo < 0 ) ? bcadd( $modulo, $this->prime ) : $modulo;
206
-	}
207
-
208
-
209
-	/**
210
-	 * Returns decomposition of the greatest common divisor of a and b
211
-	 *
212
-	 * @param int $a
213
-	 * @param int $b
214
-	 * @return array
215
-	 */
216
-	protected function gcdD( $a, $b ) {
217
-
218
-		if( $b == 0 ) {
219
-			return array( $a, 1, 0 );
220
-		} else {
221
-			$div = floor( bcdiv( $a, $b ) );
222
-			$mod = bcmod( $a, $b );
223
-			$decomp = $this->gcdD( $b, $mod );
224
-			return array( $decomp[0], $decomp[2], $decomp[1] - $decomp[2] * $div );
225
-		}
226
-	}
227
-
228
-
229
-	/**
230
-	 * Calculates the inverse modulo
231
-	 *
232
-	 * @param int $number
233
-	 * @return int
234
-	 */
235
-	protected function inverseModulo( $number ) {
236
-
237
-		$number = bcmod( $number, $this->prime );
238
-		$r = $this->gcdD( $this->prime, abs( $number ) );
239
-		$r = ( $number < 0 ) ? -$r[2] : $r[2];
240
-
241
-		return bcmod( bcadd( $this->prime, $r ), $this->prime );
242
-	}
243
-
244
-
245
-	/**
246
-	 * Calculates the reverse coefficients
247
-	 *
248
-	 * @param   array $keyX
249
-	 * @param   int   $threshold
250
-	 * @return  array
251
-	 * @throws  \RuntimeException
252
-	 */
253
-	protected function reverseCoefficients( array $keyX, $threshold ) {
254
-
255
-		$coefficients = array();
256
-
257
-		for( $i = 0; $i < $threshold; $i++ ) {
258
-			$temp = 1;
259
-			for( $j = 0; $j < $threshold; $j++ ) {
260
-				if( $i != $j ) {
261
-					$temp = $this->modulo(
262
-								 bcmul( bcmul( -$temp, $keyX[$j] ), $this->inverseModulo( $keyX[$i] - $keyX[$j] ) )
263
-					);
264
-				}
265
-			}
266
-
267
-			if( $temp == 0 ) {
268
-				/* Repeated share */
269
-				throw new \RuntimeException( 'Repeated share detected - cannot compute reverse-coefficients' );
270
-			}
271
-
272
-			$coefficients[] = $temp;
273
-		}
274
-
275
-		return $coefficients;
276
-	}
277
-
278
-
279
-	/**
280
-	 * Generate random coefficients
281
-	 *
282
-	 * @param   integer $threshold Number of coefficients needed
283
-	 * @return  array
284
-	 */
285
-	protected function generateCoefficients( $threshold ) {
286
-
287
-		$coefficients = array();
288
-		for( $i = 0; $i < $threshold - 1; $i++ ) {
289
-			do {
290
-				// the random number has to be positive integer != 0
291
-				$random = abs( $this->getRandomGenerator()->getRandomInt() );
292
-			} while($random < 1);
293
-			$coefficients[] = $this->modulo( $random );
294
-		}
295
-
296
-		return $coefficients;
297
-	}
298
-
299
-
300
-	/**
301
-	 * Calculate y values of polynomial curve using horner's method
302
-	 *
303
-	 * Horner converts a polynomial formula like
304
-	 * 11 + 7x - 5x^2 - 4x^3 + 2x^4
305
-	 * into a more efficient formula
306
-	 * 11 + x * ( 7 + x * ( -5 + x * ( -4 + x * 2 ) ) )
307
-	 *
308
-	 * @see     http://en.wikipedia.org/wiki/Horner%27s_method
309
-	 * @param   integer $xCoordinate            X coordinate
310
-	 * @param   array   $coefficients Polynomial coefficients
311
-	 * @return  integer                     Y coordinate
312
-	 */
313
-	protected function hornerMethod( $xCoordinate, array $coefficients ) {
314
-
315
-		$yCoordinate = 0;
316
-		foreach( $coefficients as $coefficient ) {
317
-			$yCoordinate = $this->modulo( $xCoordinate * $yCoordinate + $coefficient );
318
-		}
319
-
320
-		return $yCoordinate;
321
-	}
322
-
323
-
324
-	/**
325
-	 * Converts from $fromBaseInput to $toBaseInput
326
-	 *
327
-	 * @param   string $numberInput
328
-	 * @param   string $fromBaseInput
329
-	 * @param   string $toBaseInput
330
-	 * @return  string
331
-	 */
332
-	protected static function convBase( $numberInput, $fromBaseInput, $toBaseInput ) {
333
-
334
-		if( $fromBaseInput == $toBaseInput ) {
335
-			return $numberInput;
336
-		}
337
-		$fromBase = str_split( $fromBaseInput, 1 );
338
-		$toBase = str_split( $toBaseInput, 1 );
339
-		$number = str_split( $numberInput, 1 );
340
-		$fromLen = strlen( $fromBaseInput );
341
-		$toLen = strlen( $toBaseInput );
342
-		$numberLen = strlen( $numberInput );
343
-		$retVal = '';
344
-		if( $toBaseInput == '0123456789' ) {
345
-			$retVal = 0;
346
-			for( $i = 1; $i <= $numberLen; $i++ ) {
347
-				$retVal = bcadd(
348
-					$retVal,
349
-					bcmul( array_search( $number[$i - 1], $fromBase ), bcpow( $fromLen, $numberLen - $i ) )
350
-				);
351
-			}
352
-
353
-			return $retVal;
354
-		}
355
-		if( $fromBaseInput != '0123456789' ) {
356
-			$base10 = self::convBase( $numberInput, $fromBaseInput, '0123456789' );
357
-		} else {
358
-			$base10 = $numberInput;
359
-		}
360
-		if( $base10 < strlen( $toBaseInput ) ) {
361
-			return $toBase[$base10];
362
-		}
363
-		while($base10 != '0') {
364
-			$retVal = $toBase[bcmod( $base10, $toLen )] . $retVal;
365
-			$base10 = bcdiv( $base10, $toLen, 0 );
366
-		}
367
-
368
-		return $retVal;
369
-	}
370
-
371
-
372
-	/**
373
-	 * Unpack a binary string and convert it into decimals
374
-	 *
375
-	 * Convert each chunk of a binary data into decimal numbers.
376
-	 *
377
-	 * @param   string $string Binary string
378
-	 * @return  array           Array with decimal converted numbers
379
-	 */
380
-	protected function unpack( $string ) {
381
-
382
-		$chunk = 0;
383
-		$int = null;
384
-		$return = array();
385
-		foreach( unpack( 'C*', $string ) as $byte ) {
386
-			$int = bcadd( $int, bcmul( $byte, bcpow( 2, $chunk * 8 ) ) );
387
-			if( ++$chunk == $this->chunkSize ) {
388
-				$return[] = $int;
389
-				$chunk = 0;
390
-				$int = null;
391
-			}
392
-		}
393
-		if( $chunk > 0 ) {
394
-			$return[] = $int;
395
-		}
396
-
397
-		return $return;
398
-	}
399
-
400
-
401
-	/**
402
-	 * Returns maximum length of converted string to new base
403
-	 *
404
-	 * Calculate the maximum length of a string, which can be
405
-	 * represented with the number of given bytes and convert
406
-	 * its base.
407
-	 *
408
-	 * @param   integer $bytes Bytes used to represent a string
409
-	 * @return  integer Number of chars
410
-	 */
411
-	protected function maxKeyLength( $bytes ) {
412
-
413
-		$maxInt = bcpow( 2, $bytes * 8 );
414
-		$converted = self::convBase( $maxInt, self::DECIMAL, self::CHARS );
415
-		return strlen( $converted );
416
-	}
417
-
418
-
419
-	/**
420
-	 * Divide secret into chunks and calculate coordinates
421
-	 *
422
-	 * @param   string  $secret    Secret
423
-	 * @param   integer $shares    Number of parts to share
424
-	 * @param   integer $threshold Minimum number of shares required for decryption
425
-	 * @return  array
426
-	 */
427
-	protected function divideSecret( $secret, $shares, $threshold ) {
428
-
429
-		// divide secret into chunks, which we encrypt one by one
430
-		$result = array();
431
-
432
-		foreach( $this->unpack( $secret ) as $bytes ) {
433
-			$coeffs = $this->generateCoefficients( $threshold );
434
-			$coeffs[] = $bytes;
435
-
436
-			// go through x coordinates and calculate y value
437
-			for( $x = 1; $x <= $shares; $x++ ) {
438
-				// use horner method to calculate y value
439
-				$result[] = $this->hornerMethod( $x, $coeffs );
440
-			}
441
-		}
442
-
443
-		return $result;
444
-	}
445
-
446
-
447
-	/**
448
-	 * @inheritdoc
449
-	 */
450
-	public function share( $secret, $shares, $threshold = 2 ) {
451
-
452
-		$this->setMaxShares( $shares );
453
-
454
-		// check if number of shares is less than our prime, otherwise we have a security problem
455
-		if( $shares >= $this->prime || $shares < 1 ) {
456
-			throw new \OutOfRangeException( 'Number of shares has to be between 1 and ' . $this->prime . '.' );
457
-		}
458
-
459
-		if( $shares < $threshold ) {
460
-			throw new \OutOfRangeException( 'Threshold has to be between 1 and ' . $shares . '.' );
461
-		}
462
-
463
-		if( strpos( self::CHARS, self::PAD_CHAR ) !== false ) {
464
-			throw new \OutOfRangeException( 'Padding character must not be part of possible encryption chars.' );
465
-		}
466
-
467
-		// divide secret into chunks, which we encrypt one by one
468
-		$result = $this->divideSecret( $secret, $shares, $threshold );
469
-
470
-		// encode number of bytes and threshold
471
-
472
-		// calculate the maximum length of key sequence number and threshold
473
-		$maxBaseLength = $this->maxKeyLength( $this->chunkSize );
474
-		// in order to do a correct padding to the converted base, we need to use the first char of the base
475
-		$paddingChar = substr( self::CHARS, 0, 1 );
476
-		// define prefix number using the number of bytes (hex), and a left padded string used for threshold (base converted)
477
-		$fixPrefixFormat = '%x%' . $paddingChar . $maxBaseLength . 's';
478
-		// prefix is going to be the same for all keys
479
-		$prefix = sprintf( $fixPrefixFormat, $this->chunkSize, self::convBase( $threshold, self::DECIMAL, self::CHARS ) );
480
-
481
-		// convert y coordinates into hexadecimals shares
482
-		$passwords = array();
483
-		$secretLen = strlen( $secret );
484
-		// calculate how many bytes, we need to cut off during recovery
485
-		if( $secretLen % $this->chunkSize > 0 ) {
486
-			$tail = str_repeat( self::PAD_CHAR, $this->chunkSize - $secretLen % $this->chunkSize );
487
-		} else {
488
-			$tail = '';
489
-		}
490
-
491
-		$chunks = ceil( $secretLen / $this->chunkSize );
492
-		for( $i = 0; $i < $shares; ++$i ) {
493
-			$sequence = self::convBase( ( $i + 1 ), self::DECIMAL, self::CHARS );
494
-			$key = sprintf( $prefix . '%' . $paddingChar . $maxBaseLength . 's', $sequence );
495
-
496
-			for( $j = 0; $j < $chunks; ++$j ) {
497
-				$key .= str_pad(
498
-					self::convBase( $result[$j * $shares + $i], self::DECIMAL, self::CHARS ),
499
-					$maxBaseLength,
500
-					$paddingChar,
501
-					STR_PAD_LEFT
502
-				);
503
-			}
504
-			$passwords[] = $key . $tail;
505
-		}
506
-
507
-		return $passwords;
508
-	}
509
-
510
-
511
-	/**
512
-	 * Decode and merge secret chunks
513
-	 *
514
-	 * @param array   $keyX      Keys for X coordinates
515
-	 * @param array   $keyY      Keys for Y coordinates
516
-	 * @param integer $bytes     Chunk size in bytes
517
-	 * @param integer $keyLen    Key length in chunks
518
-	 * @param integer $threshold Minimum number of shares required for decryption
519
-	 * @return string
520
-	 */
521
-	protected function joinSecret( $keyX, $keyY, $bytes, $keyLen, $threshold ) {
522
-
523
-		$coefficients = $this->reverseCoefficients( $keyX, $threshold );
524
-
525
-		$secret = '';
526
-		for( $i = 0; $i < $keyLen; $i++ ) {
527
-			$temp = 0;
528
-			for( $j = 0; $j < $threshold; $j++ ) {
529
-				$temp = $this->modulo(
530
-							 bcadd( $temp, bcmul( $keyY[$j * $keyLen + $i], $coefficients[$j] ) )
531
-				);
532
-			}
533
-			// convert each byte back into char
534
-			for( $byte = 1; $byte <= $bytes; ++$byte ) {
535
-				$char = bcmod( $temp, 256 );
536
-				$secret .= chr( $char );
537
-				$temp = bcdiv( bcsub( $temp, $char ), 256 );
538
-			}
539
-		}
540
-
541
-		return $secret;
542
-	}
543
-
544
-
545
-	/**
546
-	 * @inheritdoc
547
-	 */
548
-	public function recover( array $keys ) {
549
-
550
-		if( !count( $keys ) ) {
551
-			throw new \RuntimeException( 'No keys given.' );
552
-		}
553
-
554
-		$keyX = array();
555
-		$keyY = array();
556
-		$keyLen = null;
557
-		$threshold = null;
558
-
559
-		// analyse first key
560
-		$key = reset( $keys );
561
-		// first we need to find out the bytes to predict threshold and sequence length
562
-		$bytes = hexdec( substr( $key, 0, 1 ) );
563
-		$this->setChunkSize( $bytes );
564
-		// calculate the maximum length of key sequence number and threshold
565
-		$maxBaseLength = $this->maxKeyLength( $bytes );
566
-		// define key format: bytes (hex), threshold, sequence, and key (except of bytes, all is base converted)
567
-		$keyFormat = '%1x%' . $maxBaseLength . 's%' . $maxBaseLength . 's%s';
568
-
569
-		foreach( $keys as $key ) {
570
-			// remove trailing padding characters
571
-			$key = str_replace( self::PAD_CHAR, '', $key );
572
-
573
-			// extract "public" information of key: bytes, threshold, sequence
574
-
575
-			list( $keyBytes, $keyThreshold, $keySequence, $key ) = sscanf( $key, $keyFormat );
576
-			$keyThreshold = (int)self::convBase( $keyThreshold, self::CHARS, self::DECIMAL );
577
-			$keySequence = (int)self::convBase( $keySequence, self::CHARS, self::DECIMAL );
578
-
579
-			if( $threshold === null ) {
580
-				$threshold = $keyThreshold;
581
-
582
-				if( $threshold > count( $keys ) ) {
583
-					throw new \RuntimeException( 'Not enough keys to disclose secret.' );
584
-				}
585
-			} elseif( $threshold != $keyThreshold || $bytes != hexdec( $keyBytes ) ) {
586
-				throw new \RuntimeException( 'Given keys are incompatible.' );
587
-			}
588
-
589
-			$keyX[] = $keySequence;
590
-			if( $keyLen === null ) {
591
-				$keyLen = strlen( $key );
592
-			} elseif( $keyLen != strlen( $key ) ) {
593
-				throw new \RuntimeException( 'Given keys vary in key length.' );
594
-			}
595
-			for( $i = 0; $i < $keyLen; $i += $maxBaseLength ) {
596
-				$keyY[] = self::convBase( substr( $key, $i, $maxBaseLength ), self::CHARS, self::DECIMAL );
597
-			}
598
-		}
599
-
600
-		$keyLen /= $maxBaseLength;
601
-		$secret = $this->joinSecret( $keyX, $keyY, $bytes, $keyLen, $threshold );
602
-
603
-		// remove padding from secret (NULL bytes);
604
-		$padCount = substr_count( reset( $keys ), self::PAD_CHAR );
605
-		if( $padCount ) {
606
-			$secret = substr( $secret, 0, -1 * $padCount );
607
-		}
608
-
609
-		return $secret;
610
-	}
22
+     *
23
+     * Changing this will invalid all previously created keys.
24
+     *
25
+     * @const   string
26
+     */
27
+    const DECIMAL = '0123456789';
28
+
29
+    /**
30
+     * Target base characters to be used in passwords (shares)
31
+     *
32
+     * The more characters are used, the shorter the shares might get.
33
+     * Changing this will invalid all previously created keys.
34
+     *
35
+     * @const   string
36
+     */
37
+    const CHARS = '0123456789abcdefghijklmnopqrstuvwxyz.,:;-+*#%';
38
+
39
+    /**
40
+     * Character to fill up the secret keys
41
+     *
42
+     * @const   string
43
+     */
44
+    const PAD_CHAR = '=';
45
+
46
+    /**
47
+     * Prime number has to be greater than the maximum number of shares possible
48
+     *
49
+     * @var float
50
+     */
51
+    protected $prime = 257;
52
+
53
+    /**
54
+     * Chunk size in bytes
55
+     *
56
+     * The secret will be divided equally. This value defines the chunk size and
57
+     * how many bytes will get encoded at once.
58
+     *
59
+     * @var int
60
+     */
61
+    protected $chunkSize = 1;
62
+
63
+    /**
64
+     * The random generator
65
+     *
66
+     * @var Generator
67
+     */
68
+    protected $randomGenerator;
69
+
70
+    /**
71
+     * Maximum number of shares required
72
+     *
73
+     * @var float
74
+     */
75
+    protected $maxShares = 3;
76
+
77
+
78
+    /**
79
+     * @inheritdoc
80
+     */
81
+    public function getRandomGenerator() {
82
+
83
+        if( !$this->randomGenerator ) {
84
+            $this->randomGenerator = new PhpGenerator();
85
+        }
86
+
87
+        return $this->randomGenerator;
88
+    }
89
+
90
+
91
+    /**
92
+     * @inheritdoc
93
+     * @return  Shamir
94
+     */
95
+    public function setRandomGenerator( Generator $generator ) {
96
+
97
+        $this->randomGenerator = $generator;
98
+
99
+        return $this;
100
+    }
101
+
102
+
103
+    /**
104
+     * Returns chunk size in bytes
105
+     *
106
+     * @return int
107
+     */
108
+    public function getChunkSize() {
109
+
110
+        return $this->chunkSize;
111
+    }
112
+
113
+
114
+    /**
115
+     * Sets chunk size in bytes
116
+     *
117
+     * If maximum shares have been set already, the chunk
118
+     * size might have been set with it. It is not possible
119
+     * to set a smaller size than required by shares.
120
+     *
121
+     * @see
122
+     * @param   int $chunkSize Size in number of bytes
123
+     * @return  Shamir
124
+     * @throws  \OutOfRangeException
125
+     */
126
+    public function setChunkSize( $chunkSize ) {
127
+
128
+        $chunkSize = (int)$chunkSize;
129
+        $primeNumber = array( 1 => 257, 65537, 16777259, 4294967311, 1099511627791, 281474976710677, 72057594037928017 );
130
+
131
+        if( $chunkSize > 7 ) {
132
+            throw new \OutOfRangeException( 'Chunk sizes with that many bytes are not implemented yet.' );
133
+        }
134
+
135
+        $this->chunkSize = $chunkSize;
136
+        // if chunk size has been set already, we will only increase it, if necessary
137
+        $this->prime = $primeNumber[$chunkSize];
138
+
139
+        return $this;
140
+    }
141
+
142
+
143
+    /**
144
+     * Configure encoding parameters
145
+     *
146
+     * Depending on the number of required shares, we need to change
147
+     * prime number, key length, chunk size and more.
148
+     *
149
+     * If the chunk size has been set already, it will be changed, if
150
+     * it is smaller than the necessary size.
151
+     *
152
+     * @see     setChunkSize()
153
+     * @param   int $max Maximum number of keys needed
154
+     * @return  Shamir
155
+     * @throws  \OutOfRangeException
156
+     */
157
+    protected function setMaxShares( $max ) {
158
+
159
+        // the prime number has to be larger, than the maximum number
160
+        // representable by the number of bytes. so we always need one
161
+        // byte more for encryption. if someone wants to use 256 shares,
162
+        // we could encrypt 256 with a single byte, but due to encrypting
163
+        // with a bigger prime number, we will need to use 2 bytes.
164
+
165
+        // max possible number of shares is the maximum number of bytes
166
+        // possible to be represented with max integer, but we always need
167
+        // to save one byte for encryption.
168
+        $maxPossible = 1 << ( PHP_INT_SIZE - 1 ) * 8;
169
+
170
+        if( $max > $maxPossible ) {
171
+            // we are unable to provide more bytes-1 as supported by OS
172
+            // because the prime number need to be higher than that, but
173
+            // this would exceed OS int range.
174
+            throw new \OutOfRangeException(
175
+                'Number of required keys has to be below ' . number_format( $maxPossible ) . '.'
176
+            );
177
+        }
178
+
179
+        // calculate how many bytes we need to represent number of shares.
180
+        // e.g. everything less than 256 needs only a single byte.
181
+        $chunkSize = (int)ceil( log( $max, 2 ) / 8 );
182
+        // if chunk size has been set already, we will only increase it, if necessary
183
+        $chunkSize = max( $chunkSize, $this->chunkSize );
184
+
185
+        if( $chunkSize > $this->chunkSize ) {
186
+            $this->setChunkSize( $chunkSize );
187
+        }
188
+
189
+        $this->maxShares = $max;
190
+
191
+        return $this;
192
+    }
193
+
194
+
195
+    /**
196
+     * Calculate modulo of any given number using prime
197
+     *
198
+     * @param   integer     Number
199
+     * @return  integer     Module of number
200
+     */
201
+    protected function modulo( $number ) {
202
+
203
+        $modulo = bcmod( $number, $this->prime );
204
+
205
+        return ( $modulo < 0 ) ? bcadd( $modulo, $this->prime ) : $modulo;
206
+    }
207
+
208
+
209
+    /**
210
+     * Returns decomposition of the greatest common divisor of a and b
211
+     *
212
+     * @param int $a
213
+     * @param int $b
214
+     * @return array
215
+     */
216
+    protected function gcdD( $a, $b ) {
217
+
218
+        if( $b == 0 ) {
219
+            return array( $a, 1, 0 );
220
+        } else {
221
+            $div = floor( bcdiv( $a, $b ) );
222
+            $mod = bcmod( $a, $b );
223
+            $decomp = $this->gcdD( $b, $mod );
224
+            return array( $decomp[0], $decomp[2], $decomp[1] - $decomp[2] * $div );
225
+        }
226
+    }
227
+
228
+
229
+    /**
230
+     * Calculates the inverse modulo
231
+     *
232
+     * @param int $number
233
+     * @return int
234
+     */
235
+    protected function inverseModulo( $number ) {
236
+
237
+        $number = bcmod( $number, $this->prime );
238
+        $r = $this->gcdD( $this->prime, abs( $number ) );
239
+        $r = ( $number < 0 ) ? -$r[2] : $r[2];
240
+
241
+        return bcmod( bcadd( $this->prime, $r ), $this->prime );
242
+    }
243
+
244
+
245
+    /**
246
+     * Calculates the reverse coefficients
247
+     *
248
+     * @param   array $keyX
249
+     * @param   int   $threshold
250
+     * @return  array
251
+     * @throws  \RuntimeException
252
+     */
253
+    protected function reverseCoefficients( array $keyX, $threshold ) {
254
+
255
+        $coefficients = array();
256
+
257
+        for( $i = 0; $i < $threshold; $i++ ) {
258
+            $temp = 1;
259
+            for( $j = 0; $j < $threshold; $j++ ) {
260
+                if( $i != $j ) {
261
+                    $temp = $this->modulo(
262
+                                    bcmul( bcmul( -$temp, $keyX[$j] ), $this->inverseModulo( $keyX[$i] - $keyX[$j] ) )
263
+                    );
264
+                }
265
+            }
266
+
267
+            if( $temp == 0 ) {
268
+                /* Repeated share */
269
+                throw new \RuntimeException( 'Repeated share detected - cannot compute reverse-coefficients' );
270
+            }
271
+
272
+            $coefficients[] = $temp;
273
+        }
274
+
275
+        return $coefficients;
276
+    }
277
+
278
+
279
+    /**
280
+     * Generate random coefficients
281
+     *
282
+     * @param   integer $threshold Number of coefficients needed
283
+     * @return  array
284
+     */
285
+    protected function generateCoefficients( $threshold ) {
286
+
287
+        $coefficients = array();
288
+        for( $i = 0; $i < $threshold - 1; $i++ ) {
289
+            do {
290
+                // the random number has to be positive integer != 0
291
+                $random = abs( $this->getRandomGenerator()->getRandomInt() );
292
+            } while($random < 1);
293
+            $coefficients[] = $this->modulo( $random );
294
+        }
295
+
296
+        return $coefficients;
297
+    }
298
+
299
+
300
+    /**
301
+     * Calculate y values of polynomial curve using horner's method
302
+     *
303
+     * Horner converts a polynomial formula like
304
+     * 11 + 7x - 5x^2 - 4x^3 + 2x^4
305
+     * into a more efficient formula
306
+     * 11 + x * ( 7 + x * ( -5 + x * ( -4 + x * 2 ) ) )
307
+     *
308
+     * @see     http://en.wikipedia.org/wiki/Horner%27s_method
309
+     * @param   integer $xCoordinate            X coordinate
310
+     * @param   array   $coefficients Polynomial coefficients
311
+     * @return  integer                     Y coordinate
312
+     */
313
+    protected function hornerMethod( $xCoordinate, array $coefficients ) {
314
+
315
+        $yCoordinate = 0;
316
+        foreach( $coefficients as $coefficient ) {
317
+            $yCoordinate = $this->modulo( $xCoordinate * $yCoordinate + $coefficient );
318
+        }
319
+
320
+        return $yCoordinate;
321
+    }
322
+
323
+
324
+    /**
325
+     * Converts from $fromBaseInput to $toBaseInput
326
+     *
327
+     * @param   string $numberInput
328
+     * @param   string $fromBaseInput
329
+     * @param   string $toBaseInput
330
+     * @return  string
331
+     */
332
+    protected static function convBase( $numberInput, $fromBaseInput, $toBaseInput ) {
333
+
334
+        if( $fromBaseInput == $toBaseInput ) {
335
+            return $numberInput;
336
+        }
337
+        $fromBase = str_split( $fromBaseInput, 1 );
338
+        $toBase = str_split( $toBaseInput, 1 );
339
+        $number = str_split( $numberInput, 1 );
340
+        $fromLen = strlen( $fromBaseInput );
341
+        $toLen = strlen( $toBaseInput );
342
+        $numberLen = strlen( $numberInput );
343
+        $retVal = '';
344
+        if( $toBaseInput == '0123456789' ) {
345
+            $retVal = 0;
346
+            for( $i = 1; $i <= $numberLen; $i++ ) {
347
+                $retVal = bcadd(
348
+                    $retVal,
349
+                    bcmul( array_search( $number[$i - 1], $fromBase ), bcpow( $fromLen, $numberLen - $i ) )
350
+                );
351
+            }
352
+
353
+            return $retVal;
354
+        }
355
+        if( $fromBaseInput != '0123456789' ) {
356
+            $base10 = self::convBase( $numberInput, $fromBaseInput, '0123456789' );
357
+        } else {
358
+            $base10 = $numberInput;
359
+        }
360
+        if( $base10 < strlen( $toBaseInput ) ) {
361
+            return $toBase[$base10];
362
+        }
363
+        while($base10 != '0') {
364
+            $retVal = $toBase[bcmod( $base10, $toLen )] . $retVal;
365
+            $base10 = bcdiv( $base10, $toLen, 0 );
366
+        }
367
+
368
+        return $retVal;
369
+    }
370
+
371
+
372
+    /**
373
+     * Unpack a binary string and convert it into decimals
374
+     *
375
+     * Convert each chunk of a binary data into decimal numbers.
376
+     *
377
+     * @param   string $string Binary string
378
+     * @return  array           Array with decimal converted numbers
379
+     */
380
+    protected function unpack( $string ) {
381
+
382
+        $chunk = 0;
383
+        $int = null;
384
+        $return = array();
385
+        foreach( unpack( 'C*', $string ) as $byte ) {
386
+            $int = bcadd( $int, bcmul( $byte, bcpow( 2, $chunk * 8 ) ) );
387
+            if( ++$chunk == $this->chunkSize ) {
388
+                $return[] = $int;
389
+                $chunk = 0;
390
+                $int = null;
391
+            }
392
+        }
393
+        if( $chunk > 0 ) {
394
+            $return[] = $int;
395
+        }
396
+
397
+        return $return;
398
+    }
399
+
400
+
401
+    /**
402
+     * Returns maximum length of converted string to new base
403
+     *
404
+     * Calculate the maximum length of a string, which can be
405
+     * represented with the number of given bytes and convert
406
+     * its base.
407
+     *
408
+     * @param   integer $bytes Bytes used to represent a string
409
+     * @return  integer Number of chars
410
+     */
411
+    protected function maxKeyLength( $bytes ) {
412
+
413
+        $maxInt = bcpow( 2, $bytes * 8 );
414
+        $converted = self::convBase( $maxInt, self::DECIMAL, self::CHARS );
415
+        return strlen( $converted );
416
+    }
417
+
418
+
419
+    /**
420
+     * Divide secret into chunks and calculate coordinates
421
+     *
422
+     * @param   string  $secret    Secret
423
+     * @param   integer $shares    Number of parts to share
424
+     * @param   integer $threshold Minimum number of shares required for decryption
425
+     * @return  array
426
+     */
427
+    protected function divideSecret( $secret, $shares, $threshold ) {
428
+
429
+        // divide secret into chunks, which we encrypt one by one
430
+        $result = array();
431
+
432
+        foreach( $this->unpack( $secret ) as $bytes ) {
433
+            $coeffs = $this->generateCoefficients( $threshold );
434
+            $coeffs[] = $bytes;
435
+
436
+            // go through x coordinates and calculate y value
437
+            for( $x = 1; $x <= $shares; $x++ ) {
438
+                // use horner method to calculate y value
439
+                $result[] = $this->hornerMethod( $x, $coeffs );
440
+            }
441
+        }
442
+
443
+        return $result;
444
+    }
445
+
446
+
447
+    /**
448
+     * @inheritdoc
449
+     */
450
+    public function share( $secret, $shares, $threshold = 2 ) {
451
+
452
+        $this->setMaxShares( $shares );
453
+
454
+        // check if number of shares is less than our prime, otherwise we have a security problem
455
+        if( $shares >= $this->prime || $shares < 1 ) {
456
+            throw new \OutOfRangeException( 'Number of shares has to be between 1 and ' . $this->prime . '.' );
457
+        }
458
+
459
+        if( $shares < $threshold ) {
460
+            throw new \OutOfRangeException( 'Threshold has to be between 1 and ' . $shares . '.' );
461
+        }
462
+
463
+        if( strpos( self::CHARS, self::PAD_CHAR ) !== false ) {
464
+            throw new \OutOfRangeException( 'Padding character must not be part of possible encryption chars.' );
465
+        }
466
+
467
+        // divide secret into chunks, which we encrypt one by one
468
+        $result = $this->divideSecret( $secret, $shares, $threshold );
469
+
470
+        // encode number of bytes and threshold
471
+
472
+        // calculate the maximum length of key sequence number and threshold
473
+        $maxBaseLength = $this->maxKeyLength( $this->chunkSize );
474
+        // in order to do a correct padding to the converted base, we need to use the first char of the base
475
+        $paddingChar = substr( self::CHARS, 0, 1 );
476
+        // define prefix number using the number of bytes (hex), and a left padded string used for threshold (base converted)
477
+        $fixPrefixFormat = '%x%' . $paddingChar . $maxBaseLength . 's';
478
+        // prefix is going to be the same for all keys
479
+        $prefix = sprintf( $fixPrefixFormat, $this->chunkSize, self::convBase( $threshold, self::DECIMAL, self::CHARS ) );
480
+
481
+        // convert y coordinates into hexadecimals shares
482
+        $passwords = array();
483
+        $secretLen = strlen( $secret );
484
+        // calculate how many bytes, we need to cut off during recovery
485
+        if( $secretLen % $this->chunkSize > 0 ) {
486
+            $tail = str_repeat( self::PAD_CHAR, $this->chunkSize - $secretLen % $this->chunkSize );
487
+        } else {
488
+            $tail = '';
489
+        }
490
+
491
+        $chunks = ceil( $secretLen / $this->chunkSize );
492
+        for( $i = 0; $i < $shares; ++$i ) {
493
+            $sequence = self::convBase( ( $i + 1 ), self::DECIMAL, self::CHARS );
494
+            $key = sprintf( $prefix . '%' . $paddingChar . $maxBaseLength . 's', $sequence );
495
+
496
+            for( $j = 0; $j < $chunks; ++$j ) {
497
+                $key .= str_pad(
498
+                    self::convBase( $result[$j * $shares + $i], self::DECIMAL, self::CHARS ),
499
+                    $maxBaseLength,
500
+                    $paddingChar,
501
+                    STR_PAD_LEFT
502
+                );
503
+            }
504
+            $passwords[] = $key . $tail;
505
+        }
506
+
507
+        return $passwords;
508
+    }
509
+
510
+
511
+    /**
512
+     * Decode and merge secret chunks
513
+     *
514
+     * @param array   $keyX      Keys for X coordinates
515
+     * @param array   $keyY      Keys for Y coordinates
516
+     * @param integer $bytes     Chunk size in bytes
517
+     * @param integer $keyLen    Key length in chunks
518
+     * @param integer $threshold Minimum number of shares required for decryption
519
+     * @return string
520
+     */
521
+    protected function joinSecret( $keyX, $keyY, $bytes, $keyLen, $threshold ) {
522
+
523
+        $coefficients = $this->reverseCoefficients( $keyX, $threshold );
524
+
525
+        $secret = '';
526
+        for( $i = 0; $i < $keyLen; $i++ ) {
527
+            $temp = 0;
528
+            for( $j = 0; $j < $threshold; $j++ ) {
529
+                $temp = $this->modulo(
530
+                                bcadd( $temp, bcmul( $keyY[$j * $keyLen + $i], $coefficients[$j] ) )
531
+                );
532
+            }
533
+            // convert each byte back into char
534
+            for( $byte = 1; $byte <= $bytes; ++$byte ) {
535
+                $char = bcmod( $temp, 256 );
536
+                $secret .= chr( $char );
537
+                $temp = bcdiv( bcsub( $temp, $char ), 256 );
538
+            }
539
+        }
540
+
541
+        return $secret;
542
+    }
543
+
544
+
545
+    /**
546
+     * @inheritdoc
547
+     */
548
+    public function recover( array $keys ) {
549
+
550
+        if( !count( $keys ) ) {
551
+            throw new \RuntimeException( 'No keys given.' );
552
+        }
553
+
554
+        $keyX = array();
555
+        $keyY = array();
556
+        $keyLen = null;
557
+        $threshold = null;
558
+
559
+        // analyse first key
560
+        $key = reset( $keys );
561
+        // first we need to find out the bytes to predict threshold and sequence length
562
+        $bytes = hexdec( substr( $key, 0, 1 ) );
563
+        $this->setChunkSize( $bytes );
564
+        // calculate the maximum length of key sequence number and threshold
565
+        $maxBaseLength = $this->maxKeyLength( $bytes );
566
+        // define key format: bytes (hex), threshold, sequence, and key (except of bytes, all is base converted)
567
+        $keyFormat = '%1x%' . $maxBaseLength . 's%' . $maxBaseLength . 's%s';
568
+
569
+        foreach( $keys as $key ) {
570
+            // remove trailing padding characters
571
+            $key = str_replace( self::PAD_CHAR, '', $key );
572
+
573
+            // extract "public" information of key: bytes, threshold, sequence
574
+
575
+            list( $keyBytes, $keyThreshold, $keySequence, $key ) = sscanf( $key, $keyFormat );
576
+            $keyThreshold = (int)self::convBase( $keyThreshold, self::CHARS, self::DECIMAL );
577
+            $keySequence = (int)self::convBase( $keySequence, self::CHARS, self::DECIMAL );
578
+
579
+            if( $threshold === null ) {
580
+                $threshold = $keyThreshold;
581
+
582
+                if( $threshold > count( $keys ) ) {
583
+                    throw new \RuntimeException( 'Not enough keys to disclose secret.' );
584
+                }
585
+            } elseif( $threshold != $keyThreshold || $bytes != hexdec( $keyBytes ) ) {
586
+                throw new \RuntimeException( 'Given keys are incompatible.' );
587
+            }
588
+
589
+            $keyX[] = $keySequence;
590
+            if( $keyLen === null ) {
591
+                $keyLen = strlen( $key );
592
+            } elseif( $keyLen != strlen( $key ) ) {
593
+                throw new \RuntimeException( 'Given keys vary in key length.' );
594
+            }
595
+            for( $i = 0; $i < $keyLen; $i += $maxBaseLength ) {
596
+                $keyY[] = self::convBase( substr( $key, $i, $maxBaseLength ), self::CHARS, self::DECIMAL );
597
+            }
598
+        }
599
+
600
+        $keyLen /= $maxBaseLength;
601
+        $secret = $this->joinSecret( $keyX, $keyY, $bytes, $keyLen, $threshold );
602
+
603
+        // remove padding from secret (NULL bytes);
604
+        $padCount = substr_count( reset( $keys ), self::PAD_CHAR );
605
+        if( $padCount ) {
606
+            $secret = substr( $secret, 0, -1 * $padCount );
607
+        }
608
+
609
+        return $secret;
610
+    }
611 611
 
612 612
 }
Please login to merge, or discard this patch.
src/Console/ShareCommand.php 1 patch
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -61,7 +61,7 @@  discard block
 block discarded – undo
61 61
                 '<question>Number of shared secrets to create</question> <comment>[3]</comment>: ', 3
62 62
             );
63 63
             $question->setValidator(
64
-                function ($a) {
64
+                function($a) {
65 65
                     if (!is_int($a) && !ctype_digit($a)) {
66 66
                         throw new \Exception('The number of shared secrets must be an integer');
67 67
                     }
@@ -75,7 +75,7 @@  discard block
 block discarded – undo
75 75
                 '<question>Number of shared secrets required</question> <comment>[2]</comment>: ', 2
76 76
             );
77 77
             $question->setValidator(
78
-                function ($a) {
78
+                function($a) {
79 79
                     if (!is_int($a) && !ctype_digit($a)) {
80 80
                         throw new \Exception('The number of shared secrets required must be an integer');
81 81
                     }
Please login to merge, or discard this patch.