Skip to content

Commit

Permalink
Include AES-256-CTR polyfill. Type safety.
Browse files Browse the repository at this point in the history
  • Loading branch information
paragonie-security committed Jun 6, 2018
1 parent d9a7605 commit fbcf0a8
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.idea
/composer.lock
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
"require": {
"php": "^5.6.0|^7.0.0",
"paragonie/constant_time_encoding": "^1|^2",
"paragonie/random_compat": "^1|^2"
"paragonie/random_compat": ">=1"
},
"require-dev": {
"phpunit/phpunit": "5.*",
"vimeo/psalm": "^0|^1"
"vimeo/psalm": "^1"
}
}
1 change: 1 addition & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<psalm
stopOnFirstError="false"
useDocblockTypes="true"
totallyTyped="true"
>
<projectFiles>
<directory name="src" />
Expand Down
89 changes: 83 additions & 6 deletions src/SeedSpring.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ final class SeedSpring
*/
protected $counter;

/**
* @var bool
*/
protected $usePolyfill;

/**
* SeedSpring constructor.
*
Expand All @@ -33,6 +38,11 @@ public function __construct($seed = '', $counter = 0)
}
$this->seed('set', $seed);
$this->counter = 0;
$this->usePolyfill = !\in_array(
'aes-256-ctr',
\openssl_get_cipher_methods(),
true
);
}

/**
Expand All @@ -51,6 +61,10 @@ private function seed($action = 'get', $data = '')
$seed[$hash] = $data;
return '';
} elseif ($action === 'get') {
/**
* @var array<string, string> $seed
* @var string $return
*/
return (string) $seed[$hash];
}
throw new \Error('Unknown action');
Expand Down Expand Up @@ -84,6 +98,13 @@ public function seek($position, $seektype = self::SEEK_SET)
*/
public function getBytes($numBytes)
{
if ($this->usePolyfill) {
return self::aes256ctr(
\str_repeat("\0", $numBytes),
$this->seed('get'),
$this->getNonce($numBytes)
);
}
return (string) \openssl_encrypt(
\str_repeat("\0", $numBytes),
'aes-128-ctr',
Expand Down Expand Up @@ -127,14 +148,20 @@ public function getInt($min, $max)
* $bytes => the number of random bytes we need
* $mask => an integer bitmask (for use with the &) operator
* so we can minimize the number of discards
*
* @var int $valueShift
* @var int $mask
* @var int $bytes
* @var int $bits
* @var int $attempts
*/
$attempts = $bits = $bytes = $mask = $valueShift = 0;

/**
* At this point, $range is a positive number greater than 0. It might
* overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
* a float and we will lose some precision.
* @var int $range
* @var int|float $range
*/
$range = $max - $min;

Expand All @@ -154,6 +181,7 @@ public function getInt($min, $max)
* @ref http://3v4l.org/XX9r5 (64-bit)
*/
$bytes = PHP_INT_SIZE;
/** @var int $mask */
$mask = ~0;
} else {
/**
Expand Down Expand Up @@ -190,11 +218,6 @@ public function getInt($min, $max)
* Let's grab the necessary number of random bytes
*/
$randomByteString = $this->getBytes($bytes);
if ($randomByteString === false) {
throw new \Exception(
'Random number generator failure'
);
}

/**
* Let's turn $randomByteString into an integer
Expand Down Expand Up @@ -246,4 +269,58 @@ protected function getNonce($increment = 0)
}
return \str_pad($nonce, 16, "\0", STR_PAD_LEFT);
}

/**
* Userland polyfill for AES-256-CTR, using AES-256-ECB
*
* @param string $plaintext
* @param string $key
* @param string $nonce
* @return string
*/
public static function aes256ctr($plaintext, $key, $nonce)
{
if (empty($plaintext)) {
return '';
}
$length = Binary::safeStrlen($plaintext);
/** @var int $numBlocks */
$numBlocks = (($length - 1) >> 4) + 1;
$stream = '';
for ($i = 0; $i < $numBlocks; ++$i) {
$stream .= $nonce;
$nonce = self::ctrNonceIncrease($nonce);
}
/** @var string $xor */
$xor = \openssl_encrypt(
$stream,
'aes-256-ecb',
$key,
OPENSSL_RAW_DATA
);
return (string) (
$plaintext ^ Binary::safeSubstr($xor, 0, $length)
);
}

/**
* Increase a counter nonce, starting with the LSB (big-endian)
*
* @param string $nonce
* @return string
*/
public static function ctrNonceIncrease($nonce)
{
/** @var array<int, int> $pieces */
$pieces = \unpack('C*', $nonce);
$c = 0;
++$pieces[16];
for ($i = 16; $i > 0; --$i) {
$pieces[$i] += $c;
$c = $pieces[$i] >> 8;
$pieces[$i] &= 0xff;
}
\array_unshift($pieces, \str_repeat('C', 16));
return (string) \call_user_func_array('pack', $pieces);
}
}

0 comments on commit fbcf0a8

Please sign in to comment.