Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • Saransaran/php-class-project
  • sibidharan/php-class-project
  • Madhan1024/php-class-project
  • GopiKrishnan/photogram
  • Mhd_khalid/php-class-project
  • At_muthu__/php-class-project
  • jaganbhaskar155/php-class-project
  • hariharanrd/php-class-project
  • jasper715/php-class-project
  • hanuRakesh/photogram-project-main
  • Yuvaraj21/photogram
  • ram_rogers/php-class-project
  • Hihelloboy/php-class-project
  • Nadarajan/php-class-project
  • srisanthosh156/php-class-project
  • Buvaneshwaran.k/php-class-project
  • umarfarooq07/php-class-project
  • Dhanaprakash/php-class-project
  • jashwanth142003/php-class-project
  • Esakkiraja/php-class-project
  • Boomi/php-class-project
  • Kishore2071/php-class-project
  • Ram123raj/php-class-project
  • aswinkumar27/php-class-project
  • dhilipdhilip9655/php-class-project
  • Manikandam143/php-class-project
  • VikramS/php-class-project
  • ArnoldSam/php-class-project
  • gowthamapandi0008/php-class-project
  • d.barath7639/php-class-project
  • shyalandran/php-class-project
  • kiruba_432/php-class-project
  • razakias001/php-class-project
  • kannan.b2745/php-class-project
  • sathish236tsk/php-class-project
  • rii/php-class-project
  • jonathajh4k/php-class-project
  • Neelagandan_G/php-class-project
  • Tholkappiar2003/php-class-project
  • kamaleshselvam75/php-class-project
  • devapriyan/php-class-project
  • sanojahamed/php-class-project
  • rizwankendo/php-class-project
  • senthamilselvan18000/php-class-project
  • rajeshd01/php-class-project
  • Florence/php-class-project
  • vishnu191299/php-class-project
  • Rakeshrakki/php-class-project
  • sanjay057/php-class-project
  • amarsanthoshsanthosh/photogram-project-cp
  • md_ashmar/php-class-project
  • k.nandhishwaran777k/php-class-project
52 results
Show changes
Showing
with 8359 additions and 0 deletions
Copyright (c) 2011-2019 TerraFrost and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
# phpseclib - PHP Secure Communications Library
[![CI Status](https://github.com/phpseclib/phpseclib/actions/workflows/ci.yml/badge.svg?branch=3.0&event=push "CI Status")](https://github.com/phpseclib/phpseclib)
## Supporting phpseclib
- [Become a backer or sponsor on Patreon](https://www.patreon.com/phpseclib)
- [One-time donation via PayPal or crypto-currencies](http://sourceforge.net/donate/index.php?group_id=198487)
- [Subscribe to Tidelift](https://tidelift.com/subscription/pkg/packagist-phpseclib-phpseclib?utm_source=packagist-phpseclib-phpseclib&utm_medium=referral&utm_campaign=readme)
## Introduction
MIT-licensed pure-PHP implementations of the following:
SSH-2, SFTP, X.509, an arbitrary-precision integer arithmetic library, Ed25519 / Ed449 / Curve25519 / Curve449, ECDSA / ECDH (with support for 66 curves), RSA (PKCS#1 v2.2 compliant), DSA / DH, DES / 3DES / RC4 / Rijndael / AES / Blowfish / Twofish / Salsa20 / ChaCha20, GCM / Poly1305
* [Browse Git](https://github.com/phpseclib/phpseclib)
## Documentation
* [Documentation / Manual](https://phpseclib.com/)
* [API Documentation](https://api.phpseclib.com/3.0/) (generated by Doctum)
## Branches
### master
* Development Branch
* Unstable API
* Do not use in production
### 3.0
* Long term support (LTS) release
* Major expansion of cryptographic primitives
* Minimum PHP version: 5.6.1
* PSR-4 autoloading with namespace rooted at `\phpseclib3`
* Install via Composer: `composer require phpseclib/phpseclib:~3.0`
### 2.0
* Long term support (LTS) release
* Modernized version of 1.0
* Minimum PHP version: 5.3.3
* PSR-4 autoloading with namespace rooted at `\phpseclib`
* Install via Composer: `composer require phpseclib/phpseclib:~2.0`
### 1.0
* Long term support (LTS) release
* PHP4 compatible
* Composer compatible (PSR-0 autoloading)
* Install using Composer: `composer require phpseclib/phpseclib:~1.0`
* Install using PEAR: See [phpseclib PEAR Channel Documentation](http://phpseclib.sourceforge.net/pear.htm)
* [Download 1.0.20 as ZIP](http://sourceforge.net/projects/phpseclib/files/phpseclib1.0.20.zip/download)
## Security contact information
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
## Support
Need Support?
* [Checkout Questions and Answers on Stack Overflow](http://stackoverflow.com/questions/tagged/phpseclib)
* [Create a Support Ticket on GitHub](https://github.com/phpseclib/phpseclib/issues/new)
* [Browse the Support Forum](http://www.frostjedi.com/phpbb/viewforum.php?f=46) (no longer in use)
## Special Thanks
Special Thanks to our $50+ sponsors!:
- Allan Simon
- [ChargeOver](https://chargeover.com/)
## Contributing
1. Fork the Project
2. Ensure you have Composer installed (see [Composer Download Instructions](https://getcomposer.org/download/))
3. Install Development Dependencies
```sh
composer install
```
4. Create a Feature Branch
5. Run continuous integration checks:
```sh
composer global require php:^8.1 squizlabs/php_codesniffer friendsofphp/php-cs-fixer vimeo/psalm
phpcs --standard=build/php_codesniffer.xml
php-cs-fixer fix --config=build/php-cs-fixer.php --diff --dry-run --using-cache=no
psalm --config=build/psalm.xml --no-cache --long-progress --report-show-info=false --output-format=text
vendor/bin/phpunit --verbose --configuration tests/phpunit.xml
```
6. Send us a Pull Request
{
"name": "phpseclib/phpseclib",
"type": "library",
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
"keywords": [
"security",
"crypto",
"cryptography",
"encryption",
"signature",
"signing",
"rsa",
"aes",
"blowfish",
"twofish",
"ssh",
"sftp",
"x509",
"x.509",
"asn1",
"asn.1",
"BigInteger"
],
"homepage": "http://phpseclib.sourceforge.net",
"license": "MIT",
"authors": [
{
"name": "Jim Wigginton",
"email": "terrafrost@php.net",
"role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
"email": "pm@datasphere.ch",
"role": "Developer"
},
{
"name": "Andreas Fischer",
"email": "bantu@phpbb.com",
"role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
"email": "petrich@tronic-media.com",
"role": "Developer"
},
{
"name": "Graham Campbell",
"email": "graham@alt-three.com",
"role": "Developer"
}
],
"require": {
"php": ">=5.6.1",
"paragonie/constant_time_encoding": "^1|^2",
"paragonie/random_compat": "^1.4|^2.0|^9.99.99"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"suggest": {
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-dom": "Install the DOM extension to load XML formatted public keys."
},
"autoload": {
"files": [
"phpseclib/bootstrap.php"
],
"psr-4": {
"phpseclib3\\": "phpseclib/"
}
},
"autoload-dev": {
"psr-4": {
"phpseclib3\\Tests\\": "tests/"
}
},
"config": {
"sort-packages": true
}
}
<?php
/**
* Common String Functions
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Common\Functions;
use ParagonIE\ConstantTime\Base64;
use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\ConstantTime\Hex;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\Common\FiniteField;
/**
* Common String Functions
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Strings
{
/**
* String Shift
*
* Inspired by array_shift
*
* @param string $string
* @param int $index
* @return string
*/
public static function shift(&$string, $index = 1)
{
$substr = substr($string, 0, $index);
$string = substr($string, $index);
return $substr;
}
/**
* String Pop
*
* Inspired by array_pop
*
* @param string $string
* @param int $index
* @return string
*/
public static function pop(&$string, $index = 1)
{
$substr = substr($string, -$index);
$string = substr($string, 0, -$index);
return $substr;
}
/**
* Parse SSH2-style string
*
* Returns either an array or a boolean if $data is malformed.
*
* Valid characters for $format are as follows:
*
* C = byte
* b = boolean (true/false)
* N = uint32
* Q = uint64
* s = string
* i = mpint
* L = name-list
*
* uint64 is not supported.
*
* @param string $format
* @param string $data
* @return mixed
*/
public static function unpackSSH2($format, &$data)
{
$format = self::formatPack($format);
$result = [];
for ($i = 0; $i < strlen($format); $i++) {
switch ($format[$i]) {
case 'C':
case 'b':
if (!strlen($data)) {
throw new \LengthException('At least one byte needs to be present for successful C / b decodes');
}
break;
case 'N':
case 'i':
case 's':
case 'L':
if (strlen($data) < 4) {
throw new \LengthException('At least four byte needs to be present for successful N / i / s / L decodes');
}
break;
case 'Q':
if (strlen($data) < 8) {
throw new \LengthException('At least eight byte needs to be present for successful N / i / s / L decodes');
}
break;
default:
throw new \InvalidArgumentException('$format contains an invalid character');
}
switch ($format[$i]) {
case 'C':
$result[] = ord(self::shift($data));
continue 2;
case 'b':
$result[] = ord(self::shift($data)) != 0;
continue 2;
case 'N':
list(, $temp) = unpack('N', self::shift($data, 4));
$result[] = $temp;
continue 2;
case 'Q':
// pack() added support for Q in PHP 5.6.3 and PHP 5.6 is phpseclib 3's minimum version
// so in theory we could support this BUT, "64-bit format codes are not available for
// 32-bit versions" and phpseclib works on 32-bit installs. on 32-bit installs
// 64-bit floats can be used to get larger numbers then 32-bit signed ints would allow
// for. sure, you're not gonna get the full precision of 64-bit numbers but just because
// you need > 32-bit precision doesn't mean you need the full 64-bit precision
extract(unpack('Nupper/Nlower', self::shift($data, 8)));
$temp = $upper ? 4294967296 * $upper : 0;
$temp += $lower < 0 ? ($lower & 0x7FFFFFFFF) + 0x80000000 : $lower;
// $temp = hexdec(bin2hex(self::shift($data, 8)));
$result[] = $temp;
continue 2;
}
list(, $length) = unpack('N', self::shift($data, 4));
if (strlen($data) < $length) {
throw new \LengthException("$length bytes needed; " . strlen($data) . ' bytes available');
}
$temp = self::shift($data, $length);
switch ($format[$i]) {
case 'i':
$result[] = new BigInteger($temp, -256);
break;
case 's':
$result[] = $temp;
break;
case 'L':
$result[] = explode(',', $temp);
}
}
return $result;
}
/**
* Create SSH2-style string
*
* @param string $format
* @param string|int|float|array|bool ...$elements
* @return string
*/
public static function packSSH2($format, ...$elements)
{
$format = self::formatPack($format);
if (strlen($format) != count($elements)) {
throw new \InvalidArgumentException('There must be as many arguments as there are characters in the $format string');
}
$result = '';
for ($i = 0; $i < strlen($format); $i++) {
$element = $elements[$i];
switch ($format[$i]) {
case 'C':
if (!is_int($element)) {
throw new \InvalidArgumentException('Bytes must be represented as an integer between 0 and 255, inclusive.');
}
$result .= pack('C', $element);
break;
case 'b':
if (!is_bool($element)) {
throw new \InvalidArgumentException('A boolean parameter was expected.');
}
$result .= $element ? "\1" : "\0";
break;
case 'Q':
if (!is_int($element) && !is_float($element)) {
throw new \InvalidArgumentException('An integer was expected.');
}
// 4294967296 == 1 << 32
$result .= pack('NN', $element / 4294967296, $element);
break;
case 'N':
if (is_float($element)) {
$element = (int) $element;
}
if (!is_int($element)) {
throw new \InvalidArgumentException('An integer was expected.');
}
$result .= pack('N', $element);
break;
case 's':
if (!self::is_stringable($element)) {
throw new \InvalidArgumentException('A string was expected.');
}
$result .= pack('Na*', strlen($element), $element);
break;
case 'i':
if (!$element instanceof BigInteger && !$element instanceof FiniteField\Integer) {
throw new \InvalidArgumentException('A phpseclib3\Math\BigInteger or phpseclib3\Math\Common\FiniteField\Integer object was expected.');
}
$element = $element->toBytes(true);
$result .= pack('Na*', strlen($element), $element);
break;
case 'L':
if (!is_array($element)) {
throw new \InvalidArgumentException('An array was expected.');
}
$element = implode(',', $element);
$result .= pack('Na*', strlen($element), $element);
break;
default:
throw new \InvalidArgumentException('$format contains an invalid character');
}
}
return $result;
}
/**
* Expand a pack string
*
* Converts C5 to CCCCC, for example.
*
* @param string $format
* @return string
*/
private static function formatPack($format)
{
$parts = preg_split('#(\d+)#', $format, -1, PREG_SPLIT_DELIM_CAPTURE);
$format = '';
for ($i = 1; $i < count($parts); $i += 2) {
$format .= substr($parts[$i - 1], 0, -1) . str_repeat(substr($parts[$i - 1], -1), $parts[$i]);
}
$format .= $parts[$i - 1];
return $format;
}
/**
* Convert binary data into bits
*
* bin2hex / hex2bin refer to base-256 encoded data as binary, whilst
* decbin / bindec refer to base-2 encoded data as binary. For the purposes
* of this function, bin refers to base-256 encoded data whilst bits refers
* to base-2 encoded data
*
* @param string $x
* @return string
*/
public static function bits2bin($x)
{
/*
// the pure-PHP approach is faster than the GMP approach
if (function_exists('gmp_export')) {
return strlen($x) ? gmp_export(gmp_init($x, 2)) : gmp_init(0);
}
*/
if (preg_match('#[^01]#', $x)) {
throw new \RuntimeException('The only valid characters are 0 and 1');
}
if (!defined('PHP_INT_MIN')) {
define('PHP_INT_MIN', ~PHP_INT_MAX);
}
$length = strlen($x);
if (!$length) {
return '';
}
$block_size = PHP_INT_SIZE << 3;
$pad = $block_size - ($length % $block_size);
if ($pad != $block_size) {
$x = str_repeat('0', $pad) . $x;
}
$parts = str_split($x, $block_size);
$str = '';
foreach ($parts as $part) {
$xor = $part[0] == '1' ? PHP_INT_MIN : 0;
$part[0] = '0';
$str .= pack(
PHP_INT_SIZE == 4 ? 'N' : 'J',
$xor ^ eval('return 0b' . $part . ';')
);
}
return ltrim($str, "\0");
}
/**
* Convert bits to binary data
*
* @param string $x
* @return string
*/
public static function bin2bits($x, $trim = true)
{
/*
// the pure-PHP approach is slower than the GMP approach BUT
// i want to the pure-PHP version to be easily unit tested as well
if (function_exists('gmp_import')) {
return gmp_strval(gmp_import($x), 2);
}
*/
$len = strlen($x);
$mod = $len % PHP_INT_SIZE;
if ($mod) {
$x = str_pad($x, $len + PHP_INT_SIZE - $mod, "\0", STR_PAD_LEFT);
}
$bits = '';
if (PHP_INT_SIZE == 4) {
$digits = unpack('N*', $x);
foreach ($digits as $digit) {
$bits .= sprintf('%032b', $digit);
}
} else {
$digits = unpack('J*', $x);
foreach ($digits as $digit) {
$bits .= sprintf('%064b', $digit);
}
}
return $trim ? ltrim($bits, '0') : $bits;
}
/**
* Switch Endianness Bit Order
*
* @param string $x
* @return string
*/
public static function switchEndianness($x)
{
$r = '';
for ($i = strlen($x) - 1; $i >= 0; $i--) {
$b = ord($x[$i]);
if (PHP_INT_SIZE === 8) {
// 3 operations
// from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64BitsDiv
$r .= chr((($b * 0x0202020202) & 0x010884422010) % 1023);
} else {
// 7 operations
// from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits
$p1 = ($b * 0x0802) & 0x22110;
$p2 = ($b * 0x8020) & 0x88440;
$r .= chr(
(($p1 | $p2) * 0x10101) >> 16
);
}
}
return $r;
}
/**
* Increment the current string
*
* @param string $var
* @return string
*/
public static function increment_str(&$var)
{
if (function_exists('sodium_increment')) {
$var = strrev($var);
sodium_increment($var);
$var = strrev($var);
return $var;
}
for ($i = 4; $i <= strlen($var); $i += 4) {
$temp = substr($var, -$i, 4);
switch ($temp) {
case "\xFF\xFF\xFF\xFF":
$var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4);
break;
case "\x7F\xFF\xFF\xFF":
$var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4);
return $var;
default:
$temp = unpack('Nnum', $temp);
$var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4);
return $var;
}
}
$remainder = strlen($var) % 4;
if ($remainder == 0) {
return $var;
}
$temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT));
$temp = substr(pack('N', $temp['num'] + 1), -$remainder);
$var = substr_replace($var, $temp, 0, $remainder);
return $var;
}
/**
* Find whether the type of a variable is string (or could be converted to one)
*
* @param mixed $var
* @return bool
* @psalm-assert-if-true string|\Stringable $var
*/
public static function is_stringable($var)
{
return is_string($var) || (is_object($var) && method_exists($var, '__toString'));
}
/**
* Constant Time Base64-decoding
*
* ParagoneIE\ConstantTime doesn't use libsodium if it's available so we'll do so
* ourselves. see https://github.com/paragonie/constant_time_encoding/issues/39
*
* @param string $data
* @return string
*/
public static function base64_decode($data)
{
return function_exists('sodium_base642bin') ?
sodium_base642bin($data, SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING, '=') :
Base64::decode($data);
}
/**
* Constant Time Base64-decoding (URL safe)
*
* @param string $data
* @return string
*/
public static function base64url_decode($data)
{
// return self::base64_decode(str_replace(['-', '_'], ['+', '/'], $data));
return function_exists('sodium_base642bin') ?
sodium_base642bin($data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, '=') :
Base64UrlSafe::decode($data);
}
/**
* Constant Time Base64-encoding
*
* @param string $data
* @return string
*/
public static function base64_encode($data)
{
return function_exists('sodium_bin2base64') ?
sodium_bin2base64($data, SODIUM_BASE64_VARIANT_ORIGINAL) :
Base64::encode($data);
}
/**
* Constant Time Base64-encoding (URL safe)
*
* @param string $data
* @return string
*/
public static function base64url_encode($data)
{
// return str_replace(['+', '/'], ['-', '_'], self::base64_encode($data));
return function_exists('sodium_bin2base64') ?
sodium_bin2base64($data, SODIUM_BASE64_VARIANT_URLSAFE) :
Base64UrlSafe::encode($data);
}
/**
* Constant Time Hex Decoder
*
* @param string $data
* @return string
*/
public static function hex2bin($data)
{
return function_exists('sodium_hex2bin') ?
sodium_hex2bin($data) :
Hex::decode($data);
}
/**
* Constant Time Hex Encoder
*
* @param string $data
* @return string
*/
public static function bin2hex($data)
{
return function_exists('sodium_bin2hex') ?
sodium_bin2hex($data) :
Hex::encode($data);
}
}
<?php
/**
* Pure-PHP implementation of AES.
*
* Uses mcrypt, if available/possible, and an internal implementation, otherwise.
*
* PHP version 5
*
* NOTE: Since AES.php is (for compatibility and phpseclib-historical reasons) virtually
* just a wrapper to Rijndael.php you may consider using Rijndael.php instead of
* to save one include_once().
*
* If {@link self::setKeyLength() setKeyLength()} isn't called, it'll be calculated from
* {@link self::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's 136-bits
* it'll be null-padded to 192-bits and 192 bits will be the key length until {@link self::setKey() setKey()}
* is called, again, at which point, it'll be recalculated.
*
* Since \phpseclib3\Crypt\AES extends \phpseclib3\Crypt\Rijndael, some functions are available to be called that, in the context of AES, don't
* make a whole lot of sense. {@link self::setBlockLength() setBlockLength()}, for instance. Calling that function,
* however possible, won't do anything (AES has a fixed block length whereas Rijndael has a variable one).
*
* Here's a short example of how to use this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $aes = new \phpseclib3\Crypt\AES('ctr');
*
* $aes->setKey('abcdefghijklmnop');
*
* $size = 10 * 1024;
* $plaintext = '';
* for ($i = 0; $i < $size; $i++) {
* $plaintext.= 'a';
* }
*
* echo $aes->decrypt($aes->encrypt($plaintext));
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2008 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt;
/**
* Pure-PHP implementation of AES.
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class AES extends Rijndael
{
/**
* Dummy function
*
* Since \phpseclib3\Crypt\AES extends \phpseclib3\Crypt\Rijndael, this function is, technically, available, but it doesn't do anything.
*
* @see \phpseclib3\Crypt\Rijndael::setBlockLength()
* @param int $length
* @throws \BadMethodCallException anytime it's called
*/
public function setBlockLength($length)
{
throw new \BadMethodCallException('The block length cannot be set for AES.');
}
/**
* Sets the key length
*
* Valid key lengths are 128, 192, and 256. Set the link to bool(false) to disable a fixed key length
*
* @see \phpseclib3\Crypt\Rijndael:setKeyLength()
* @param int $length
* @throws \LengthException if the key length isn't supported
*/
public function setKeyLength($length)
{
switch ($length) {
case 128:
case 192:
case 256:
break;
default:
throw new \LengthException('Key of size ' . $length . ' not supported by this algorithm. Only keys of sizes 128, 192 or 256 supported');
}
parent::setKeyLength($length);
}
/**
* Sets the key.
*
* Rijndael supports five different key lengths, AES only supports three.
*
* @see \phpseclib3\Crypt\Rijndael:setKey()
* @see setKeyLength()
* @param string $key
* @throws \LengthException if the key length isn't supported
*/
public function setKey($key)
{
switch (strlen($key)) {
case 16:
case 24:
case 32:
break;
default:
throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported');
}
parent::setKey($key);
}
}
<?php
/**
* Pure-PHP implementation of Blowfish.
*
* Uses mcrypt, if available, and an internal implementation, otherwise.
*
* PHP version 5
*
* Useful resources are as follows:
*
* - {@link http://en.wikipedia.org/wiki/Blowfish_(cipher) Wikipedia description of Blowfish}
*
* # An overview of bcrypt vs Blowfish
*
* OpenSSH private keys use a customized version of bcrypt. Specifically, instead of
* encrypting OrpheanBeholderScryDoubt 64 times OpenSSH's bcrypt variant encrypts
* OxychromaticBlowfishSwatDynamite 64 times. so we can't use crypt().
*
* bcrypt is basically Blowfish but instead of performing the key expansion once it performs
* the expansion 129 times for each round, with the first key expansion interleaving the salt
* and password. This renders OpenSSL unusable and forces us to use a pure-PHP implementation
* of blowfish.
*
* # phpseclib's four different _encryptBlock() implementations
*
* When using Blowfish as an encryption algorithm, _encryptBlock() is called 9 + 512 +
* (the number of blocks in the plaintext) times.
*
* Each of the first 9 calls to _encryptBlock() modify the P-array. Each of the next 512
* calls modify the S-boxes. The remaining _encryptBlock() calls operate on the plaintext to
* produce the ciphertext. In the pure-PHP implementation of Blowfish these remaining
* _encryptBlock() calls are highly optimized through the use of eval(). Among other things,
* P-array lookups are eliminated by hard-coding the key-dependent P-array values, and thus we
* have explained 2 of the 4 different _encryptBlock() implementations.
*
* With bcrypt things are a bit different. _encryptBlock() is called 1,079,296 times,
* assuming 16 rounds (which is what OpenSSH's bcrypt defaults to). The eval()-optimized
* _encryptBlock() isn't as beneficial because the P-array values are not constant. Well, they
* are constant, but only for, at most, 777 _encryptBlock() calls, which is equivalent to ~6KB
* of data. The average length of back to back _encryptBlock() calls with a fixed P-array is
* 514.12, which is ~4KB of data. Creating an eval()-optimized _encryptBlock() has an upfront
* cost, which is CPU dependent and is probably not going to be worth it for just ~4KB of
* data. Conseqeuently, bcrypt does not benefit from the eval()-optimized _encryptBlock().
*
* The regular _encryptBlock() does unpack() and pack() on every call, as well, and that can
* begin to add up after one million function calls.
*
* In theory, one might think that it might be beneficial to rewrite all block ciphers so
* that, instead of passing strings to _encryptBlock(), you convert the string to an array of
* integers and then pass successive subarrays of that array to _encryptBlock. This, however,
* kills PHP's memory use. Like let's say you have a 1MB long string. After doing
* $in = str_repeat('a', 1024 * 1024); PHP's memory utilization jumps up by ~1MB. After doing
* $blocks = str_split($in, 4); it jumps up by an additional ~16MB. After
* $blocks = array_map(fn($x) => unpack('N*', $x), $blocks); it jumps up by an additional
* ~90MB, yielding a 106x increase in memory usage. Consequently, it bcrypt calls a different
* _encryptBlock() then the regular Blowfish does. That said, the Blowfish _encryptBlock() is
* basically just a thin wrapper around the bcrypt _encryptBlock(), so there's that.
*
* This explains 3 of the 4 _encryptBlock() implementations. the last _encryptBlock()
* implementation can best be understood by doing Ctrl + F and searching for where
* self::$use_reg_intval is defined.
*
* # phpseclib's three different _setupKey() implementations
*
* Every bcrypt round is the equivalent of encrypting 512KB of data. Since OpenSSH uses 16
* rounds by default that's ~8MB of data that's essentially being encrypted whenever
* you use bcrypt. That's a lot of data, however, bcrypt operates within tighter constraints
* than regular Blowfish, so we can use that to our advantage. In particular, whereas Blowfish
* supports variable length keys, in bcrypt, the initial "key" is the sha512 hash of the
* password. sha512 hashes are 512 bits or 64 bytes long and thus the bcrypt keys are of a
* fixed length whereas Blowfish keys are not of a fixed length.
*
* bcrypt actually has two different key expansion steps. The first one (expandstate) is
* constantly XOR'ing every _encryptBlock() parameter against the salt prior _encryptBlock()'s
* being called. The second one (expand0state) is more similar to Blowfish's _setupKey()
* but it can still use the fixed length key optimization discussed above and can do away with
* the pack() / unpack() calls.
*
* I suppose _setupKey() could be made to be a thin wrapper around expandstate() but idk it's
* just a lot of work for very marginal benefits as _setupKey() is only called once for
* regular Blowfish vs the 128 times it's called --per round-- with bcrypt.
*
* # blowfish + bcrypt in the same class
*
* Altho there's a lot of Blowfish code that bcrypt doesn't re-use, bcrypt does re-use the
* initial S-boxes, the initial P-array and the int-only _encryptBlock() implementation.
*
* # Credit
*
* phpseclib's bcrypt implementation is based losely off of OpenSSH's implementation:
*
* https://github.com/openssh/openssh-portable/blob/master/openbsd-compat/bcrypt_pbkdf.c
*
* Here's a short example of how to use this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
* $blowfish = new \phpseclib3\Crypt\Blowfish('ctr');
*
* $blowfish->setKey('12345678901234567890123456789012');
*
* $plaintext = str_repeat('a', 1024);
*
* echo $blowfish->decrypt($blowfish->encrypt($plaintext));
* ?>
* </code>
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt;
use phpseclib3\Crypt\Common\BlockCipher;
/**
* Pure-PHP implementation of Blowfish.
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
*/
class Blowfish extends BlockCipher
{
/**
* Block Length of the cipher
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::block_size
* @var int
*/
protected $block_size = 8;
/**
* The mcrypt specific name of the cipher
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::cipher_name_mcrypt
* @var string
*/
protected $cipher_name_mcrypt = 'blowfish';
/**
* Optimizing value while CFB-encrypting
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::cfb_init_len
* @var int
*/
protected $cfb_init_len = 500;
/**
* The fixed subkeys boxes ($sbox0 - $sbox3) with 256 entries each
*
* S-Box 0
*
* @var array
*/
private static $sbox0 = [
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
];
/**
* S-Box 1
*
* @var array
*/
private static $sbox1 = [
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
];
/**
* S-Box 2
*
* @var array
*/
private static $sbox2 = [
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
];
/**
* S-Box 3
*
* @var array
*/
private static $sbox3 = [
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
];
/**
* P-Array consists of 18 32-bit subkeys
*
* @var array
*/
private static $parray = [
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b
];
/**
* The BCTX-working Array
*
* Holds the expanded key [p] and the key-depended s-boxes [sb]
*
* @var array
*/
private $bctx;
/**
* Holds the last used key
*
* @var array
*/
private $kl;
/**
* The Key Length (in bytes)
* {@internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $Nk
* because the encryption / decryption / key schedule creation requires this number and not $key_length. We could
* derive this from $key_length or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
* of that, we'll just precompute it once.}
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::setKeyLength()
* @var int
*/
protected $key_length = 16;
/**
* Default Constructor.
*
* @param string $mode
* @throws \InvalidArgumentException if an invalid / unsupported mode is provided
*/
public function __construct($mode)
{
parent::__construct($mode);
if ($this->mode == self::MODE_STREAM) {
throw new \InvalidArgumentException('Block ciphers cannot be ran in stream mode');
}
}
/**
* Sets the key length.
*
* Key lengths can be between 32 and 448 bits.
*
* @param int $length
*/
public function setKeyLength($length)
{
if ($length < 32 || $length > 448) {
throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes between 32 and 448 bits are supported');
}
$this->key_length = $length >> 3;
parent::setKeyLength($length);
}
/**
* Test for engine validity
*
* This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
* @param int $engine
* @return bool
*/
protected function isValidEngineHelper($engine)
{
if ($engine == self::ENGINE_OPENSSL) {
if ($this->key_length < 16) {
return false;
}
// quoting https://www.openssl.org/news/openssl-3.0-notes.html, OpenSSL 3.0.1
// "Moved all variations of the EVP ciphers CAST5, BF, IDEA, SEED, RC2, RC4, RC5, and DES to the legacy provider"
// in theory openssl_get_cipher_methods() should catch this but, on GitHub Actions, at least, it does not
if (version_compare(preg_replace('#OpenSSL (\d+\.\d+\.\d+) .*#', '$1', OPENSSL_VERSION_TEXT), '3.0.1', '>=')) {
return false;
}
$this->cipher_name_openssl_ecb = 'bf-ecb';
$this->cipher_name_openssl = 'bf-' . $this->openssl_translate_mode();
}
return parent::isValidEngineHelper($engine);
}
/**
* Setup the key (expansion)
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::_setupKey()
*/
protected function setupKey()
{
if (isset($this->kl['key']) && $this->key === $this->kl['key']) {
// already expanded
return;
}
$this->kl = ['key' => $this->key];
/* key-expanding p[] and S-Box building sb[] */
$this->bctx = [
'p' => [],
'sb' => [
self::$sbox0,
self::$sbox1,
self::$sbox2,
self::$sbox3
]
];
// unpack binary string in unsigned chars
$key = array_values(unpack('C*', $this->key));
$keyl = count($key);
// with bcrypt $keyl will always be 16 (because the key is the sha512 of the key you provide)
for ($j = 0, $i = 0; $i < 18; ++$i) {
// xor P1 with the first 32-bits of the key, xor P2 with the second 32-bits ...
for ($data = 0, $k = 0; $k < 4; ++$k) {
$data = ($data << 8) | $key[$j];
if (++$j >= $keyl) {
$j = 0;
}
}
$this->bctx['p'][] = self::$parray[$i] ^ intval($data);
}
// encrypt the zero-string, replace P1 and P2 with the encrypted data,
// encrypt P3 and P4 with the new P1 and P2, do it with all P-array and subkeys
$data = "\0\0\0\0\0\0\0\0";
for ($i = 0; $i < 18; $i += 2) {
list($l, $r) = array_values(unpack('N*', $data = $this->encryptBlock($data)));
$this->bctx['p'][$i ] = $l;
$this->bctx['p'][$i + 1] = $r;
}
for ($i = 0; $i < 4; ++$i) {
for ($j = 0; $j < 256; $j += 2) {
list($l, $r) = array_values(unpack('N*', $data = $this->encryptBlock($data)));
$this->bctx['sb'][$i][$j ] = $l;
$this->bctx['sb'][$i][$j + 1] = $r;
}
}
}
/**
* Initialize Static Variables
*/
protected static function initialize_static_variables()
{
if (is_float(self::$sbox2[0])) {
self::$sbox0 = array_map('intval', self::$sbox0);
self::$sbox1 = array_map('intval', self::$sbox1);
self::$sbox2 = array_map('intval', self::$sbox2);
self::$sbox3 = array_map('intval', self::$sbox3);
self::$parray = array_map('intval', self::$parray);
}
parent::initialize_static_variables();
}
/**
* bcrypt
*
* @param string $sha2pass
* @param string $sha2salt
* @access private
* @return string
*/
private static function bcrypt_hash($sha2pass, $sha2salt)
{
$p = self::$parray;
$sbox0 = self::$sbox0;
$sbox1 = self::$sbox1;
$sbox2 = self::$sbox2;
$sbox3 = self::$sbox3;
$cdata = array_values(unpack('N*', 'OxychromaticBlowfishSwatDynamite'));
$sha2pass = array_values(unpack('N*', $sha2pass));
$sha2salt = array_values(unpack('N*', $sha2salt));
self::expandstate($sha2salt, $sha2pass, $sbox0, $sbox1, $sbox2, $sbox3, $p);
for ($i = 0; $i < 64; $i++) {
self::expand0state($sha2salt, $sbox0, $sbox1, $sbox2, $sbox3, $p);
self::expand0state($sha2pass, $sbox0, $sbox1, $sbox2, $sbox3, $p);
}
for ($i = 0; $i < 64; $i++) {
for ($j = 0; $j < 8; $j += 2) { // count($cdata) == 8
list($cdata[$j], $cdata[$j + 1]) = self::encryptBlockHelperFast($cdata[$j], $cdata[$j + 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
}
}
return pack('L*', ...$cdata);
}
/**
* Performs OpenSSH-style bcrypt
*
* @param string $pass
* @param string $salt
* @param int $keylen
* @param int $rounds
* @access public
* @return string
*/
public static function bcrypt_pbkdf($pass, $salt, $keylen, $rounds)
{
self::initialize_static_variables();
if (PHP_INT_SIZE == 4) {
throw new \RuntimeException('bcrypt is far too slow to be practical on 32-bit versions of PHP');
}
$sha2pass = hash('sha512', $pass, true);
$results = [];
$count = 1;
while (32 * count($results) < $keylen) {
$countsalt = $salt . pack('N', $count++);
$sha2salt = hash('sha512', $countsalt, true);
$out = $tmpout = self::bcrypt_hash($sha2pass, $sha2salt);
for ($i = 1; $i < $rounds; $i++) {
$sha2salt = hash('sha512', $tmpout, true);
$tmpout = self::bcrypt_hash($sha2pass, $sha2salt);
$out ^= $tmpout;
}
$results[] = $out;
}
$output = '';
for ($i = 0; $i < 32; $i++) {
foreach ($results as $result) {
$output .= $result[$i];
}
}
return substr($output, 0, $keylen);
}
/**
* Key expansion without salt
*
* @access private
* @param int[] $key
* @param int[] $sbox0
* @param int[] $sbox1
* @param int[] $sbox2
* @param int[] $sbox3
* @param int[] $p
* @see self::_bcrypt_hash()
*/
private static function expand0state(array $key, array &$sbox0, array &$sbox1, array &$sbox2, array &$sbox3, array &$p)
{
// expand0state is basically the same thing as this:
//return self::expandstate(array_fill(0, 16, 0), $key);
// but this separate function eliminates a bunch of XORs and array lookups
$p = [
$p[0] ^ $key[0],
$p[1] ^ $key[1],
$p[2] ^ $key[2],
$p[3] ^ $key[3],
$p[4] ^ $key[4],
$p[5] ^ $key[5],
$p[6] ^ $key[6],
$p[7] ^ $key[7],
$p[8] ^ $key[8],
$p[9] ^ $key[9],
$p[10] ^ $key[10],
$p[11] ^ $key[11],
$p[12] ^ $key[12],
$p[13] ^ $key[13],
$p[14] ^ $key[14],
$p[15] ^ $key[15],
$p[16] ^ $key[0],
$p[17] ^ $key[1]
];
// @codingStandardsIgnoreStart
list( $p[0], $p[1]) = self::encryptBlockHelperFast( 0, 0, $sbox0, $sbox1, $sbox2, $sbox3, $p);
list( $p[2], $p[3]) = self::encryptBlockHelperFast($p[ 0], $p[ 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list( $p[4], $p[5]) = self::encryptBlockHelperFast($p[ 2], $p[ 3], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list( $p[6], $p[7]) = self::encryptBlockHelperFast($p[ 4], $p[ 5], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list( $p[8], $p[9]) = self::encryptBlockHelperFast($p[ 6], $p[ 7], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list($p[10], $p[11]) = self::encryptBlockHelperFast($p[ 8], $p[ 9], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list($p[12], $p[13]) = self::encryptBlockHelperFast($p[10], $p[11], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list($p[14], $p[15]) = self::encryptBlockHelperFast($p[12], $p[13], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list($p[16], $p[17]) = self::encryptBlockHelperFast($p[14], $p[15], $sbox0, $sbox1, $sbox2, $sbox3, $p);
// @codingStandardsIgnoreEnd
list($sbox0[0], $sbox0[1]) = self::encryptBlockHelperFast($p[16], $p[17], $sbox0, $sbox1, $sbox2, $sbox3, $p);
for ($i = 2; $i < 256; $i += 2) {
list($sbox0[$i], $sbox0[$i + 1]) = self::encryptBlockHelperFast($sbox0[$i - 2], $sbox0[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
}
list($sbox1[0], $sbox1[1]) = self::encryptBlockHelperFast($sbox0[254], $sbox0[255], $sbox0, $sbox1, $sbox2, $sbox3, $p);
for ($i = 2; $i < 256; $i += 2) {
list($sbox1[$i], $sbox1[$i + 1]) = self::encryptBlockHelperFast($sbox1[$i - 2], $sbox1[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
}
list($sbox2[0], $sbox2[1]) = self::encryptBlockHelperFast($sbox1[254], $sbox1[255], $sbox0, $sbox1, $sbox2, $sbox3, $p);
for ($i = 2; $i < 256; $i += 2) {
list($sbox2[$i], $sbox2[$i + 1]) = self::encryptBlockHelperFast($sbox2[$i - 2], $sbox2[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
}
list($sbox3[0], $sbox3[1]) = self::encryptBlockHelperFast($sbox2[254], $sbox2[255], $sbox0, $sbox1, $sbox2, $sbox3, $p);
for ($i = 2; $i < 256; $i += 2) {
list($sbox3[$i], $sbox3[$i + 1]) = self::encryptBlockHelperFast($sbox3[$i - 2], $sbox3[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
}
}
/**
* Key expansion with salt
*
* @access private
* @param int[] $data
* @param int[] $key
* @param int[] $sbox0
* @param int[] $sbox1
* @param int[] $sbox2
* @param int[] $sbox3
* @param int[] $p
* @see self::_bcrypt_hash()
*/
private static function expandstate(array $data, array $key, array &$sbox0, array &$sbox1, array &$sbox2, array &$sbox3, array &$p)
{
$p = [
$p[0] ^ $key[0],
$p[1] ^ $key[1],
$p[2] ^ $key[2],
$p[3] ^ $key[3],
$p[4] ^ $key[4],
$p[5] ^ $key[5],
$p[6] ^ $key[6],
$p[7] ^ $key[7],
$p[8] ^ $key[8],
$p[9] ^ $key[9],
$p[10] ^ $key[10],
$p[11] ^ $key[11],
$p[12] ^ $key[12],
$p[13] ^ $key[13],
$p[14] ^ $key[14],
$p[15] ^ $key[15],
$p[16] ^ $key[0],
$p[17] ^ $key[1]
];
// @codingStandardsIgnoreStart
list( $p[0], $p[1]) = self::encryptBlockHelperFast($data[ 0] , $data[ 1] , $sbox0, $sbox1, $sbox2, $sbox3, $p);
list( $p[2], $p[3]) = self::encryptBlockHelperFast($data[ 2] ^ $p[ 0], $data[ 3] ^ $p[ 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list( $p[4], $p[5]) = self::encryptBlockHelperFast($data[ 4] ^ $p[ 2], $data[ 5] ^ $p[ 3], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list( $p[6], $p[7]) = self::encryptBlockHelperFast($data[ 6] ^ $p[ 4], $data[ 7] ^ $p[ 5], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list( $p[8], $p[9]) = self::encryptBlockHelperFast($data[ 8] ^ $p[ 6], $data[ 9] ^ $p[ 7], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list($p[10], $p[11]) = self::encryptBlockHelperFast($data[10] ^ $p[ 8], $data[11] ^ $p[ 9], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list($p[12], $p[13]) = self::encryptBlockHelperFast($data[12] ^ $p[10], $data[13] ^ $p[11], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list($p[14], $p[15]) = self::encryptBlockHelperFast($data[14] ^ $p[12], $data[15] ^ $p[13], $sbox0, $sbox1, $sbox2, $sbox3, $p);
list($p[16], $p[17]) = self::encryptBlockHelperFast($data[ 0] ^ $p[14], $data[ 1] ^ $p[15], $sbox0, $sbox1, $sbox2, $sbox3, $p);
// @codingStandardsIgnoreEnd
list($sbox0[0], $sbox0[1]) = self::encryptBlockHelperFast($data[2] ^ $p[16], $data[3] ^ $p[17], $sbox0, $sbox1, $sbox2, $sbox3, $p);
for ($i = 2, $j = 4; $i < 256; $i += 2, $j = ($j + 2) % 16) { // instead of 16 maybe count($data) would be better?
list($sbox0[$i], $sbox0[$i + 1]) = self::encryptBlockHelperFast($data[$j] ^ $sbox0[$i - 2], $data[$j + 1] ^ $sbox0[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
}
list($sbox1[0], $sbox1[1]) = self::encryptBlockHelperFast($data[2] ^ $sbox0[254], $data[3] ^ $sbox0[255], $sbox0, $sbox1, $sbox2, $sbox3, $p);
for ($i = 2, $j = 4; $i < 256; $i += 2, $j = ($j + 2) % 16) {
list($sbox1[$i], $sbox1[$i + 1]) = self::encryptBlockHelperFast($data[$j] ^ $sbox1[$i - 2], $data[$j + 1] ^ $sbox1[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
}
list($sbox2[0], $sbox2[1]) = self::encryptBlockHelperFast($data[2] ^ $sbox1[254], $data[3] ^ $sbox1[255], $sbox0, $sbox1, $sbox2, $sbox3, $p);
for ($i = 2, $j = 4; $i < 256; $i += 2, $j = ($j + 2) % 16) {
list($sbox2[$i], $sbox2[$i + 1]) = self::encryptBlockHelperFast($data[$j] ^ $sbox2[$i - 2], $data[$j + 1] ^ $sbox2[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
}
list($sbox3[0], $sbox3[1]) = self::encryptBlockHelperFast($data[2] ^ $sbox2[254], $data[3] ^ $sbox2[255], $sbox0, $sbox1, $sbox2, $sbox3, $p);
for ($i = 2, $j = 4; $i < 256; $i += 2, $j = ($j + 2) % 16) {
list($sbox3[$i], $sbox3[$i + 1]) = self::encryptBlockHelperFast($data[$j] ^ $sbox3[$i - 2], $data[$j + 1] ^ $sbox3[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
}
}
/**
* Encrypts a block
*
* @param string $in
* @return string
*/
protected function encryptBlock($in)
{
$p = $this->bctx['p'];
// extract($this->bctx['sb'], EXTR_PREFIX_ALL, 'sb'); // slower
$sb_0 = $this->bctx['sb'][0];
$sb_1 = $this->bctx['sb'][1];
$sb_2 = $this->bctx['sb'][2];
$sb_3 = $this->bctx['sb'][3];
$in = unpack('N*', $in);
$l = $in[1];
$r = $in[2];
list($r, $l) = PHP_INT_SIZE == 4 ?
self::encryptBlockHelperSlow($l, $r, $sb_0, $sb_1, $sb_2, $sb_3, $p) :
self::encryptBlockHelperFast($l, $r, $sb_0, $sb_1, $sb_2, $sb_3, $p);
return pack("N*", $r, $l);
}
/**
* Fast helper function for block encryption
*
* @access private
* @param int $x0
* @param int $x1
* @param int[] $sbox0
* @param int[] $sbox1
* @param int[] $sbox2
* @param int[] $sbox3
* @param int[] $p
* @return int[]
*/
private static function encryptBlockHelperFast($x0, $x1, array $sbox0, array $sbox1, array $sbox2, array $sbox3, array $p)
{
$x0 ^= $p[0];
$x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[1];
$x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[2];
$x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[3];
$x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[4];
$x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[5];
$x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[6];
$x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[7];
$x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[8];
$x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[9];
$x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[10];
$x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[11];
$x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[12];
$x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[13];
$x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[14];
$x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[15];
$x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[16];
return [$x1 & 0xFFFFFFFF ^ $p[17], $x0 & 0xFFFFFFFF];
}
/**
* Slow helper function for block encryption
*
* @access private
* @param int $x0
* @param int $x1
* @param int[] $sbox0
* @param int[] $sbox1
* @param int[] $sbox2
* @param int[] $sbox3
* @param int[] $p
* @return int[]
*/
private static function encryptBlockHelperSlow($x0, $x1, array $sbox0, array $sbox1, array $sbox2, array $sbox3, array $p)
{
// -16777216 == intval(0xFF000000) on 32-bit PHP installs
$x0 ^= $p[0];
$x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[1];
$x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[2];
$x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[3];
$x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[4];
$x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[5];
$x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[6];
$x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[7];
$x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[8];
$x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[9];
$x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[10];
$x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[11];
$x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[12];
$x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[13];
$x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[14];
$x1 ^= self::safe_intval((self::safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[15];
$x0 ^= self::safe_intval((self::safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[16];
return [$x1 ^ $p[17], $x0];
}
/**
* Decrypts a block
*
* @param string $in
* @return string
*/
protected function decryptBlock($in)
{
$p = $this->bctx['p'];
$sb_0 = $this->bctx['sb'][0];
$sb_1 = $this->bctx['sb'][1];
$sb_2 = $this->bctx['sb'][2];
$sb_3 = $this->bctx['sb'][3];
$in = unpack('N*', $in);
$l = $in[1];
$r = $in[2];
for ($i = 17; $i > 2; $i -= 2) {
$l ^= $p[$i];
$r ^= self::safe_intval((self::safe_intval($sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]) ^
$sb_2[$l >> 8 & 0xff]) +
$sb_3[$l & 0xff]);
$r ^= $p[$i - 1];
$l ^= self::safe_intval((self::safe_intval($sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]) ^
$sb_2[$r >> 8 & 0xff]) +
$sb_3[$r & 0xff]);
}
return pack('N*', $r ^ $p[0], $l ^ $p[1]);
}
/**
* Setup the performance-optimized function for de/encrypt()
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::_setupInlineCrypt()
*/
protected function setupInlineCrypt()
{
$p = $this->bctx['p'];
$init_crypt = '
static $sb_0, $sb_1, $sb_2, $sb_3;
if (!$sb_0) {
$sb_0 = $this->bctx["sb"][0];
$sb_1 = $this->bctx["sb"][1];
$sb_2 = $this->bctx["sb"][2];
$sb_3 = $this->bctx["sb"][3];
}
';
$safeint = self::safe_intval_inline();
// Generating encrypt code:
$encrypt_block = '
$in = unpack("N*", $in);
$l = $in[1];
$r = $in[2];
';
for ($i = 0; $i < 16; $i += 2) {
$encrypt_block .= '
$l^= ' . $p[$i] . ';
$r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]') . ' ^
$sb_2[$l >> 8 & 0xff]) +
$sb_3[$l & 0xff]') . ';
$r^= ' . $p[$i + 1] . ';
$l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]') . ' ^
$sb_2[$r >> 8 & 0xff]) +
$sb_3[$r & 0xff]') . ';
';
}
$encrypt_block .= '
$in = pack("N*",
$r ^ ' . $p[17] . ',
$l ^ ' . $p[16] . '
);
';
// Generating decrypt code:
$decrypt_block = '
$in = unpack("N*", $in);
$l = $in[1];
$r = $in[2];
';
for ($i = 17; $i > 2; $i -= 2) {
$decrypt_block .= '
$l^= ' . $p[$i] . ';
$r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]') . ' ^
$sb_2[$l >> 8 & 0xff]) +
$sb_3[$l & 0xff]') . ';
$r^= ' . $p[$i - 1] . ';
$l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]') . ' ^
$sb_2[$r >> 8 & 0xff]) +
$sb_3[$r & 0xff]') . ';
';
}
$decrypt_block .= '
$in = pack("N*",
$r ^ ' . $p[0] . ',
$l ^ ' . $p[1] . '
);
';
$this->inline_crypt = $this->createInlineCryptFunction(
[
'init_crypt' => $init_crypt,
'init_encrypt' => '',
'init_decrypt' => '',
'encrypt_block' => $encrypt_block,
'decrypt_block' => $decrypt_block
]
);
}
}
<?php
/**
* Pure-PHP implementation of ChaCha20.
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2019 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt;
use phpseclib3\Exception\BadDecryptionException;
use phpseclib3\Exception\InsufficientSetupException;
/**
* Pure-PHP implementation of ChaCha20.
*
* @author Jim Wigginton <terrafrost@php.net>
*/
class ChaCha20 extends Salsa20
{
/**
* The OpenSSL specific name of the cipher
*
* @var string
*/
protected $cipher_name_openssl = 'chacha20';
/**
* Test for engine validity
*
* This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
* @param int $engine
* @return bool
*/
protected function isValidEngineHelper($engine)
{
switch ($engine) {
case self::ENGINE_LIBSODIUM:
// PHP 7.2.0 (30 Nov 2017) added support for libsodium
// we could probably make it so that if $this->counter == 0 then the first block would be done with either OpenSSL
// or PHP and then subsequent blocks would then be done with libsodium but idk - it's not a high priority atm
// we could also make it so that if $this->counter == 0 and $this->continuousBuffer then do the first string
// with libsodium and subsequent strings with openssl or pure-PHP but again not a high priority
return function_exists('sodium_crypto_aead_chacha20poly1305_ietf_encrypt') &&
$this->key_length == 32 &&
(($this->usePoly1305 && !isset($this->poly1305Key) && $this->counter == 0) || $this->counter == 1) &&
!$this->continuousBuffer;
case self::ENGINE_OPENSSL:
// OpenSSL 1.1.0 (released 25 Aug 2016) added support for chacha20.
// PHP didn't support OpenSSL 1.1.0 until 7.0.19 (11 May 2017)
// if you attempt to provide openssl with a 128 bit key (as opposed to a 256 bit key) openssl will null
// pad the key to 256 bits and still use the expansion constant for 256-bit keys. the fact that
// openssl treats the IV as both the counter and nonce, however, let's us use openssl in continuous mode
// whereas libsodium does not
if ($this->key_length != 32) {
return false;
}
}
return parent::isValidEngineHelper($engine);
}
/**
* Encrypts a message.
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
* @see self::crypt()
* @param string $plaintext
* @return string $ciphertext
*/
public function encrypt($plaintext)
{
$this->setup();
if ($this->engine == self::ENGINE_LIBSODIUM) {
return $this->encrypt_with_libsodium($plaintext);
}
return parent::encrypt($plaintext);
}
/**
* Decrypts a message.
*
* $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
* At least if the continuous buffer is disabled.
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
* @see self::crypt()
* @param string $ciphertext
* @return string $plaintext
*/
public function decrypt($ciphertext)
{
$this->setup();
if ($this->engine == self::ENGINE_LIBSODIUM) {
return $this->decrypt_with_libsodium($ciphertext);
}
return parent::decrypt($ciphertext);
}
/**
* Encrypts a message with libsodium
*
* @see self::encrypt()
* @param string $plaintext
* @return string $text
*/
private function encrypt_with_libsodium($plaintext)
{
$params = [$plaintext, $this->aad, $this->nonce, $this->key];
$ciphertext = strlen($this->nonce) == 8 ?
sodium_crypto_aead_chacha20poly1305_encrypt(...$params) :
sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params);
if (!$this->usePoly1305) {
return substr($ciphertext, 0, strlen($plaintext));
}
$newciphertext = substr($ciphertext, 0, strlen($plaintext));
$this->newtag = $this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12 ?
substr($ciphertext, strlen($plaintext)) :
$this->poly1305($newciphertext);
return $newciphertext;
}
/**
* Decrypts a message with libsodium
*
* @see self::decrypt()
* @param string $ciphertext
* @return string $text
*/
private function decrypt_with_libsodium($ciphertext)
{
$params = [$ciphertext, $this->aad, $this->nonce, $this->key];
if (isset($this->poly1305Key)) {
if ($this->oldtag === false) {
throw new InsufficientSetupException('Authentication Tag has not been set');
}
if ($this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12) {
$plaintext = sodium_crypto_aead_chacha20poly1305_ietf_decrypt(...$params);
$this->oldtag = false;
if ($plaintext === false) {
throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
}
return $plaintext;
}
$newtag = $this->poly1305($ciphertext);
if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) {
$this->oldtag = false;
throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
}
$this->oldtag = false;
}
$plaintext = strlen($this->nonce) == 8 ?
sodium_crypto_aead_chacha20poly1305_encrypt(...$params) :
sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params);
return substr($plaintext, 0, strlen($ciphertext));
}
/**
* Sets the nonce.
*
* @param string $nonce
*/
public function setNonce($nonce)
{
if (!is_string($nonce)) {
throw new \UnexpectedValueException('The nonce should be a string');
}
/*
from https://tools.ietf.org/html/rfc7539#page-7
"Note also that the original ChaCha had a 64-bit nonce and 64-bit
block count. We have modified this here to be more consistent with
recommendations in Section 3.2 of [RFC5116]."
*/
switch (strlen($nonce)) {
case 8: // 64 bits
case 12: // 96 bits
break;
default:
throw new \LengthException('Nonce of size ' . strlen($nonce) . ' not supported by this algorithm. Only 64-bit nonces or 96-bit nonces are supported');
}
$this->nonce = $nonce;
$this->changed = true;
$this->setEngine();
}
/**
* Setup the self::ENGINE_INTERNAL $engine
*
* (re)init, if necessary, the internal cipher $engine
*
* _setup() will be called each time if $changed === true
* typically this happens when using one or more of following public methods:
*
* - setKey()
*
* - setNonce()
*
* - First run of encrypt() / decrypt() with no init-settings
*
* @see self::setKey()
* @see self::setNonce()
* @see self::disableContinuousBuffer()
*/
protected function setup()
{
if (!$this->changed) {
return;
}
$this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter];
$this->changed = $this->nonIVChanged = false;
if ($this->nonce === false) {
throw new InsufficientSetupException('No nonce has been defined');
}
if ($this->key === false) {
throw new InsufficientSetupException('No key has been defined');
}
if ($this->usePoly1305 && !isset($this->poly1305Key)) {
$this->usingGeneratedPoly1305Key = true;
if ($this->engine == self::ENGINE_LIBSODIUM) {
return;
}
$this->createPoly1305Key();
}
$key = $this->key;
if (strlen($key) == 16) {
$constant = 'expand 16-byte k';
$key .= $key;
} else {
$constant = 'expand 32-byte k';
}
$this->p1 = $constant . $key;
$this->p2 = $this->nonce;
if (strlen($this->nonce) == 8) {
$this->p2 = "\0\0\0\0" . $this->p2;
}
}
/**
* The quarterround function
*
* @param int $a
* @param int $b
* @param int $c
* @param int $d
*/
protected static function quarterRound(&$a, &$b, &$c, &$d)
{
// in https://datatracker.ietf.org/doc/html/rfc7539#section-2.1 the addition,
// xor'ing and rotation are all on the same line so i'm keeping it on the same
// line here as well
// @codingStandardsIgnoreStart
$a+= $b; $d = self::leftRotate(intval($d) ^ intval($a), 16);
$c+= $d; $b = self::leftRotate(intval($b) ^ intval($c), 12);
$a+= $b; $d = self::leftRotate(intval($d) ^ intval($a), 8);
$c+= $d; $b = self::leftRotate(intval($b) ^ intval($c), 7);
// @codingStandardsIgnoreEnd
}
/**
* The doubleround function
*
* @param int $x0 (by reference)
* @param int $x1 (by reference)
* @param int $x2 (by reference)
* @param int $x3 (by reference)
* @param int $x4 (by reference)
* @param int $x5 (by reference)
* @param int $x6 (by reference)
* @param int $x7 (by reference)
* @param int $x8 (by reference)
* @param int $x9 (by reference)
* @param int $x10 (by reference)
* @param int $x11 (by reference)
* @param int $x12 (by reference)
* @param int $x13 (by reference)
* @param int $x14 (by reference)
* @param int $x15 (by reference)
*/
protected static function doubleRound(&$x0, &$x1, &$x2, &$x3, &$x4, &$x5, &$x6, &$x7, &$x8, &$x9, &$x10, &$x11, &$x12, &$x13, &$x14, &$x15)
{
// columnRound
static::quarterRound($x0, $x4, $x8, $x12);
static::quarterRound($x1, $x5, $x9, $x13);
static::quarterRound($x2, $x6, $x10, $x14);
static::quarterRound($x3, $x7, $x11, $x15);
// rowRound
static::quarterRound($x0, $x5, $x10, $x15);
static::quarterRound($x1, $x6, $x11, $x12);
static::quarterRound($x2, $x7, $x8, $x13);
static::quarterRound($x3, $x4, $x9, $x14);
}
/**
* The Salsa20 hash function function
*
* On my laptop this loop unrolled / function dereferenced version of parent::salsa20 encrypts 1mb of text in
* 0.65s vs the 0.85s that it takes with the parent method.
*
* If we were free to assume that the host OS would always be 64-bits then the if condition in leftRotate could
* be eliminated and we could knock this done to 0.60s.
*
* For comparison purposes, RC4 takes 0.16s and AES in CTR mode with the Eval engine takes 0.48s.
* AES in CTR mode with the PHP engine takes 1.19s. Salsa20 / ChaCha20 do not benefit as much from the Eval
* approach due to the fact that there are a lot less variables to de-reference, fewer loops to unroll, etc
*
* @param string $x
*/
protected static function salsa20($x)
{
list(, $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15) = unpack('V*', $x);
$z0 = $x0;
$z1 = $x1;
$z2 = $x2;
$z3 = $x3;
$z4 = $x4;
$z5 = $x5;
$z6 = $x6;
$z7 = $x7;
$z8 = $x8;
$z9 = $x9;
$z10 = $x10;
$z11 = $x11;
$z12 = $x12;
$z13 = $x13;
$z14 = $x14;
$z15 = $x15;
// @codingStandardsIgnoreStart
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// columnRound
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
$x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
$x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
$x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
$x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
$x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
$x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
$x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
$x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
// rowRound
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
$x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
$x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
$x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
$x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
$x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
$x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
$x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
$x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
// @codingStandardsIgnoreEnd
$x0 += $z0;
$x1 += $z1;
$x2 += $z2;
$x3 += $z3;
$x4 += $z4;
$x5 += $z5;
$x6 += $z6;
$x7 += $z7;
$x8 += $z8;
$x9 += $z9;
$x10 += $z10;
$x11 += $z11;
$x12 += $z12;
$x13 += $z13;
$x14 += $z14;
$x15 += $z15;
return pack('V*', $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15);
}
}
<?php
/**
* Base Class for all asymmetric key ciphers
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common;
use phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\Hash;
use phpseclib3\Crypt\RSA;
use phpseclib3\Exception\NoKeyLoadedException;
use phpseclib3\Exception\UnsupportedFormatException;
use phpseclib3\Math\BigInteger;
/**
* Base Class for all asymmetric cipher classes
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class AsymmetricKey
{
/**
* Precomputed Zero
*
* @var \phpseclib3\Math\BigInteger
*/
protected static $zero;
/**
* Precomputed One
*
* @var \phpseclib3\Math\BigInteger
*/
protected static $one;
/**
* Format of the loaded key
*
* @var string
*/
protected $format;
/**
* Hash function
*
* @var \phpseclib3\Crypt\Hash
*/
protected $hash;
/**
* HMAC function
*
* @var \phpseclib3\Crypt\Hash
*/
private $hmac;
/**
* Supported plugins (lower case)
*
* @see self::initialize_static_variables()
* @var array
*/
private static $plugins = [];
/**
* Invisible plugins
*
* @see self::initialize_static_variables()
* @var array
*/
private static $invisiblePlugins = [];
/**
* Available Engines
*
* @var boolean[]
*/
protected static $engines = [];
/**
* Key Comment
*
* @var null|string
*/
private $comment;
/**
* @param string $type
* @return string
*/
abstract public function toString($type, array $options = []);
/**
* The constructor
*/
protected function __construct()
{
self::initialize_static_variables();
$this->hash = new Hash('sha256');
$this->hmac = new Hash('sha256');
}
/**
* Initialize static variables
*/
protected static function initialize_static_variables()
{
if (!isset(self::$zero)) {
self::$zero = new BigInteger(0);
self::$one = new BigInteger(1);
}
self::loadPlugins('Keys');
if (static::ALGORITHM != 'RSA' && static::ALGORITHM != 'DH') {
self::loadPlugins('Signature');
}
}
/**
* Load the key
*
* @param string $key
* @param string $password optional
* @return AsymmetricKey
*/
public static function load($key, $password = false)
{
self::initialize_static_variables();
$components = false;
foreach (self::$plugins[static::ALGORITHM]['Keys'] as $format) {
if (isset(self::$invisiblePlugins[static::ALGORITHM]) && in_array($format, self::$invisiblePlugins[static::ALGORITHM])) {
continue;
}
try {
$components = $format::load($key, $password);
} catch (\Exception $e) {
$components = false;
}
if ($components !== false) {
break;
}
}
if ($components === false) {
throw new NoKeyLoadedException('Unable to read key');
}
$components['format'] = $format;
$components['secret'] = isset($components['secret']) ? $components['secret'] : '';
$comment = isset($components['comment']) ? $components['comment'] : null;
$new = static::onLoad($components);
$new->format = $format;
$new->comment = $comment;
return $new instanceof PrivateKey ?
$new->withPassword($password) :
$new;
}
/**
* Loads a private key
*
* @return PrivateKey
* @param string|array $key
* @param string $password optional
*/
public static function loadPrivateKey($key, $password = '')
{
$key = self::load($key, $password);
if (!$key instanceof PrivateKey) {
throw new NoKeyLoadedException('The key that was loaded was not a private key');
}
return $key;
}
/**
* Loads a public key
*
* @return PublicKey
* @param string|array $key
*/
public static function loadPublicKey($key)
{
$key = self::load($key);
if (!$key instanceof PublicKey) {
throw new NoKeyLoadedException('The key that was loaded was not a public key');
}
return $key;
}
/**
* Loads parameters
*
* @return AsymmetricKey
* @param string|array $key
*/
public static function loadParameters($key)
{
$key = self::load($key);
if (!$key instanceof PrivateKey && !$key instanceof PublicKey) {
throw new NoKeyLoadedException('The key that was loaded was not a parameter');
}
return $key;
}
/**
* Load the key, assuming a specific format
*
* @param string $type
* @param string $key
* @param string $password optional
* @return static
*/
public static function loadFormat($type, $key, $password = false)
{
self::initialize_static_variables();
$components = false;
$format = strtolower($type);
if (isset(self::$plugins[static::ALGORITHM]['Keys'][$format])) {
$format = self::$plugins[static::ALGORITHM]['Keys'][$format];
$components = $format::load($key, $password);
}
if ($components === false) {
throw new NoKeyLoadedException('Unable to read key');
}
$components['format'] = $format;
$components['secret'] = isset($components['secret']) ? $components['secret'] : '';
$new = static::onLoad($components);
$new->format = $format;
return $new instanceof PrivateKey ?
$new->withPassword($password) :
$new;
}
/**
* Loads a private key
*
* @return PrivateKey
* @param string $type
* @param string $key
* @param string $password optional
*/
public static function loadPrivateKeyFormat($type, $key, $password = false)
{
$key = self::loadFormat($type, $key, $password);
if (!$key instanceof PrivateKey) {
throw new NoKeyLoadedException('The key that was loaded was not a private key');
}
return $key;
}
/**
* Loads a public key
*
* @return PublicKey
* @param string $type
* @param string $key
*/
public static function loadPublicKeyFormat($type, $key)
{
$key = self::loadFormat($type, $key);
if (!$key instanceof PublicKey) {
throw new NoKeyLoadedException('The key that was loaded was not a public key');
}
return $key;
}
/**
* Loads parameters
*
* @return AsymmetricKey
* @param string $type
* @param string|array $key
*/
public static function loadParametersFormat($type, $key)
{
$key = self::loadFormat($type, $key);
if (!$key instanceof PrivateKey && !$key instanceof PublicKey) {
throw new NoKeyLoadedException('The key that was loaded was not a parameter');
}
return $key;
}
/**
* Validate Plugin
*
* @param string $format
* @param string $type
* @param string $method optional
* @return mixed
*/
protected static function validatePlugin($format, $type, $method = null)
{
$type = strtolower($type);
if (!isset(self::$plugins[static::ALGORITHM][$format][$type])) {
throw new UnsupportedFormatException("$type is not a supported format");
}
$type = self::$plugins[static::ALGORITHM][$format][$type];
if (isset($method) && !method_exists($type, $method)) {
throw new UnsupportedFormatException("$type does not implement $method");
}
return $type;
}
/**
* Load Plugins
*
* @param string $format
*/
private static function loadPlugins($format)
{
if (!isset(self::$plugins[static::ALGORITHM][$format])) {
self::$plugins[static::ALGORITHM][$format] = [];
foreach (new \DirectoryIterator(__DIR__ . '/../' . static::ALGORITHM . '/Formats/' . $format . '/') as $file) {
if ($file->getExtension() != 'php') {
continue;
}
$name = $file->getBasename('.php');
if ($name[0] == '.') {
continue;
}
$type = 'phpseclib3\Crypt\\' . static::ALGORITHM . '\\Formats\\' . $format . '\\' . $name;
$reflect = new \ReflectionClass($type);
if ($reflect->isTrait()) {
continue;
}
self::$plugins[static::ALGORITHM][$format][strtolower($name)] = $type;
if ($reflect->hasConstant('IS_INVISIBLE')) {
self::$invisiblePlugins[static::ALGORITHM][] = $type;
}
}
}
}
/**
* Returns a list of supported formats.
*
* @return array
*/
public static function getSupportedKeyFormats()
{
self::initialize_static_variables();
return self::$plugins[static::ALGORITHM]['Keys'];
}
/**
* Add a fileformat plugin
*
* The plugin needs to either already be loaded or be auto-loadable.
* Loading a plugin whose shortname overwrite an existing shortname will overwrite the old plugin.
*
* @see self::load()
* @param string $fullname
* @return bool
*/
public static function addFileFormat($fullname)
{
self::initialize_static_variables();
if (class_exists($fullname)) {
$meta = new \ReflectionClass($fullname);
$shortname = $meta->getShortName();
self::$plugins[static::ALGORITHM]['Keys'][strtolower($shortname)] = $fullname;
if ($meta->hasConstant('IS_INVISIBLE')) {
self::$invisiblePlugins[static::ALGORITHM] = strtolower($name);
}
}
}
/**
* Returns the format of the loaded key.
*
* If the key that was loaded wasn't in a valid or if the key was auto-generated
* with RSA::createKey() then this will throw an exception.
*
* @see self::load()
* @return mixed
*/
public function getLoadedFormat()
{
if (empty($this->format)) {
throw new NoKeyLoadedException('This key was created with createKey - it was not loaded with load. Therefore there is no "loaded format"');
}
$meta = new \ReflectionClass($this->format);
return $meta->getShortName();
}
/**
* Returns the key's comment
*
* Not all key formats support comments. If you want to set a comment use toString()
*
* @return null|string
*/
public function getComment()
{
return $this->comment;
}
/**
* Tests engine validity
*
*/
public static function useBestEngine()
{
static::$engines = [
'PHP' => true,
'OpenSSL' => extension_loaded('openssl'),
// this test can be satisfied by either of the following:
// http://php.net/manual/en/book.sodium.php
// https://github.com/paragonie/sodium_compat
'libsodium' => function_exists('sodium_crypto_sign_keypair')
];
return static::$engines;
}
/**
* Flag to use internal engine only (useful for unit testing)
*
*/
public static function useInternalEngine()
{
static::$engines = [
'PHP' => true,
'OpenSSL' => false,
'libsodium' => false
];
}
/**
* __toString() magic method
*
* @return string
*/
public function __toString()
{
return $this->toString('PKCS8');
}
/**
* Determines which hashing function should be used
*
* @param string $hash
*/
public function withHash($hash)
{
$new = clone $this;
$new->hash = new Hash($hash);
$new->hmac = new Hash($hash);
return $new;
}
/**
* Returns the hash algorithm currently being used
*
*/
public function getHash()
{
return clone $this->hash;
}
/**
* Compute the pseudorandom k for signature generation,
* using the process specified for deterministic DSA.
*
* @param string $h1
* @return string
*/
protected function computek($h1)
{
$v = str_repeat("\1", strlen($h1));
$k = str_repeat("\0", strlen($h1));
$x = $this->int2octets($this->x);
$h1 = $this->bits2octets($h1);
$this->hmac->setKey($k);
$k = $this->hmac->hash($v . "\0" . $x . $h1);
$this->hmac->setKey($k);
$v = $this->hmac->hash($v);
$k = $this->hmac->hash($v . "\1" . $x . $h1);
$this->hmac->setKey($k);
$v = $this->hmac->hash($v);
$qlen = $this->q->getLengthInBytes();
while (true) {
$t = '';
while (strlen($t) < $qlen) {
$v = $this->hmac->hash($v);
$t = $t . $v;
}
$k = $this->bits2int($t);
if (!$k->equals(self::$zero) && $k->compare($this->q) < 0) {
break;
}
$k = $this->hmac->hash($v . "\0");
$this->hmac->setKey($k);
$v = $this->hmac->hash($v);
}
return $k;
}
/**
* Integer to Octet String
*
* @param \phpseclib3\Math\BigInteger $v
* @return string
*/
private function int2octets($v)
{
$out = $v->toBytes();
$rolen = $this->q->getLengthInBytes();
if (strlen($out) < $rolen) {
return str_pad($out, $rolen, "\0", STR_PAD_LEFT);
} elseif (strlen($out) > $rolen) {
return substr($out, -$rolen);
} else {
return $out;
}
}
/**
* Bit String to Integer
*
* @param string $in
* @return \phpseclib3\Math\BigInteger
*/
protected function bits2int($in)
{
$v = new BigInteger($in, 256);
$vlen = strlen($in) << 3;
$qlen = $this->q->getLength();
if ($vlen > $qlen) {
return $v->bitwise_rightShift($vlen - $qlen);
}
return $v;
}
/**
* Bit String to Octet String
*
* @param string $in
* @return string
*/
private function bits2octets($in)
{
$z1 = $this->bits2int($in);
$z2 = $z1->subtract($this->q);
return $z2->compare(self::$zero) < 0 ?
$this->int2octets($z1) :
$this->int2octets($z2);
}
}
<?php
/**
* Base Class for all block ciphers
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common;
/**
* Base Class for all block cipher classes
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class BlockCipher extends SymmetricKey
{
}
<?php
/**
* JSON Web Key (RFC7517) Handler
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
/**
* JSON Web Key Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class JWK
{
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password
* @return array
*/
public static function load($key, $password = '')
{
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
$key = preg_replace('#\s#', '', $key); // remove whitespace
if (PHP_VERSION_ID >= 73000) {
$key = json_decode($key, null, 512, JSON_THROW_ON_ERROR);
} else {
$key = json_decode($key);
if (!$key) {
throw new \RuntimeException('Unable to decode JSON');
}
}
if (isset($key->kty)) {
return $key;
}
if (count($key->keys) != 1) {
throw new \RuntimeException('Although the JWK key format supports multiple keys phpseclib does not');
}
return $key->keys[0];
}
/**
* Wrap a key appropriately
*
* @return string
*/
protected static function wrapKey(array $key, array $options)
{
return json_encode(['keys' => [$key + $options]]);
}
}
<?php
/**
* OpenSSH Key Handler
*
* PHP version 5
*
* Place in $HOME/.ssh/authorized_keys
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\Random;
/**
* OpenSSH Formatted RSA Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class OpenSSH
{
/**
* Default comment
*
* @var string
*/
protected static $comment = 'phpseclib-generated-key';
/**
* Binary key flag
*
* @var bool
*/
protected static $binary = false;
/**
* Sets the default comment
*
* @param string $comment
*/
public static function setComment($comment)
{
self::$comment = str_replace(["\r", "\n"], '', $comment);
}
/**
* Break a public or private key down into its constituent components
*
* $type can be either ssh-dss or ssh-rsa
*
* @param string $key
* @param string $password
* @return array
*/
public static function load($key, $password = '')
{
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
// key format is described here:
// https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
if (strpos($key, 'BEGIN OPENSSH PRIVATE KEY') !== false) {
$key = preg_replace('#(?:^-.*?-[\r\n]*$)|\s#ms', '', $key);
$key = Strings::base64_decode($key);
$magic = Strings::shift($key, 15);
if ($magic != "openssh-key-v1\0") {
throw new \RuntimeException('Expected openssh-key-v1');
}
list($ciphername, $kdfname, $kdfoptions, $numKeys) = Strings::unpackSSH2('sssN', $key);
if ($numKeys != 1) {
// if we wanted to support multiple keys we could update PublicKeyLoader to preview what the # of keys
// would be; it'd then call Common\Keys\OpenSSH.php::load() and get the paddedKey. it'd then pass
// that to the appropriate key loading parser $numKey times or something
throw new \RuntimeException('Although the OpenSSH private key format supports multiple keys phpseclib does not');
}
switch ($ciphername) {
case 'none':
break;
case 'aes256-ctr':
if ($kdfname != 'bcrypt') {
throw new \RuntimeException('Only the bcrypt kdf is supported (' . $kdfname . ' encountered)');
}
list($salt, $rounds) = Strings::unpackSSH2('sN', $kdfoptions);
$crypto = new AES('ctr');
//$crypto->setKeyLength(256);
//$crypto->disablePadding();
$crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32);
break;
default:
throw new \RuntimeException('The only supported cipherse are: none, aes256-ctr (' . $ciphername . ' is being used)');
}
list($publicKey, $paddedKey) = Strings::unpackSSH2('ss', $key);
list($type) = Strings::unpackSSH2('s', $publicKey);
if (isset($crypto)) {
$paddedKey = $crypto->decrypt($paddedKey);
}
list($checkint1, $checkint2) = Strings::unpackSSH2('NN', $paddedKey);
// any leftover bytes in $paddedKey are for padding? but they should be sequential bytes. eg. 1, 2, 3, etc.
if ($checkint1 != $checkint2) {
throw new \RuntimeException('The two checkints do not match');
}
self::checkType($type);
return compact('type', 'publicKey', 'paddedKey');
}
$parts = explode(' ', $key, 3);
if (!isset($parts[1])) {
$key = base64_decode($parts[0]);
$comment = false;
} else {
$asciiType = $parts[0];
self::checkType($parts[0]);
$key = base64_decode($parts[1]);
$comment = isset($parts[2]) ? $parts[2] : false;
}
if ($key === false) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
list($type) = Strings::unpackSSH2('s', $key);
self::checkType($type);
if (isset($asciiType) && $asciiType != $type) {
throw new \RuntimeException('Two different types of keys are claimed: ' . $asciiType . ' and ' . $type);
}
if (strlen($key) <= 4) {
throw new \UnexpectedValueException('Key appears to be malformed');
}
$publicKey = $key;
return compact('type', 'publicKey', 'comment');
}
/**
* Toggle between binary and printable keys
*
* Printable keys are what are generated by default. These are the ones that go in
* $HOME/.ssh/authorized_key.
*
* @param bool $enabled
*/
public static function setBinaryOutput($enabled)
{
self::$binary = $enabled;
}
/**
* Checks to see if the type is valid
*
* @param string $candidate
*/
private static function checkType($candidate)
{
if (!in_array($candidate, static::$types)) {
throw new \RuntimeException("The key type ($candidate) is not equal to: " . implode(',', static::$types));
}
}
/**
* Wrap a private key appropriately
*
* @param string $publicKey
* @param string $privateKey
* @param string $password
* @param array $options
* @return string
*/
protected static function wrapPrivateKey($publicKey, $privateKey, $password, $options)
{
list(, $checkint) = unpack('N', Random::string(4));
$comment = isset($options['comment']) ? $options['comment'] : self::$comment;
$paddedKey = Strings::packSSH2('NN', $checkint, $checkint) .
$privateKey .
Strings::packSSH2('s', $comment);
$usesEncryption = !empty($password) && is_string($password);
/*
from http://tools.ietf.org/html/rfc4253#section-6 :
Note that the length of the concatenation of 'packet_length',
'padding_length', 'payload', and 'random padding' MUST be a multiple
of the cipher block size or 8, whichever is larger.
*/
$blockSize = $usesEncryption ? 16 : 8;
$paddingLength = (($blockSize - 1) * strlen($paddedKey)) % $blockSize;
for ($i = 1; $i <= $paddingLength; $i++) {
$paddedKey .= chr($i);
}
if (!$usesEncryption) {
$key = Strings::packSSH2('sssNss', 'none', 'none', '', 1, $publicKey, $paddedKey);
} else {
$rounds = isset($options['rounds']) ? $options['rounds'] : 16;
$salt = Random::string(16);
$kdfoptions = Strings::packSSH2('sN', $salt, $rounds);
$crypto = new AES('ctr');
$crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32);
$paddedKey = $crypto->encrypt($paddedKey);
$key = Strings::packSSH2('sssNss', 'aes256-ctr', 'bcrypt', $kdfoptions, 1, $publicKey, $paddedKey);
}
$key = "openssh-key-v1\0$key";
return "-----BEGIN OPENSSH PRIVATE KEY-----\n" .
chunk_split(Strings::base64_encode($key), 70, "\n") .
"-----END OPENSSH PRIVATE KEY-----\n";
}
}
<?php
/**
* PKCS Formatted Key Handler
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Keys;
/**
* PKCS1 Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS
{
/**
* Auto-detect the format
*/
const MODE_ANY = 0;
/**
* Require base64-encoded PEM's be supplied
*/
const MODE_PEM = 1;
/**
* Require raw DER's be supplied
*/
const MODE_DER = 2;
/**#@-*/
/**
* Is the key a base-64 encoded PEM, DER or should it be auto-detected?
*
* @var int
*/
protected static $format = self::MODE_ANY;
/**
* Require base64-encoded PEM's be supplied
*
*/
public static function requirePEM()
{
self::$format = self::MODE_PEM;
}
/**
* Require raw DER's be supplied
*
*/
public static function requireDER()
{
self::$format = self::MODE_DER;
}
/**
* Accept any format and auto detect the format
*
* This is the default setting
*
*/
public static function requireAny()
{
self::$format = self::MODE_ANY;
}
}
<?php
/**
* PKCS1 Formatted Key Handler
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\DES;
use phpseclib3\Crypt\Random;
use phpseclib3\Crypt\TripleDES;
use phpseclib3\Exception\UnsupportedAlgorithmException;
use phpseclib3\File\ASN1;
/**
* PKCS1 Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS1 extends PKCS
{
/**
* Default encryption algorithm
*
* @var string
*/
private static $defaultEncryptionAlgorithm = 'AES-128-CBC';
/**
* Sets the default encryption algorithm
*
* @param string $algo
*/
public static function setEncryptionAlgorithm($algo)
{
self::$defaultEncryptionAlgorithm = $algo;
}
/**
* Returns the mode constant corresponding to the mode string
*
* @param string $mode
* @return int
* @throws \UnexpectedValueException if the block cipher mode is unsupported
*/
private static function getEncryptionMode($mode)
{
switch ($mode) {
case 'CBC':
case 'ECB':
case 'CFB':
case 'OFB':
case 'CTR':
return $mode;
}
throw new \UnexpectedValueException('Unsupported block cipher mode of operation');
}
/**
* Returns a cipher object corresponding to a string
*
* @param string $algo
* @return string
* @throws \UnexpectedValueException if the encryption algorithm is unsupported
*/
private static function getEncryptionObject($algo)
{
$modes = '(CBC|ECB|CFB|OFB|CTR)';
switch (true) {
case preg_match("#^AES-(128|192|256)-$modes$#", $algo, $matches):
$cipher = new AES(self::getEncryptionMode($matches[2]));
$cipher->setKeyLength($matches[1]);
return $cipher;
case preg_match("#^DES-EDE3-$modes$#", $algo, $matches):
return new TripleDES(self::getEncryptionMode($matches[1]));
case preg_match("#^DES-$modes$#", $algo, $matches):
return new DES(self::getEncryptionMode($matches[1]));
default:
throw new UnsupportedAlgorithmException($algo . ' is not a supported algorithm');
}
}
/**
* Generate a symmetric key for PKCS#1 keys
*
* @param string $password
* @param string $iv
* @param int $length
* @return string
*/
private static function generateSymmetricKey($password, $iv, $length)
{
$symkey = '';
$iv = substr($iv, 0, 8);
while (strlen($symkey) < $length) {
$symkey .= md5($symkey . $password . $iv, true);
}
return substr($symkey, 0, $length);
}
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password optional
* @return array
*/
protected static function load($key, $password)
{
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
/* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is
"outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to
protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding
two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here:
http://tools.ietf.org/html/rfc1421#section-4.6.1.1
http://tools.ietf.org/html/rfc1421#section-4.6.1.3
DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell.
DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation
function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's
own implementation. ie. the implementation *is* the standard and any bugs that may exist in that
implementation are part of the standard, as well.
* OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */
if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) {
$iv = Strings::hex2bin(trim($matches[2]));
// remove the Proc-Type / DEK-Info sections as they're no longer needed
$key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key);
$ciphertext = ASN1::extractBER($key);
if ($ciphertext === false) {
$ciphertext = $key;
}
$crypto = self::getEncryptionObject($matches[1]);
$crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3));
$crypto->setIV($iv);
$key = $crypto->decrypt($ciphertext);
} else {
if (self::$format != self::MODE_DER) {
$decoded = ASN1::extractBER($key);
if ($decoded !== false) {
$key = $decoded;
} elseif (self::$format == self::MODE_PEM) {
throw new \UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text');
}
}
}
return $key;
}
/**
* Wrap a private key appropriately
*
* @param string $key
* @param string $type
* @param string $password
* @param array $options optional
* @return string
*/
protected static function wrapPrivateKey($key, $type, $password, array $options = [])
{
if (empty($password) || !is_string($password)) {
return "-----BEGIN $type PRIVATE KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END $type PRIVATE KEY-----";
}
$encryptionAlgorithm = isset($options['encryptionAlgorithm']) ? $options['encryptionAlgorithm'] : self::$defaultEncryptionAlgorithm;
$cipher = self::getEncryptionObject($encryptionAlgorithm);
$iv = Random::string($cipher->getBlockLength() >> 3);
$cipher->setKey(self::generateSymmetricKey($password, $iv, $cipher->getKeyLength() >> 3));
$cipher->setIV($iv);
$iv = strtoupper(Strings::bin2hex($iv));
return "-----BEGIN $type PRIVATE KEY-----\r\n" .
"Proc-Type: 4,ENCRYPTED\r\n" .
"DEK-Info: " . $encryptionAlgorithm . ",$iv\r\n" .
"\r\n" .
chunk_split(Strings::base64_encode($cipher->encrypt($key)), 64) .
"-----END $type PRIVATE KEY-----";
}
/**
* Wrap a public key appropriately
*
* @param string $key
* @param string $type
* @return string
*/
protected static function wrapPublicKey($key, $type)
{
return "-----BEGIN $type PUBLIC KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END $type PUBLIC KEY-----";
}
}
<?php
/**
* PKCS#8 Formatted Key Handler
*
* PHP version 5
*
* Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
*
* Processes keys with the following headers:
*
* -----BEGIN ENCRYPTED PRIVATE KEY-----
* -----BEGIN PRIVATE KEY-----
* -----BEGIN PUBLIC KEY-----
*
* Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
* is specific to private keys it's basically creating a DER-encoded wrapper
* for keys. This just extends that same concept to public keys (much like ssh-keygen)
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2015 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\DES;
use phpseclib3\Crypt\Random;
use phpseclib3\Crypt\RC2;
use phpseclib3\Crypt\RC4;
use phpseclib3\Crypt\TripleDES;
use phpseclib3\Exception\InsufficientSetupException;
use phpseclib3\Exception\UnsupportedAlgorithmException;
use phpseclib3\File\ASN1;
use phpseclib3\File\ASN1\Maps;
/**
* PKCS#8 Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PKCS8 extends PKCS
{
/**
* Default encryption algorithm
*
* @var string
*/
private static $defaultEncryptionAlgorithm = 'id-PBES2';
/**
* Default encryption scheme
*
* Only used when defaultEncryptionAlgorithm is id-PBES2
*
* @var string
*/
private static $defaultEncryptionScheme = 'aes128-CBC-PAD';
/**
* Default PRF
*
* Only used when defaultEncryptionAlgorithm is id-PBES2
*
* @var string
*/
private static $defaultPRF = 'id-hmacWithSHA256';
/**
* Default Iteration Count
*
* @var int
*/
private static $defaultIterationCount = 2048;
/**
* OIDs loaded
*
* @var bool
*/
private static $oidsLoaded = false;
/**
* Sets the default encryption algorithm
*
* @param string $algo
*/
public static function setEncryptionAlgorithm($algo)
{
self::$defaultEncryptionAlgorithm = $algo;
}
/**
* Sets the default encryption algorithm for PBES2
*
* @param string $algo
*/
public static function setEncryptionScheme($algo)
{
self::$defaultEncryptionScheme = $algo;
}
/**
* Sets the iteration count
*
* @param int $count
*/
public static function setIterationCount($count)
{
self::$defaultIterationCount = $count;
}
/**
* Sets the PRF for PBES2
*
* @param string $algo
*/
public static function setPRF($algo)
{
self::$defaultPRF = $algo;
}
/**
* Returns a SymmetricKey object based on a PBES1 $algo
*
* @return \phpseclib3\Crypt\Common\SymmetricKey
* @param string $algo
*/
private static function getPBES1EncryptionObject($algo)
{
$algo = preg_match('#^pbeWith(?:MD2|MD5|SHA1|SHA)And(.*?)-CBC$#', $algo, $matches) ?
$matches[1] :
substr($algo, 13); // strlen('pbeWithSHAAnd') == 13
switch ($algo) {
case 'DES':
$cipher = new DES('cbc');
break;
case 'RC2':
$cipher = new RC2('cbc');
break;
case '3-KeyTripleDES':
$cipher = new TripleDES('cbc');
break;
case '2-KeyTripleDES':
$cipher = new TripleDES('cbc');
$cipher->setKeyLength(128);
break;
case '128BitRC2':
$cipher = new RC2('cbc');
$cipher->setKeyLength(128);
break;
case '40BitRC2':
$cipher = new RC2('cbc');
$cipher->setKeyLength(40);
break;
case '128BitRC4':
$cipher = new RC4();
$cipher->setKeyLength(128);
break;
case '40BitRC4':
$cipher = new RC4();
$cipher->setKeyLength(40);
break;
default:
throw new UnsupportedAlgorithmException("$algo is not a supported algorithm");
}
return $cipher;
}
/**
* Returns a hash based on a PBES1 $algo
*
* @return string
* @param string $algo
*/
private static function getPBES1Hash($algo)
{
if (preg_match('#^pbeWith(MD2|MD5|SHA1|SHA)And.*?-CBC$#', $algo, $matches)) {
return $matches[1] == 'SHA' ? 'sha1' : $matches[1];
}
return 'sha1';
}
/**
* Returns a KDF baesd on a PBES1 $algo
*
* @return string
* @param string $algo
*/
private static function getPBES1KDF($algo)
{
switch ($algo) {
case 'pbeWithMD2AndDES-CBC':
case 'pbeWithMD2AndRC2-CBC':
case 'pbeWithMD5AndDES-CBC':
case 'pbeWithMD5AndRC2-CBC':
case 'pbeWithSHA1AndDES-CBC':
case 'pbeWithSHA1AndRC2-CBC':
return 'pbkdf1';
}
return 'pkcs12';
}
/**
* Returns a SymmetricKey object baesd on a PBES2 $algo
*
* @return SymmetricKey
* @param string $algo
*/
private static function getPBES2EncryptionObject($algo)
{
switch ($algo) {
case 'desCBC':
$cipher = new TripleDES('cbc');
break;
case 'des-EDE3-CBC':
$cipher = new TripleDES('cbc');
break;
case 'rc2CBC':
$cipher = new RC2('cbc');
// in theory this can be changed
$cipher->setKeyLength(128);
break;
case 'rc5-CBC-PAD':
throw new UnsupportedAlgorithmException('rc5-CBC-PAD is not supported for PBES2 PKCS#8 keys');
case 'aes128-CBC-PAD':
case 'aes192-CBC-PAD':
case 'aes256-CBC-PAD':
$cipher = new AES('cbc');
$cipher->setKeyLength(substr($algo, 3, 3));
break;
default:
throw new UnsupportedAlgorithmException("$algo is not supported");
}
return $cipher;
}
/**
* Initialize static variables
*
*/
private static function initialize_static_variables()
{
if (!isset(static::$childOIDsLoaded)) {
throw new InsufficientSetupException('This class should not be called directly');
}
if (!static::$childOIDsLoaded) {
ASN1::loadOIDs(is_array(static::OID_NAME) ?
array_combine(static::OID_NAME, static::OID_VALUE) :
[static::OID_NAME => static::OID_VALUE]);
static::$childOIDsLoaded = true;
}
if (!self::$oidsLoaded) {
// from https://tools.ietf.org/html/rfc2898
ASN1::loadOIDs([
// PBES1 encryption schemes
'pbeWithMD2AndDES-CBC' => '1.2.840.113549.1.5.1',
'pbeWithMD2AndRC2-CBC' => '1.2.840.113549.1.5.4',
'pbeWithMD5AndDES-CBC' => '1.2.840.113549.1.5.3',
'pbeWithMD5AndRC2-CBC' => '1.2.840.113549.1.5.6',
'pbeWithSHA1AndDES-CBC' => '1.2.840.113549.1.5.10',
'pbeWithSHA1AndRC2-CBC' => '1.2.840.113549.1.5.11',
// from PKCS#12:
// https://tools.ietf.org/html/rfc7292
'pbeWithSHAAnd128BitRC4' => '1.2.840.113549.1.12.1.1',
'pbeWithSHAAnd40BitRC4' => '1.2.840.113549.1.12.1.2',
'pbeWithSHAAnd3-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.3',
'pbeWithSHAAnd2-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.4',
'pbeWithSHAAnd128BitRC2-CBC' => '1.2.840.113549.1.12.1.5',
'pbeWithSHAAnd40BitRC2-CBC' => '1.2.840.113549.1.12.1.6',
'id-PBKDF2' => '1.2.840.113549.1.5.12',
'id-PBES2' => '1.2.840.113549.1.5.13',
'id-PBMAC1' => '1.2.840.113549.1.5.14',
// from PKCS#5 v2.1:
// http://www.rsa.com/rsalabs/pkcs/files/h11302-wp-pkcs5v2-1-password-based-cryptography-standard.pdf
'id-hmacWithSHA1' => '1.2.840.113549.2.7',
'id-hmacWithSHA224' => '1.2.840.113549.2.8',
'id-hmacWithSHA256' => '1.2.840.113549.2.9',
'id-hmacWithSHA384' => '1.2.840.113549.2.10',
'id-hmacWithSHA512' => '1.2.840.113549.2.11',
'id-hmacWithSHA512-224' => '1.2.840.113549.2.12',
'id-hmacWithSHA512-256' => '1.2.840.113549.2.13',
'desCBC' => '1.3.14.3.2.7',
'des-EDE3-CBC' => '1.2.840.113549.3.7',
'rc2CBC' => '1.2.840.113549.3.2',
'rc5-CBC-PAD' => '1.2.840.113549.3.9',
'aes128-CBC-PAD' => '2.16.840.1.101.3.4.1.2',
'aes192-CBC-PAD' => '2.16.840.1.101.3.4.1.22',
'aes256-CBC-PAD' => '2.16.840.1.101.3.4.1.42'
]);
self::$oidsLoaded = true;
}
}
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password optional
* @return array
*/
protected static function load($key, $password = '')
{
$decoded = self::preParse($key);
$meta = [];
$decrypted = ASN1::asn1map($decoded[0], Maps\EncryptedPrivateKeyInfo::MAP);
if (strlen($password) && is_array($decrypted)) {
$algorithm = $decrypted['encryptionAlgorithm']['algorithm'];
switch ($algorithm) {
// PBES1
case 'pbeWithMD2AndDES-CBC':
case 'pbeWithMD2AndRC2-CBC':
case 'pbeWithMD5AndDES-CBC':
case 'pbeWithMD5AndRC2-CBC':
case 'pbeWithSHA1AndDES-CBC':
case 'pbeWithSHA1AndRC2-CBC':
case 'pbeWithSHAAnd3-KeyTripleDES-CBC':
case 'pbeWithSHAAnd2-KeyTripleDES-CBC':
case 'pbeWithSHAAnd128BitRC2-CBC':
case 'pbeWithSHAAnd40BitRC2-CBC':
case 'pbeWithSHAAnd128BitRC4':
case 'pbeWithSHAAnd40BitRC4':
$cipher = self::getPBES1EncryptionObject($algorithm);
$hash = self::getPBES1Hash($algorithm);
$kdf = self::getPBES1KDF($algorithm);
$meta['meta']['algorithm'] = $algorithm;
$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
if (!$temp) {
throw new \RuntimeException('Unable to decode BER');
}
extract(ASN1::asn1map($temp[0], Maps\PBEParameter::MAP));
$iterationCount = (int) $iterationCount->toString();
$cipher->setPassword($password, $kdf, $hash, $salt, $iterationCount);
$key = $cipher->decrypt($decrypted['encryptedData']);
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER 2');
}
break;
case 'id-PBES2':
$meta['meta']['algorithm'] = $algorithm;
$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
if (!$temp) {
throw new \RuntimeException('Unable to decode BER');
}
$temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
extract($temp);
$cipher = self::getPBES2EncryptionObject($encryptionScheme['algorithm']);
$meta['meta']['cipher'] = $encryptionScheme['algorithm'];
$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
if (!$temp) {
throw new \RuntimeException('Unable to decode BER');
}
$temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
extract($temp);
if (!$cipher instanceof RC2) {
$cipher->setIV($encryptionScheme['parameters']['octetString']);
} else {
$temp = ASN1::decodeBER($encryptionScheme['parameters']);
if (!$temp) {
throw new \RuntimeException('Unable to decode BER');
}
extract(ASN1::asn1map($temp[0], Maps\RC2CBCParameter::MAP));
$effectiveKeyLength = (int) $rc2ParametersVersion->toString();
switch ($effectiveKeyLength) {
case 160:
$effectiveKeyLength = 40;
break;
case 120:
$effectiveKeyLength = 64;
break;
case 58:
$effectiveKeyLength = 128;
break;
//default: // should be >= 256
}
$cipher->setIV($iv);
$cipher->setKeyLength($effectiveKeyLength);
}
$meta['meta']['keyDerivationFunc'] = $keyDerivationFunc['algorithm'];
switch ($keyDerivationFunc['algorithm']) {
case 'id-PBKDF2':
$temp = ASN1::decodeBER($keyDerivationFunc['parameters']);
if (!$temp) {
throw new \RuntimeException('Unable to decode BER');
}
$prf = ['algorithm' => 'id-hmacWithSHA1'];
$params = ASN1::asn1map($temp[0], Maps\PBKDF2params::MAP);
extract($params);
$meta['meta']['prf'] = $prf['algorithm'];
$hash = str_replace('-', '/', substr($prf['algorithm'], 11));
$params = [
$password,
'pbkdf2',
$hash,
$salt,
(int) $iterationCount->toString()
];
if (isset($keyLength)) {
$params[] = (int) $keyLength->toString();
}
$cipher->setPassword(...$params);
$key = $cipher->decrypt($decrypted['encryptedData']);
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER 3');
}
break;
default:
throw new UnsupportedAlgorithmException('Only PBKDF2 is supported for PBES2 PKCS#8 keys');
}
break;
case 'id-PBMAC1':
//$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
//$value = ASN1::asn1map($temp[0], Maps\PBMAC1params::MAP);
// since i can't find any implementation that does PBMAC1 it is unsupported
throw new UnsupportedAlgorithmException('Only PBES1 and PBES2 PKCS#8 keys are supported.');
// at this point we'll assume that the key conforms to PublicKeyInfo
}
}
$private = ASN1::asn1map($decoded[0], Maps\OneAsymmetricKey::MAP);
if (is_array($private)) {
if (isset($private['privateKeyAlgorithm']['parameters']) && !$private['privateKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][1]['content'][1])) {
$temp = $decoded[0]['content'][1]['content'][1];
$private['privateKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length']));
}
if (is_array(static::OID_NAME)) {
if (!in_array($private['privateKeyAlgorithm']['algorithm'], static::OID_NAME)) {
throw new UnsupportedAlgorithmException($private['privateKeyAlgorithm']['algorithm'] . ' is not a supported key type');
}
} else {
if ($private['privateKeyAlgorithm']['algorithm'] != static::OID_NAME) {
throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $private['privateKeyAlgorithm']['algorithm'] . ' key');
}
}
if (isset($private['publicKey'])) {
if ($private['publicKey'][0] != "\0") {
throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($private['publicKey'][0]));
}
$private['publicKey'] = substr($private['publicKey'], 1);
}
return $private + $meta;
}
// EncryptedPrivateKeyInfo and PublicKeyInfo have largely identical "signatures". the only difference
// is that the former has an octet string and the later has a bit string. the first byte of a bit
// string represents the number of bits in the last byte that are to be ignored but, currently,
// bit strings wanting a non-zero amount of bits trimmed are not supported
$public = ASN1::asn1map($decoded[0], Maps\PublicKeyInfo::MAP);
if (is_array($public)) {
if ($public['publicKey'][0] != "\0") {
throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($public['publicKey'][0]));
}
if (is_array(static::OID_NAME)) {
if (!in_array($public['publicKeyAlgorithm']['algorithm'], static::OID_NAME)) {
throw new UnsupportedAlgorithmException($public['publicKeyAlgorithm']['algorithm'] . ' is not a supported key type');
}
} else {
if ($public['publicKeyAlgorithm']['algorithm'] != static::OID_NAME) {
throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $public['publicKeyAlgorithm']['algorithm'] . ' key');
}
}
if (isset($public['publicKeyAlgorithm']['parameters']) && !$public['publicKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][0]['content'][1])) {
$temp = $decoded[0]['content'][0]['content'][1];
$public['publicKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length']));
}
$public['publicKey'] = substr($public['publicKey'], 1);
return $public;
}
throw new \RuntimeException('Unable to parse using either OneAsymmetricKey or PublicKeyInfo ASN1 maps');
}
/**
* Wrap a private key appropriately
*
* @param string $key
* @param string $attr
* @param mixed $params
* @param string $password
* @param string $oid optional
* @param string $publicKey optional
* @param array $options optional
* @return string
*/
protected static function wrapPrivateKey($key, $attr, $params, $password, $oid = null, $publicKey = '', array $options = [])
{
self::initialize_static_variables();
$key = [
'version' => 'v1',
'privateKeyAlgorithm' => [
'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid
],
'privateKey' => $key
];
if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
$key['privateKeyAlgorithm']['parameters'] = $params;
}
if (!empty($attr)) {
$key['attributes'] = $attr;
}
if (!empty($publicKey)) {
$key['version'] = 'v2';
$key['publicKey'] = $publicKey;
}
$key = ASN1::encodeDER($key, Maps\OneAsymmetricKey::MAP);
if (!empty($password) && is_string($password)) {
$salt = Random::string(8);
$iterationCount = isset($options['iterationCount']) ? $options['iterationCount'] : self::$defaultIterationCount;
$encryptionAlgorithm = isset($options['encryptionAlgorithm']) ? $options['encryptionAlgorithm'] : self::$defaultEncryptionAlgorithm;
$encryptionScheme = isset($options['encryptionScheme']) ? $options['encryptionScheme'] : self::$defaultEncryptionScheme;
$prf = isset($options['PRF']) ? $options['PRF'] : self::$defaultPRF;
if ($encryptionAlgorithm == 'id-PBES2') {
$crypto = self::getPBES2EncryptionObject($encryptionScheme);
$hash = str_replace('-', '/', substr($prf, 11));
$kdf = 'pbkdf2';
$iv = Random::string($crypto->getBlockLength() >> 3);
$PBKDF2params = [
'salt' => $salt,
'iterationCount' => $iterationCount,
'prf' => ['algorithm' => $prf, 'parameters' => null]
];
$PBKDF2params = ASN1::encodeDER($PBKDF2params, Maps\PBKDF2params::MAP);
if (!$crypto instanceof RC2) {
$params = ['octetString' => $iv];
} else {
$params = [
'rc2ParametersVersion' => 58,
'iv' => $iv
];
$params = ASN1::encodeDER($params, Maps\RC2CBCParameter::MAP);
$params = new ASN1\Element($params);
}
$params = [
'keyDerivationFunc' => [
'algorithm' => 'id-PBKDF2',
'parameters' => new ASN1\Element($PBKDF2params)
],
'encryptionScheme' => [
'algorithm' => $encryptionScheme,
'parameters' => $params
]
];
$params = ASN1::encodeDER($params, Maps\PBES2params::MAP);
$crypto->setIV($iv);
} else {
$crypto = self::getPBES1EncryptionObject($encryptionAlgorithm);
$hash = self::getPBES1Hash($encryptionAlgorithm);
$kdf = self::getPBES1KDF($encryptionAlgorithm);
$params = [
'salt' => $salt,
'iterationCount' => $iterationCount
];
$params = ASN1::encodeDER($params, Maps\PBEParameter::MAP);
}
$crypto->setPassword($password, $kdf, $hash, $salt, $iterationCount);
$key = $crypto->encrypt($key);
$key = [
'encryptionAlgorithm' => [
'algorithm' => $encryptionAlgorithm,
'parameters' => new ASN1\Element($params)
],
'encryptedData' => $key
];
$key = ASN1::encodeDER($key, Maps\EncryptedPrivateKeyInfo::MAP);
return "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END ENCRYPTED PRIVATE KEY-----";
}
return "-----BEGIN PRIVATE KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END PRIVATE KEY-----";
}
/**
* Wrap a public key appropriately
*
* @param string $key
* @param mixed $params
* @param string $oid
* @return string
*/
protected static function wrapPublicKey($key, $params, $oid = null)
{
self::initialize_static_variables();
$key = [
'publicKeyAlgorithm' => [
'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid
],
'publicKey' => "\0" . $key
];
if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
$key['publicKeyAlgorithm']['parameters'] = $params;
}
$key = ASN1::encodeDER($key, Maps\PublicKeyInfo::MAP);
return "-----BEGIN PUBLIC KEY-----\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
"-----END PUBLIC KEY-----";
}
/**
* Perform some preliminary parsing of the key
*
* @param string $key
* @return array
*/
private static function preParse(&$key)
{
self::initialize_static_variables();
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
if (self::$format != self::MODE_DER) {
$decoded = ASN1::extractBER($key);
if ($decoded !== false) {
$key = $decoded;
} elseif (self::$format == self::MODE_PEM) {
throw new \UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text');
}
}
$decoded = ASN1::decodeBER($key);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER');
}
return $decoded;
}
/**
* Returns the encryption parameters used by the key
*
* @param string $key
* @return array
*/
public static function extractEncryptionAlgorithm($key)
{
$decoded = self::preParse($key);
$r = ASN1::asn1map($decoded[0], ASN1\Maps\EncryptedPrivateKeyInfo::MAP);
if (!is_array($r)) {
throw new \RuntimeException('Unable to parse using EncryptedPrivateKeyInfo map');
}
if ($r['encryptionAlgorithm']['algorithm'] == 'id-PBES2') {
$decoded = ASN1::decodeBER($r['encryptionAlgorithm']['parameters']->element);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER');
}
$r['encryptionAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], ASN1\Maps\PBES2params::MAP);
$kdf = &$r['encryptionAlgorithm']['parameters']['keyDerivationFunc'];
switch ($kdf['algorithm']) {
case 'id-PBKDF2':
$decoded = ASN1::decodeBER($kdf['parameters']->element);
if (!$decoded) {
throw new \RuntimeException('Unable to decode BER');
}
$kdf['parameters'] = ASN1::asn1map($decoded[0], Maps\PBKDF2params::MAP);
}
}
return $r['encryptionAlgorithm'];
}
}
<?php
/**
* PuTTY Formatted Key Handler
*
* See PuTTY's SSHPUBK.C and https://tartarus.org/~simon/putty-snapshots/htmldoc/AppendixC.html
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Keys;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\Hash;
use phpseclib3\Crypt\Random;
use phpseclib3\Exception\UnsupportedAlgorithmException;
/**
* PuTTY Formatted Key Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class PuTTY
{
/**
* Default comment
*
* @var string
*/
private static $comment = 'phpseclib-generated-key';
/**
* Default version
*
* @var int
*/
private static $version = 2;
/**
* Sets the default comment
*
* @param string $comment
*/
public static function setComment($comment)
{
self::$comment = str_replace(["\r", "\n"], '', $comment);
}
/**
* Sets the default version
*
* @param int $version
*/
public static function setVersion($version)
{
if ($version != 2 && $version != 3) {
throw new \RuntimeException('Only supported versions are 2 and 3');
}
self::$version = $version;
}
/**
* Generate a symmetric key for PuTTY v2 keys
*
* @param string $password
* @param int $length
* @return string
*/
private static function generateV2Key($password, $length)
{
$symkey = '';
$sequence = 0;
while (strlen($symkey) < $length) {
$temp = pack('Na*', $sequence++, $password);
$symkey .= Strings::hex2bin(sha1($temp));
}
return substr($symkey, 0, $length);
}
/**
* Generate a symmetric key for PuTTY v3 keys
*
* @param string $password
* @param string $flavour
* @param int $memory
* @param int $passes
* @param string $salt
* @return array
*/
private static function generateV3Key($password, $flavour, $memory, $passes, $salt)
{
if (!function_exists('sodium_crypto_pwhash')) {
throw new \RuntimeException('sodium_crypto_pwhash needs to exist for Argon2 password hasing');
}
switch ($flavour) {
case 'Argon2i':
$flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2I13;
break;
case 'Argon2id':
$flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13;
break;
default:
throw new UnsupportedAlgorithmException('Only Argon2i and Argon2id are supported');
}
$length = 80; // keylen + ivlen + mac_keylen
$temp = sodium_crypto_pwhash($length, $password, $salt, $passes, $memory << 10, $flavour);
$symkey = substr($temp, 0, 32);
$symiv = substr($temp, 32, 16);
$hashkey = substr($temp, -32);
return compact('symkey', 'symiv', 'hashkey');
}
/**
* Break a public or private key down into its constituent components
*
* @param string $key
* @param string $password
* @return array
*/
public static function load($key, $password)
{
if (!Strings::is_stringable($key)) {
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
}
if (strpos($key, 'BEGIN SSH2 PUBLIC KEY') !== false) {
$lines = preg_split('#[\r\n]+#', $key);
switch (true) {
case $lines[0] != '---- BEGIN SSH2 PUBLIC KEY ----':
throw new \UnexpectedValueException('Key doesn\'t start with ---- BEGIN SSH2 PUBLIC KEY ----');
case $lines[count($lines) - 1] != '---- END SSH2 PUBLIC KEY ----':
throw new \UnexpectedValueException('Key doesn\'t end with ---- END SSH2 PUBLIC KEY ----');
}
$lines = array_splice($lines, 1, -1);
$lines = array_map(function ($line) {
return rtrim($line, "\r\n");
}, $lines);
$data = $current = '';
$values = [];
$in_value = false;
foreach ($lines as $line) {
switch (true) {
case preg_match('#^(.*?): (.*)#', $line, $match):
$in_value = $line[strlen($line) - 1] == '\\';
$current = strtolower($match[1]);
$values[$current] = $in_value ? substr($match[2], 0, -1) : $match[2];
break;
case $in_value:
$in_value = $line[strlen($line) - 1] == '\\';
$values[$current] .= $in_value ? substr($line, 0, -1) : $line;
break;
default:
$data .= $line;
}
}
$components = call_user_func([static::PUBLIC_HANDLER, 'load'], $data);
if ($components === false) {
throw new \UnexpectedValueException('Unable to decode public key');
}
$components += $values;
$components['comment'] = str_replace(['\\\\', '\"'], ['\\', '"'], $values['comment']);
return $components;
}
$components = [];
$key = preg_split('#\r\n|\r|\n#', trim($key));
if (Strings::shift($key[0], strlen('PuTTY-User-Key-File-')) != 'PuTTY-User-Key-File-') {
return false;
}
$version = (int) Strings::shift($key[0], 3); // should be either "2: " or "3: 0" prior to int casting
if ($version != 2 && $version != 3) {
throw new \RuntimeException('Only v2 and v3 PuTTY private keys are supported');
}
$components['type'] = $type = rtrim($key[0]);
if (!in_array($type, static::$types)) {
$error = count(static::$types) == 1 ?
'Only ' . static::$types[0] . ' keys are supported. ' :
'';
throw new UnsupportedAlgorithmException($error . 'This is an unsupported ' . $type . ' key');
}
$encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1]));
$components['comment'] = trim(preg_replace('#Comment: (.+)#', '$1', $key[2]));
$publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3]));
$public = Strings::base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength))));
$source = Strings::packSSH2('ssss', $type, $encryption, $components['comment'], $public);
extract(unpack('Nlength', Strings::shift($public, 4)));
$newtype = Strings::shift($public, $length);
if ($newtype != $type) {
throw new \RuntimeException('The binary type does not match the human readable type field');
}
$components['public'] = $public;
switch ($version) {
case 3:
$hashkey = '';
break;
case 2:
$hashkey = 'putty-private-key-file-mac-key';
}
$offset = $publicLength + 4;
switch ($encryption) {
case 'aes256-cbc':
$crypto = new AES('cbc');
switch ($version) {
case 3:
$flavour = trim(preg_replace('#Key-Derivation: (.*)#', '$1', $key[$offset++]));
$memory = trim(preg_replace('#Argon2-Memory: (\d+)#', '$1', $key[$offset++]));
$passes = trim(preg_replace('#Argon2-Passes: (\d+)#', '$1', $key[$offset++]));
$parallelism = trim(preg_replace('#Argon2-Parallelism: (\d+)#', '$1', $key[$offset++]));
$salt = Strings::hex2bin(trim(preg_replace('#Argon2-Salt: ([0-9a-f]+)#', '$1', $key[$offset++])));
extract(self::generateV3Key($password, $flavour, $memory, $passes, $salt));
break;
case 2:
$symkey = self::generateV2Key($password, 32);
$symiv = str_repeat("\0", $crypto->getBlockLength() >> 3);
$hashkey .= $password;
}
}
switch ($version) {
case 3:
$hash = new Hash('sha256');
$hash->setKey($hashkey);
break;
case 2:
$hash = new Hash('sha1');
$hash->setKey(sha1($hashkey, true));
}
$privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$offset++]));
$private = Strings::base64_decode(implode('', array_map('trim', array_slice($key, $offset, $privateLength))));
if ($encryption != 'none') {
$crypto->setKey($symkey);
$crypto->setIV($symiv);
$crypto->disablePadding();
$private = $crypto->decrypt($private);
}
$source .= Strings::packSSH2('s', $private);
$hmac = trim(preg_replace('#Private-MAC: (.+)#', '$1', $key[$offset + $privateLength]));
$hmac = Strings::hex2bin($hmac);
if (!hash_equals($hash->hash($source), $hmac)) {
throw new \UnexpectedValueException('MAC validation error');
}
$components['private'] = $private;
return $components;
}
/**
* Wrap a private key appropriately
*
* @param string $public
* @param string $private
* @param string $type
* @param string $password
* @param array $options optional
* @return string
*/
protected static function wrapPrivateKey($public, $private, $type, $password, array $options = [])
{
$encryption = (!empty($password) || is_string($password)) ? 'aes256-cbc' : 'none';
$comment = isset($options['comment']) ? $options['comment'] : self::$comment;
$version = isset($options['version']) ? $options['version'] : self::$version;
$key = "PuTTY-User-Key-File-$version: $type\r\n";
$key .= "Encryption: $encryption\r\n";
$key .= "Comment: $comment\r\n";
$public = Strings::packSSH2('s', $type) . $public;
$source = Strings::packSSH2('ssss', $type, $encryption, $comment, $public);
$public = Strings::base64_encode($public);
$key .= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n";
$key .= chunk_split($public, 64);
if (empty($password) && !is_string($password)) {
$source .= Strings::packSSH2('s', $private);
switch ($version) {
case 3:
$hash = new Hash('sha256');
$hash->setKey('');
break;
case 2:
$hash = new Hash('sha1');
$hash->setKey(sha1('putty-private-key-file-mac-key', true));
}
} else {
$private .= Random::string(16 - (strlen($private) & 15));
$source .= Strings::packSSH2('s', $private);
$crypto = new AES('cbc');
switch ($version) {
case 3:
$salt = Random::string(16);
$key .= "Key-Derivation: Argon2id\r\n";
$key .= "Argon2-Memory: 8192\r\n";
$key .= "Argon2-Passes: 13\r\n";
$key .= "Argon2-Parallelism: 1\r\n";
$key .= "Argon2-Salt: " . Strings::bin2hex($salt) . "\r\n";
extract(self::generateV3Key($password, 'Argon2id', 8192, 13, $salt));
$hash = new Hash('sha256');
$hash->setKey($hashkey);
break;
case 2:
$symkey = self::generateV2Key($password, 32);
$symiv = str_repeat("\0", $crypto->getBlockLength() >> 3);
$hashkey = 'putty-private-key-file-mac-key' . $password;
$hash = new Hash('sha1');
$hash->setKey(sha1($hashkey, true));
}
$crypto->setKey($symkey);
$crypto->setIV($symiv);
$crypto->disablePadding();
$private = $crypto->encrypt($private);
$mac = $hash->hash($source);
}
$private = Strings::base64_encode($private);
$key .= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n";
$key .= chunk_split($private, 64);
$key .= 'Private-MAC: ' . Strings::bin2hex($hash->hash($source)) . "\r\n";
return $key;
}
/**
* Wrap a public key appropriately
*
* This is basically the format described in RFC 4716 (https://tools.ietf.org/html/rfc4716)
*
* @param string $key
* @param string $type
* @return string
*/
protected static function wrapPublicKey($key, $type)
{
$key = pack('Na*a*', strlen($type), $type, $key);
$key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" .
'Comment: "' . str_replace(['\\', '"'], ['\\\\', '\"'], self::$comment) . "\"\r\n" .
chunk_split(Strings::base64_encode($key), 64) .
'---- END SSH2 PUBLIC KEY ----';
return $key;
}
}
<?php
/**
* Raw Signature Handler
*
* PHP version 5
*
* Handles signatures as arrays
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2016 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common\Formats\Signature;
use phpseclib3\Math\BigInteger;
/**
* Raw Signature Handler
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class Raw
{
/**
* Loads a signature
*
* @param array $sig
* @return array|bool
*/
public static function load($sig)
{
switch (true) {
case !is_array($sig):
case !isset($sig['r']) || !isset($sig['s']):
case !$sig['r'] instanceof BigInteger:
case !$sig['s'] instanceof BigInteger:
return false;
}
return [
'r' => $sig['r'],
's' => $sig['s']
];
}
/**
* Returns a signature in the appropriate format
*
* @param \phpseclib3\Math\BigInteger $r
* @param \phpseclib3\Math\BigInteger $s
* @return string
*/
public static function save(BigInteger $r, BigInteger $s)
{
return compact('r', 's');
}
}
<?php
/**
* PrivateKey interface
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2009 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common;
/**
* PrivateKey interface
*
* @author Jim Wigginton <terrafrost@php.net>
*/
interface PrivateKey
{
public function sign($message);
//public function decrypt($ciphertext);
public function getPublicKey();
public function toString($type, array $options = []);
/**
* @param string|false $password
* @return mixed
*/
public function withPassword($password = false);
}
<?php
/**
* PublicKey interface
*
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2009 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common;
/**
* PublicKey interface
*
* @author Jim Wigginton <terrafrost@php.net>
*/
interface PublicKey
{
public function verify($message, $signature);
//public function encrypt($plaintext);
public function toString($type, array $options = []);
public function getFingerprint($algorithm);
}
<?php
/**
* Base Class for all stream ciphers
*
* PHP version 5
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common;
/**
* Base Class for all stream cipher classes
*
* @author Jim Wigginton <terrafrost@php.net>
*/
abstract class StreamCipher extends SymmetricKey
{
/**
* Block Length of the cipher
*
* Stream ciphers do not have a block size
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::block_size
* @var int
*/
protected $block_size = 0;
/**
* Default Constructor.
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
* @return \phpseclib3\Crypt\Common\StreamCipher
*/
public function __construct()
{
parent::__construct('stream');
}
/**
* Stream ciphers not use an IV
*
* @return bool
*/
public function usesIV()
{
return false;
}
}
<?php
/**
* Base Class for all \phpseclib3\Crypt\* cipher classes
*
* PHP version 5
*
* Internally for phpseclib developers:
* If you plan to add a new cipher class, please note following rules:
*
* - The new \phpseclib3\Crypt\* cipher class should extend \phpseclib3\Crypt\Common\SymmetricKey
*
* - Following methods are then required to be overridden/overloaded:
*
* - encryptBlock()
*
* - decryptBlock()
*
* - setupKey()
*
* - All other methods are optional to be overridden/overloaded
*
* - Look at the source code of the current ciphers how they extend \phpseclib3\Crypt\Common\SymmetricKey
* and take one of them as a start up for the new cipher class.
*
* - Please read all the other comments/notes/hints here also for each class var/method
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
namespace phpseclib3\Crypt\Common;
use phpseclib3\Common\Functions\Strings;
use phpseclib3\Crypt\Blowfish;
use phpseclib3\Crypt\Hash;
use phpseclib3\Exception\BadDecryptionException;
use phpseclib3\Exception\BadModeException;
use phpseclib3\Exception\InconsistentSetupException;
use phpseclib3\Exception\InsufficientSetupException;
use phpseclib3\Exception\UnsupportedAlgorithmException;
use phpseclib3\Math\BigInteger;
use phpseclib3\Math\BinaryField;
use phpseclib3\Math\PrimeField;
/**
* Base Class for all \phpseclib3\Crypt\* cipher classes
*
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
*/
abstract class SymmetricKey
{
/**
* Encrypt / decrypt using the Counter mode.
*
* Set to -1 since that's what Crypt/Random.php uses to index the CTR mode.
*
* @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
*/
const MODE_CTR = -1;
/**
* Encrypt / decrypt using the Electronic Code Book mode.
*
* @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
*/
const MODE_ECB = 1;
/**
* Encrypt / decrypt using the Code Book Chaining mode.
*
* @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
*/
const MODE_CBC = 2;
/**
* Encrypt / decrypt using the Cipher Feedback mode.
*
* @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
*/
const MODE_CFB = 3;
/**
* Encrypt / decrypt using the Cipher Feedback mode (8bit)
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
*/
const MODE_CFB8 = 7;
/**
* Encrypt / decrypt using the Output Feedback mode (8bit)
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
*/
const MODE_OFB8 = 8;
/**
* Encrypt / decrypt using the Output Feedback mode.
*
* @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
*/
const MODE_OFB = 4;
/**
* Encrypt / decrypt using Galois/Counter mode.
*
* @link https://en.wikipedia.org/wiki/Galois/Counter_Mode
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
*/
const MODE_GCM = 5;
/**
* Encrypt / decrypt using streaming mode.
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
* @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
*/
const MODE_STREAM = 6;
/**
* Mode Map
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
*/
const MODE_MAP = [
'ctr' => self::MODE_CTR,
'ecb' => self::MODE_ECB,
'cbc' => self::MODE_CBC,
'cfb' => self::MODE_CFB,
'cfb8' => self::MODE_CFB8,
'ofb' => self::MODE_OFB,
'ofb8' => self::MODE_OFB8,
'gcm' => self::MODE_GCM,
'stream' => self::MODE_STREAM
];
/**
* Base value for the internal implementation $engine switch
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
*/
const ENGINE_INTERNAL = 1;
/**
* Base value for the eval() implementation $engine switch
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
*/
const ENGINE_EVAL = 2;
/**
* Base value for the mcrypt implementation $engine switch
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
*/
const ENGINE_MCRYPT = 3;
/**
* Base value for the openssl implementation $engine switch
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
*/
const ENGINE_OPENSSL = 4;
/**
* Base value for the libsodium implementation $engine switch
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
*/
const ENGINE_LIBSODIUM = 5;
/**
* Base value for the openssl / gcm implementation $engine switch
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
*/
const ENGINE_OPENSSL_GCM = 6;
/**
* Engine Reverse Map
*
* @see \phpseclib3\Crypt\Common\SymmetricKey::getEngine()
*/
const ENGINE_MAP = [
self::ENGINE_INTERNAL => 'PHP',
self::ENGINE_EVAL => 'Eval',
self::ENGINE_MCRYPT => 'mcrypt',
self::ENGINE_OPENSSL => 'OpenSSL',
self::ENGINE_LIBSODIUM => 'libsodium',
self::ENGINE_OPENSSL_GCM => 'OpenSSL (GCM)'
];
/**
* The Encryption Mode
*
* @see self::__construct()
* @var int
*/
protected $mode;
/**
* The Block Length of the block cipher
*
* @var int
*/
protected $block_size = 16;
/**
* The Key
*
* @see self::setKey()
* @var string
*/
protected $key = false;
/**
* HMAC Key
*
* @see self::setupGCM()
* @var ?string
*/
protected $hKey = false;
/**
* The Initialization Vector
*
* @see self::setIV()
* @var string
*/
protected $iv = false;
/**
* A "sliding" Initialization Vector
*
* @see self::enableContinuousBuffer()
* @see self::clearBuffers()
* @var string
*/
protected $encryptIV;
/**
* A "sliding" Initialization Vector
*
* @see self::enableContinuousBuffer()
* @see self::clearBuffers()
* @var string
*/
protected $decryptIV;
/**
* Continuous Buffer status
*
* @see self::enableContinuousBuffer()
* @var bool
*/
protected $continuousBuffer = false;
/**
* Encryption buffer for CTR, OFB and CFB modes
*
* @see self::encrypt()
* @see self::clearBuffers()
* @var array
*/
protected $enbuffer;
/**
* Decryption buffer for CTR, OFB and CFB modes
*
* @see self::decrypt()
* @see self::clearBuffers()
* @var array
*/
protected $debuffer;
/**
* mcrypt resource for encryption
*
* The mcrypt resource can be recreated every time something needs to be created or it can be created just once.
* Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode.
*
* @see self::encrypt()
* @var resource
*/
private $enmcrypt;
/**
* mcrypt resource for decryption
*
* The mcrypt resource can be recreated every time something needs to be created or it can be created just once.
* Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode.
*
* @see self::decrypt()
* @var resource
*/
private $demcrypt;
/**
* Does the enmcrypt resource need to be (re)initialized?
*
* @see \phpseclib3\Crypt\Twofish::setKey()
* @see \phpseclib3\Crypt\Twofish::setIV()
* @var bool
*/
private $enchanged = true;
/**
* Does the demcrypt resource need to be (re)initialized?
*
* @see \phpseclib3\Crypt\Twofish::setKey()
* @see \phpseclib3\Crypt\Twofish::setIV()
* @var bool
*/
private $dechanged = true;
/**
* mcrypt resource for CFB mode
*
* mcrypt's CFB mode, in (and only in) buffered context,
* is broken, so phpseclib implements the CFB mode by it self,
* even when the mcrypt php extension is available.
*
* In order to do the CFB-mode work (fast) phpseclib
* use a separate ECB-mode mcrypt resource.
*
* @link http://phpseclib.sourceforge.net/cfb-demo.phps
* @see self::encrypt()
* @see self::decrypt()
* @see self::setupMcrypt()
* @var resource
*/
private $ecb;
/**
* Optimizing value while CFB-encrypting
*
* Only relevant if $continuousBuffer enabled
* and $engine == self::ENGINE_MCRYPT
*
* It's faster to re-init $enmcrypt if
* $buffer bytes > $cfb_init_len than
* using the $ecb resource furthermore.
*
* This value depends of the chosen cipher
* and the time it would be needed for it's
* initialization [by mcrypt_generic_init()]
* which, typically, depends on the complexity
* on its internaly Key-expanding algorithm.
*
* @see self::encrypt()
* @var int
*/
protected $cfb_init_len = 600;
/**
* Does internal cipher state need to be (re)initialized?
*
* @see self::setKey()
* @see self::setIV()
* @see self::disableContinuousBuffer()
* @var bool
*/
protected $changed = true;
/**
* Does Eval engie need to be (re)initialized?
*
* @see self::setup()
* @var bool
*/
protected $nonIVChanged = true;
/**
* Padding status
*
* @see self::enablePadding()
* @var bool
*/
private $padding = true;
/**
* Is the mode one that is paddable?
*
* @see self::__construct()
* @var bool
*/
private $paddable = false;
/**
* Holds which crypt engine internaly should be use,
* which will be determined automatically on __construct()
*
* Currently available $engines are:
* - self::ENGINE_LIBSODIUM (very fast, php-extension: libsodium, extension_loaded('libsodium') required)
* - self::ENGINE_OPENSSL_GCM (very fast, php-extension: openssl, extension_loaded('openssl') required)
* - self::ENGINE_OPENSSL (very fast, php-extension: openssl, extension_loaded('openssl') required)
* - self::ENGINE_MCRYPT (fast, php-extension: mcrypt, extension_loaded('mcrypt') required)
* - self::ENGINE_EVAL (medium, pure php-engine, no php-extension required)
* - self::ENGINE_INTERNAL (slower, pure php-engine, no php-extension required)
*
* @see self::setEngine()
* @see self::encrypt()
* @see self::decrypt()
* @var int
*/
protected $engine;
/**
* Holds the preferred crypt engine
*
* @see self::setEngine()
* @see self::setPreferredEngine()
* @var int
*/
private $preferredEngine;
/**
* The mcrypt specific name of the cipher
*
* Only used if $engine == self::ENGINE_MCRYPT
*
* @link http://www.php.net/mcrypt_module_open
* @link http://www.php.net/mcrypt_list_algorithms
* @see self::setupMcrypt()
* @var string
*/
protected $cipher_name_mcrypt;
/**
* The openssl specific name of the cipher
*
* Only used if $engine == self::ENGINE_OPENSSL
*
* @link http://www.php.net/openssl-get-cipher-methods
* @var string
*/
protected $cipher_name_openssl;
/**
* The openssl specific name of the cipher in ECB mode
*
* If OpenSSL does not support the mode we're trying to use (CTR)
* it can still be emulated with ECB mode.
*
* @link http://www.php.net/openssl-get-cipher-methods
* @var string
*/
protected $cipher_name_openssl_ecb;
/**
* The default salt used by setPassword()
*
* @see self::setPassword()
* @var string
*/
private $password_default_salt = 'phpseclib/salt';
/**
* The name of the performance-optimized callback function
*
* Used by encrypt() / decrypt()
* only if $engine == self::ENGINE_INTERNAL
*
* @see self::encrypt()
* @see self::decrypt()
* @see self::setupInlineCrypt()
* @var Callback
*/
protected $inline_crypt;
/**
* If OpenSSL can be used in ECB but not in CTR we can emulate CTR
*
* @see self::openssl_ctr_process()
* @var bool
*/
private $openssl_emulate_ctr = false;
/**
* Don't truncate / null pad key
*
* @see self::clearBuffers()
* @var bool
*/
private $skip_key_adjustment = false;
/**
* Has the key length explicitly been set or should it be derived from the key, itself?
*
* @see self::setKeyLength()
* @var bool
*/
protected $explicit_key_length = false;
/**
* Hash subkey for GHASH
*
* @see self::setupGCM()
* @see self::ghash()
* @var BinaryField\Integer
*/
private $h;
/**
* Additional authenticated data
*
* @var string
*/
protected $aad = '';
/**
* Authentication Tag produced after a round of encryption
*
* @var string
*/
protected $newtag = false;
/**
* Authentication Tag to be verified during decryption
*
* @var string
*/
protected $oldtag = false;
/**
* GCM Binary Field
*
* @see self::__construct()
* @see self::ghash()
* @var BinaryField
*/
private static $gcmField;
/**
* Poly1305 Prime Field
*
* @see self::enablePoly1305()
* @see self::poly1305()
* @var PrimeField
*/
private static $poly1305Field;
/**
* Flag for using regular vs "safe" intval
*
* @see self::initialize_static_variables()
* @var boolean
*/
protected static $use_reg_intval;
/**
* Poly1305 Key
*
* @see self::setPoly1305Key()
* @see self::poly1305()
* @var string
*/
protected $poly1305Key;
/**
* Poly1305 Flag
*
* @see self::setPoly1305Key()
* @see self::enablePoly1305()
* @var boolean
*/
protected $usePoly1305 = false;
/**
* The Original Initialization Vector
*
* GCM uses the nonce to build the IV but we want to be able to distinguish between nonce-derived
* IV's and user-set IV's
*
* @see self::setIV()
* @var string
*/
private $origIV = false;
/**
* Nonce
*
* Only used with GCM. We could re-use setIV() but nonce's can be of a different length and
* toggling between GCM and other modes could be more complicated if we re-used setIV()
*
* @see self::setNonce()
* @var string
*/
protected $nonce = false;
/**
* Default Constructor.
*
* $mode could be:
*
* - ecb
*
* - cbc
*
* - ctr
*
* - cfb
*
* - cfb8
*
* - ofb
*
* - ofb8
*
* - gcm
*
* @param string $mode
* @throws BadModeException if an invalid / unsupported mode is provided
*/
public function __construct($mode)
{
$mode = strtolower($mode);
// necessary because of 5.6 compatibility; we can't do isset(self::MODE_MAP[$mode]) in 5.6
$map = self::MODE_MAP;
if (!isset($map[$mode])) {
throw new BadModeException('No valid mode has been specified');
}
$mode = self::MODE_MAP[$mode];
// $mode dependent settings
switch ($mode) {
case self::MODE_ECB:
case self::MODE_CBC:
$this->paddable = true;
break;
case self::MODE_CTR:
case self::MODE_CFB:
case self::MODE_CFB8:
case self::MODE_OFB:
case self::MODE_OFB8:
case self::MODE_STREAM:
$this->paddable = false;
break;
case self::MODE_GCM:
if ($this->block_size != 16) {
throw new BadModeException('GCM is only valid for block ciphers with a block size of 128 bits');
}
if (!isset(self::$gcmField)) {
self::$gcmField = new BinaryField(128, 7, 2, 1, 0);
}
$this->paddable = false;
break;
default:
throw new BadModeException('No valid mode has been specified');
}
$this->mode = $mode;
static::initialize_static_variables();
}
/**
* Initialize static variables
*/
protected static function initialize_static_variables()
{
if (!isset(self::$use_reg_intval)) {
switch (true) {
// PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
case (PHP_OS & "\xDF\xDF\xDF") === 'WIN':
case (php_uname('m') & "\xDF\xDF\xDF") != 'ARM':
case defined('PHP_INT_SIZE') && PHP_INT_SIZE == 8:
self::$use_reg_intval = true;
break;
case (php_uname('m') & "\xDF\xDF\xDF") == 'ARM':
switch (true) {
/* PHP 7.0.0 introduced a bug that affected 32-bit ARM processors:
https://github.com/php/php-src/commit/716da71446ebbd40fa6cf2cea8a4b70f504cc3cd
altho the changelogs make no mention of it, this bug was fixed with this commit:
https://github.com/php/php-src/commit/c1729272b17a1fe893d1a54e423d3b71470f3ee8
affected versions of PHP are: 7.0.x, 7.1.0 - 7.1.23 and 7.2.0 - 7.2.11 */
case PHP_VERSION_ID >= 70000 && PHP_VERSION_ID <= 70123:
case PHP_VERSION_ID >= 70200 && PHP_VERSION_ID <= 70211:
self::$use_reg_intval = false;
break;
default:
self::$use_reg_intval = true;
}
}
}
}
/**
* Sets the initialization vector.
*
* setIV() is not required when ecb or gcm modes are being used.
*
* {@internal Can be overwritten by a sub class, but does not have to be}
*
* @param string $iv
* @throws \LengthException if the IV length isn't equal to the block size
* @throws \BadMethodCallException if an IV is provided when one shouldn't be
*/
public function setIV($iv)
{
if ($this->mode == self::MODE_ECB) {
throw new \BadMethodCallException('This mode does not require an IV.');
}
if ($this->mode == self::MODE_GCM) {
throw new \BadMethodCallException('Use setNonce instead');
}
if (!$this->usesIV()) {
throw new \BadMethodCallException('This algorithm does not use an IV.');
}
if (strlen($iv) != $this->block_size) {
throw new \LengthException('Received initialization vector of size ' . strlen($iv) . ', but size ' . $this->block_size . ' is required');
}
$this->iv = $this->origIV = $iv;
$this->changed = true;
}
/**
* Enables Poly1305 mode.
*
* Once enabled Poly1305 cannot be disabled.
*
* @throws \BadMethodCallException if Poly1305 is enabled whilst in GCM mode
*/
public function enablePoly1305()
{
if ($this->mode == self::MODE_GCM) {
throw new \BadMethodCallException('Poly1305 cannot be used in GCM mode');
}
$this->usePoly1305 = true;
}
/**
* Enables Poly1305 mode.
*
* Once enabled Poly1305 cannot be disabled. If $key is not passed then an attempt to call createPoly1305Key
* will be made.
*
* @param string $key optional
* @throws \LengthException if the key isn't long enough
* @throws \BadMethodCallException if Poly1305 is enabled whilst in GCM mode
*/
public function setPoly1305Key($key = null)
{
if ($this->mode == self::MODE_GCM) {
throw new \BadMethodCallException('Poly1305 cannot be used in GCM mode');
}
if (!is_string($key) || strlen($key) != 32) {
throw new \LengthException('The Poly1305 key must be 32 bytes long (256 bits)');
}
if (!isset(self::$poly1305Field)) {
// 2^130-5
self::$poly1305Field = new PrimeField(new BigInteger('3fffffffffffffffffffffffffffffffb', 16));
}
$this->poly1305Key = $key;
$this->usePoly1305 = true;
}
/**
* Sets the nonce.
*
* setNonce() is only required when gcm is used
*
* @param string $nonce
* @throws \BadMethodCallException if an nonce is provided when one shouldn't be
*/
public function setNonce($nonce)
{
if ($this->mode != self::MODE_GCM) {
throw new \BadMethodCallException('Nonces are only used in GCM mode.');
}
$this->nonce = $nonce;
$this->setEngine();
}
/**
* Sets additional authenticated data
*
* setAAD() is only used by gcm or in poly1305 mode
*
* @param string $aad
* @throws \BadMethodCallException if mode isn't GCM or if poly1305 isn't being utilized
*/
public function setAAD($aad)
{
if ($this->mode != self::MODE_GCM && !$this->usePoly1305) {
throw new \BadMethodCallException('Additional authenticated data is only utilized in GCM mode or with Poly1305');
}
$this->aad = $aad;
}
/**
* Returns whether or not the algorithm uses an IV
*
* @return bool
*/
public function usesIV()
{
return $this->mode != self::MODE_GCM && $this->mode != self::MODE_ECB;
}
/**
* Returns whether or not the algorithm uses a nonce
*
* @return bool
*/
public function usesNonce()
{
return $this->mode == self::MODE_GCM;
}
/**
* Returns the current key length in bits
*
* @return int
*/
public function getKeyLength()
{
return $this->key_length << 3;
}
/**
* Returns the current block length in bits
*
* @return int
*/
public function getBlockLength()
{
return $this->block_size << 3;
}
/**
* Returns the current block length in bytes
*
* @return int
*/
public function getBlockLengthInBytes()
{
return $this->block_size;
}
/**
* Sets the key length.
*
* Keys with explicitly set lengths need to be treated accordingly
*
* @param int $length
*/
public function setKeyLength($length)
{
$this->explicit_key_length = $length >> 3;
if (is_string($this->key) && strlen($this->key) != $this->explicit_key_length) {
$this->key = false;
throw new InconsistentSetupException('Key has already been set and is not ' . $this->explicit_key_length . ' bytes long');
}
}
/**
* Sets the key.
*
* The min/max length(s) of the key depends on the cipher which is used.
* If the key not fits the length(s) of the cipher it will paded with null bytes
* up to the closest valid key length. If the key is more than max length,
* we trim the excess bits.
*
* If the key is not explicitly set, it'll be assumed to be all null bytes.
*
* {@internal Could, but not must, extend by the child Crypt_* class}
*
* @param string $key
*/
public function setKey($key)
{
if ($this->explicit_key_length !== false && strlen($key) != $this->explicit_key_length) {
throw new InconsistentSetupException('Key length has already been set to ' . $this->explicit_key_length . ' bytes and this key is ' . strlen($key) . ' bytes');
}
$this->key = $key;
$this->key_length = strlen($key);
$this->setEngine();
}
/**
* Sets the password.
*
* Depending on what $method is set to, setPassword()'s (optional) parameters are as follows:
* {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2} or pbkdf1:
* $hash, $salt, $count, $dkLen
*
* Where $hash (default = sha1) currently supports the following hashes: see: Crypt/Hash.php
* {@link https://en.wikipedia.org/wiki/Bcrypt bcypt}:
* $salt, $rounds, $keylen
*
* This is a modified version of bcrypt used by OpenSSH.
*
* {@internal Could, but not must, extend by the child Crypt_* class}
*
* @see Crypt/Hash.php
* @param string $password
* @param string $method
* @param string[] ...$func_args
* @throws \LengthException if pbkdf1 is being used and the derived key length exceeds the hash length
* @throws \RuntimeException if bcrypt is being used and a salt isn't provided
* @return bool
*/
public function setPassword($password, $method = 'pbkdf2', ...$func_args)
{
$key = '';
$method = strtolower($method);
switch ($method) {
case 'bcrypt':
if (!isset($func_args[2])) {
throw new \RuntimeException('A salt must be provided for bcrypt to work');
}
$salt = $func_args[0];
$rounds = isset($func_args[1]) ? $func_args[1] : 16;
$keylen = isset($func_args[2]) ? $func_args[2] : $this->key_length;
$key = Blowfish::bcrypt_pbkdf($password, $salt, $keylen + $this->block_size, $rounds);
$this->setKey(substr($key, 0, $keylen));
$this->setIV(substr($key, $keylen));
return true;
case 'pkcs12': // from https://tools.ietf.org/html/rfc7292#appendix-B.2
case 'pbkdf1':
case 'pbkdf2':
// Hash function
$hash = isset($func_args[0]) ? strtolower($func_args[0]) : 'sha1';
$hashObj = new Hash();
$hashObj->setHash($hash);
// WPA and WPA2 use the SSID as the salt
$salt = isset($func_args[1]) ? $func_args[1] : $this->password_default_salt;
// RFC2898#section-4.2 uses 1,000 iterations by default
// WPA and WPA2 use 4,096.
$count = isset($func_args[2]) ? $func_args[2] : 1000;
// Keylength
if (isset($func_args[3])) {
if ($func_args[3] <= 0) {
throw new \LengthException('Derived key length cannot be longer 0 or less');
}
$dkLen = $func_args[3];
} else {
$key_length = $this->explicit_key_length !== false ? $this->explicit_key_length : $this->key_length;
$dkLen = $method == 'pbkdf1' ? 2 * $key_length : $key_length;
}
switch (true) {
case $method == 'pkcs12':
/*
In this specification, however, all passwords are created from
BMPStrings with a NULL terminator. This means that each character in
the original BMPString is encoded in 2 bytes in big-endian format
(most-significant byte first). There are no Unicode byte order
marks. The 2 bytes produced from the last character in the BMPString
are followed by 2 additional bytes with the value 0x00.
-- https://tools.ietf.org/html/rfc7292#appendix-B.1
*/
$password = "\0" . chunk_split($password, 1, "\0") . "\0";
/*
This standard specifies 3 different values for the ID byte mentioned
above:
1. If ID=1, then the pseudorandom bits being produced are to be used
as key material for performing encryption or decryption.
2. If ID=2, then the pseudorandom bits being produced are to be used
as an IV (Initial Value) for encryption or decryption.
3. If ID=3, then the pseudorandom bits being produced are to be used
as an integrity key for MACing.
*/
// Construct a string, D (the "diversifier"), by concatenating v/8
// copies of ID.
$blockLength = $hashObj->getBlockLengthInBytes();
$d1 = str_repeat(chr(1), $blockLength);
$d2 = str_repeat(chr(2), $blockLength);
$s = '';
if (strlen($salt)) {
while (strlen($s) < $blockLength) {
$s .= $salt;
}
}
$s = substr($s, 0, $blockLength);
$p = '';
if (strlen($password)) {
while (strlen($p) < $blockLength) {
$p .= $password;
}
}
$p = substr($p, 0, $blockLength);
$i = $s . $p;
$this->setKey(self::pkcs12helper($dkLen, $hashObj, $i, $d1, $count));
if ($this->usesIV()) {
$this->setIV(self::pkcs12helper($this->block_size, $hashObj, $i, $d2, $count));
}
return true;
case $method == 'pbkdf1':
if ($dkLen > $hashObj->getLengthInBytes()) {
throw new \LengthException('Derived key length cannot be longer than the hash length');
}
$t = $password . $salt;
for ($i = 0; $i < $count; ++$i) {
$t = $hashObj->hash($t);
}
$key = substr($t, 0, $dkLen);
$this->setKey(substr($key, 0, $dkLen >> 1));
if ($this->usesIV()) {
$this->setIV(substr($key, $dkLen >> 1));
}
return true;
case !in_array($hash, hash_algos()):
$i = 1;
$hashObj->setKey($password);
while (strlen($key) < $dkLen) {
$f = $u = $hashObj->hash($salt . pack('N', $i++));
for ($j = 2; $j <= $count; ++$j) {
$u = $hashObj->hash($u);
$f ^= $u;
}
$key .= $f;
}
$key = substr($key, 0, $dkLen);
break;
default:
$key = hash_pbkdf2($hash, $password, $salt, $count, $dkLen, true);
}
break;
default:
throw new UnsupportedAlgorithmException($method . ' is not a supported password hashing method');
}
$this->setKey($key);
return true;
}
/**
* PKCS#12 KDF Helper Function
*
* As discussed here:
*
* {@link https://tools.ietf.org/html/rfc7292#appendix-B}
*
* @see self::setPassword()
* @param int $n
* @param \phpseclib3\Crypt\Hash $hashObj
* @param string $i
* @param string $d
* @param int $count
* @return string $a
*/
private static function pkcs12helper($n, $hashObj, $i, $d, $count)
{
static $one;
if (!isset($one)) {
$one = new BigInteger(1);
}
$blockLength = $hashObj->getBlockLength() >> 3;
$c = ceil($n / $hashObj->getLengthInBytes());
$a = '';
for ($j = 1; $j <= $c; $j++) {
$ai = $d . $i;
for ($k = 0; $k < $count; $k++) {
$ai = $hashObj->hash($ai);
}
$b = '';
while (strlen($b) < $blockLength) {
$b .= $ai;
}
$b = substr($b, 0, $blockLength);
$b = new BigInteger($b, 256);
$newi = '';
for ($k = 0; $k < strlen($i); $k += $blockLength) {
$temp = substr($i, $k, $blockLength);
$temp = new BigInteger($temp, 256);
$temp->setPrecision($blockLength << 3);
$temp = $temp->add($b);
$temp = $temp->add($one);
$newi .= $temp->toBytes(false);
}
$i = $newi;
$a .= $ai;
}
return substr($a, 0, $n);
}
/**
* Encrypts a message.
*
* $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other cipher
* implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's
* necessary are discussed in the following
* URL:
*
* {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html}
*
* An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does.
* strlen($plaintext) will still need to be a multiple of the block size, however, arbitrary values can be added to make it that
* length.
*
* {@internal Could, but not must, extend by the child Crypt_* class}
*
* @see self::decrypt()
* @param string $plaintext
* @return string $ciphertext
*/
public function encrypt($plaintext)
{
if ($this->paddable) {
$plaintext = $this->pad($plaintext);
}
$this->setup();
if ($this->mode == self::MODE_GCM) {
$oldIV = $this->iv;
Strings::increment_str($this->iv);
$cipher = new static('ctr');
$cipher->setKey($this->key);
$cipher->setIV($this->iv);
$ciphertext = $cipher->encrypt($plaintext);
$s = $this->ghash(
self::nullPad128($this->aad) .
self::nullPad128($ciphertext) .
self::len64($this->aad) .
self::len64($ciphertext)
);
$cipher->encryptIV = $this->iv = $this->encryptIV = $this->decryptIV = $oldIV;
$this->newtag = $cipher->encrypt($s);
return $ciphertext;
}
if (isset($this->poly1305Key)) {
$cipher = clone $this;
unset($cipher->poly1305Key);
$this->usePoly1305 = false;
$ciphertext = $cipher->encrypt($plaintext);
$this->newtag = $this->poly1305($ciphertext);
return $ciphertext;
}
if ($this->engine === self::ENGINE_OPENSSL) {
switch ($this->mode) {
case self::MODE_STREAM:
return openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
case self::MODE_ECB:
return openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
case self::MODE_CBC:
$result = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->encryptIV);
if ($this->continuousBuffer) {
$this->encryptIV = substr($result, -$this->block_size);
}
return $result;
case self::MODE_CTR:
return $this->openssl_ctr_process($plaintext, $this->encryptIV, $this->enbuffer);
case self::MODE_CFB:
// cfb loosely routines inspired by openssl's:
// {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
$ciphertext = '';
if ($this->continuousBuffer) {
$iv = &$this->encryptIV;
$pos = &$this->enbuffer['pos'];
} else {
$iv = $this->encryptIV;
$pos = 0;
}
$len = strlen($plaintext);
$i = 0;
if ($pos) {
$orig_pos = $pos;
$max = $this->block_size - $pos;
if ($len >= $max) {
$i = $max;
$len -= $max;
$pos = 0;
} else {
$i = $len;
$pos += $len;
$len = 0;
}
// ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
$ciphertext = substr($iv, $orig_pos) ^ $plaintext;
$iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
$plaintext = substr($plaintext, $i);
}
$overflow = $len % $this->block_size;
if ($overflow) {
$ciphertext .= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
$iv = Strings::pop($ciphertext, $this->block_size);
$size = $len - $overflow;
$block = $iv ^ substr($plaintext, -$overflow);
$iv = substr_replace($iv, $block, 0, $overflow);
$ciphertext .= $block;
$pos = $overflow;
} elseif ($len) {
$ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
$iv = substr($ciphertext, -$this->block_size);
}
return $ciphertext;
case self::MODE_CFB8:
$ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->encryptIV);
if ($this->continuousBuffer) {
if (($len = strlen($ciphertext)) >= $this->block_size) {
$this->encryptIV = substr($ciphertext, -$this->block_size);
} else {
$this->encryptIV = substr($this->encryptIV, $len - $this->block_size) . substr($ciphertext, -$len);
}
}
return $ciphertext;
case self::MODE_OFB8:
$ciphertext = '';
$len = strlen($plaintext);
$iv = $this->encryptIV;
for ($i = 0; $i < $len; ++$i) {
$xor = openssl_encrypt($iv, $this->cipher_name_openssl_ecb, $this->key, $this->openssl_options, $this->decryptIV);
$ciphertext .= $plaintext[$i] ^ $xor;
$iv = substr($iv, 1) . $xor[0];
}
if ($this->continuousBuffer) {
$this->encryptIV = $iv;
}
break;
case self::MODE_OFB:
return $this->openssl_ofb_process($plaintext, $this->encryptIV, $this->enbuffer);
}
}
if ($this->engine === self::ENGINE_MCRYPT) {
set_error_handler(function () {
});
if ($this->enchanged) {
mcrypt_generic_init($this->enmcrypt, $this->key, $this->getIV($this->encryptIV));
$this->enchanged = false;
}
// re: {@link http://phpseclib.sourceforge.net/cfb-demo.phps}
// using mcrypt's default handing of CFB the above would output two different things. using phpseclib's
// rewritten CFB implementation the above outputs the same thing twice.
if ($this->mode == self::MODE_CFB && $this->continuousBuffer) {
$block_size = $this->block_size;
$iv = &$this->encryptIV;
$pos = &$this->enbuffer['pos'];
$len = strlen($plaintext);
$ciphertext = '';
$i = 0;
if ($pos) {
$orig_pos = $pos;
$max = $block_size - $pos;
if ($len >= $max) {
$i = $max;
$len -= $max;
$pos = 0;
} else {
$i = $len;
$pos += $len;
$len = 0;
}
$ciphertext = substr($iv, $orig_pos) ^ $plaintext;
$iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
$this->enbuffer['enmcrypt_init'] = true;
}
if ($len >= $block_size) {
if ($this->enbuffer['enmcrypt_init'] === false || $len > $this->cfb_init_len) {
if ($this->enbuffer['enmcrypt_init'] === true) {
mcrypt_generic_init($this->enmcrypt, $this->key, $iv);
$this->enbuffer['enmcrypt_init'] = false;
}
$ciphertext .= mcrypt_generic($this->enmcrypt, substr($plaintext, $i, $len - $len % $block_size));
$iv = substr($ciphertext, -$block_size);
$len %= $block_size;
} else {
while ($len >= $block_size) {
$iv = mcrypt_generic($this->ecb, $iv) ^ substr($plaintext, $i, $block_size);
$ciphertext .= $iv;
$len -= $block_size;
$i += $block_size;
}
}
}
if ($len) {
$iv = mcrypt_generic($this->ecb, $iv);
$block = $iv ^ substr($plaintext, -$len);
$iv = substr_replace($iv, $block, 0, $len);
$ciphertext .= $block;
$pos = $len;
}
restore_error_handler();
return $ciphertext;
}
$ciphertext = mcrypt_generic($this->enmcrypt, $plaintext);
if (!$this->continuousBuffer) {
mcrypt_generic_init($this->enmcrypt, $this->key, $this->getIV($this->encryptIV));
}
restore_error_handler();
return $ciphertext;
}
if ($this->engine === self::ENGINE_EVAL) {
$inline = $this->inline_crypt;
return $inline('encrypt', $plaintext);
}
$buffer = &$this->enbuffer;
$block_size = $this->block_size;
$ciphertext = '';
switch ($this->mode) {
case self::MODE_ECB:
for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
$ciphertext .= $this->encryptBlock(substr($plaintext, $i, $block_size));
}
break;
case self::MODE_CBC:
$xor = $this->encryptIV;
for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
$block = substr($plaintext, $i, $block_size);
$block = $this->encryptBlock($block ^ $xor);
$xor = $block;
$ciphertext .= $block;
}
if ($this->continuousBuffer) {
$this->encryptIV = $xor;
}
break;
case self::MODE_CTR:
$xor = $this->encryptIV;
if (strlen($buffer['ciphertext'])) {
for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
$block = substr($plaintext, $i, $block_size);
if (strlen($block) > strlen($buffer['ciphertext'])) {
$buffer['ciphertext'] .= $this->encryptBlock($xor);
Strings::increment_str($xor);
}
$key = Strings::shift($buffer['ciphertext'], $block_size);
$ciphertext .= $block ^ $key;
}
} else {
for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
$block = substr($plaintext, $i, $block_size);
$key = $this->encryptBlock($xor);
Strings::increment_str($xor);
$ciphertext .= $block ^ $key;
}
}
if ($this->continuousBuffer) {
$this->encryptIV = $xor;
if ($start = strlen($plaintext) % $block_size) {
$buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext'];
}
}
break;
case self::MODE_CFB:
// cfb loosely routines inspired by openssl's:
// {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
if ($this->continuousBuffer) {
$iv = &$this->encryptIV;
$pos = &$buffer['pos'];
} else {
$iv = $this->encryptIV;
$pos = 0;
}
$len = strlen($plaintext);
$i = 0;
if ($pos) {
$orig_pos = $pos;
$max = $block_size - $pos;
if ($len >= $max) {
$i = $max;
$len -= $max;
$pos = 0;
} else {
$i = $len;
$pos += $len;
$len = 0;
}
// ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
$ciphertext = substr($iv, $orig_pos) ^ $plaintext;
$iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
}
while ($len >= $block_size) {
$iv = $this->encryptBlock($iv) ^ substr($plaintext, $i, $block_size);
$ciphertext .= $iv;
$len -= $block_size;
$i += $block_size;
}
if ($len) {
$iv = $this->encryptBlock($iv);
$block = $iv ^ substr($plaintext, $i);
$iv = substr_replace($iv, $block, 0, $len);
$ciphertext .= $block;
$pos = $len;
}
break;
case self::MODE_CFB8:
$ciphertext = '';
$len = strlen($plaintext);
$iv = $this->encryptIV;
for ($i = 0; $i < $len; ++$i) {
$ciphertext .= ($c = $plaintext[$i] ^ $this->encryptBlock($iv));
$iv = substr($iv, 1) . $c;
}
if ($this->continuousBuffer) {
if ($len >= $block_size) {
$this->encryptIV = substr($ciphertext, -$block_size);
} else {
$this->encryptIV = substr($this->encryptIV, $len - $block_size) . substr($ciphertext, -$len);
}
}
break;
case self::MODE_OFB8:
$ciphertext = '';
$len = strlen($plaintext);
$iv = $this->encryptIV;
for ($i = 0; $i < $len; ++$i) {
$xor = $this->encryptBlock($iv);
$ciphertext .= $plaintext[$i] ^ $xor;
$iv = substr($iv, 1) . $xor[0];
}
if ($this->continuousBuffer) {
$this->encryptIV = $iv;
}
break;
case self::MODE_OFB:
$xor = $this->encryptIV;
if (strlen($buffer['xor'])) {
for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
$block = substr($plaintext, $i, $block_size);
if (strlen($block) > strlen($buffer['xor'])) {
$xor = $this->encryptBlock($xor);
$buffer['xor'] .= $xor;
}
$key = Strings::shift($buffer['xor'], $block_size);
$ciphertext .= $block ^ $key;
}
} else {
for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
$xor = $this->encryptBlock($xor);
$ciphertext .= substr($plaintext, $i, $block_size) ^ $xor;
}
$key = $xor;
}
if ($this->continuousBuffer) {
$this->encryptIV = $xor;
if ($start = strlen($plaintext) % $block_size) {
$buffer['xor'] = substr($key, $start) . $buffer['xor'];
}
}
break;
case self::MODE_STREAM:
$ciphertext = $this->encryptBlock($plaintext);
break;
}
return $ciphertext;
}
/**
* Decrypts a message.
*
* If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until
* it is.
*
* {@internal Could, but not must, extend by the child Crypt_* class}
*
* @see self::encrypt()
* @param string $ciphertext
* @return string $plaintext
* @throws \LengthException if we're inside a block cipher and the ciphertext length is not a multiple of the block size
*/
public function decrypt($ciphertext)
{
if ($this->paddable && strlen($ciphertext) % $this->block_size) {
throw new \LengthException('The ciphertext length (' . strlen($ciphertext) . ') needs to be a multiple of the block size (' . $this->block_size . ')');
}
$this->setup();
if ($this->mode == self::MODE_GCM || isset($this->poly1305Key)) {
if ($this->oldtag === false) {
throw new InsufficientSetupException('Authentication Tag has not been set');
}
if (isset($this->poly1305Key)) {
$newtag = $this->poly1305($ciphertext);
} else {
$oldIV = $this->iv;
Strings::increment_str($this->iv);
$cipher = new static('ctr');
$cipher->setKey($this->key);
$cipher->setIV($this->iv);
$plaintext = $cipher->decrypt($ciphertext);
$s = $this->ghash(
self::nullPad128($this->aad) .
self::nullPad128($ciphertext) .
self::len64($this->aad) .
self::len64($ciphertext)
);
$cipher->encryptIV = $this->iv = $this->encryptIV = $this->decryptIV = $oldIV;
$newtag = $cipher->encrypt($s);
}
if ($this->oldtag != substr($newtag, 0, strlen($newtag))) {
$cipher = clone $this;
unset($cipher->poly1305Key);
$this->usePoly1305 = false;
$plaintext = $cipher->decrypt($ciphertext);
$this->oldtag = false;
throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
}
$this->oldtag = false;
return $plaintext;
}
if ($this->engine === self::ENGINE_OPENSSL) {
switch ($this->mode) {
case self::MODE_STREAM:
$plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
break;
case self::MODE_ECB:
$plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
break;
case self::MODE_CBC:
$offset = $this->block_size;
$plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->decryptIV);
if ($this->continuousBuffer) {
$this->decryptIV = substr($ciphertext, -$offset, $this->block_size);
}
break;
case self::MODE_CTR:
$plaintext = $this->openssl_ctr_process($ciphertext, $this->decryptIV, $this->debuffer);
break;
case self::MODE_CFB:
// cfb loosely routines inspired by openssl's:
// {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
$plaintext = '';
if ($this->continuousBuffer) {
$iv = &$this->decryptIV;
$pos = &$this->debuffer['pos'];
} else {
$iv = $this->decryptIV;
$pos = 0;
}
$len = strlen($ciphertext);
$i = 0;
if ($pos) {
$orig_pos = $pos;
$max = $this->block_size - $pos;
if ($len >= $max) {
$i = $max;
$len -= $max;
$pos = 0;
} else {
$i = $len;
$pos += $len;
$len = 0;
}
// ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $this->blocksize
$plaintext = substr($iv, $orig_pos) ^ $ciphertext;
$iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
$ciphertext = substr($ciphertext, $i);
}
$overflow = $len % $this->block_size;
if ($overflow) {
$plaintext .= openssl_decrypt(substr($ciphertext, 0, -$overflow), $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
if ($len - $overflow) {
$iv = substr($ciphertext, -$overflow - $this->block_size, -$overflow);
}
$iv = openssl_encrypt(str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
$plaintext .= $iv ^ substr($ciphertext, -$overflow);
$iv = substr_replace($iv, substr($ciphertext, -$overflow), 0, $overflow);
$pos = $overflow;
} elseif ($len) {
$plaintext .= openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
$iv = substr($ciphertext, -$this->block_size);
}
break;
case self::MODE_CFB8:
$plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->decryptIV);
if ($this->continuousBuffer) {
if (($len = strlen($ciphertext)) >= $this->block_size) {
$this->decryptIV = substr($ciphertext, -$this->block_size);
} else {
$this->decryptIV = substr($this->decryptIV, $len - $this->block_size) . substr($ciphertext, -$len);
}
}
break;
case self::MODE_OFB8:
$plaintext = '';
$len = strlen($ciphertext);
$iv = $this->decryptIV;
for ($i = 0; $i < $len; ++$i) {
$xor = openssl_encrypt($iv, $this->cipher_name_openssl_ecb, $this->key, $this->openssl_options, $this->decryptIV);
$plaintext .= $ciphertext[$i] ^ $xor;
$iv = substr($iv, 1) . $xor[0];
}
if ($this->continuousBuffer) {
$this->decryptIV = $iv;
}
break;
case self::MODE_OFB:
$plaintext = $this->openssl_ofb_process($ciphertext, $this->decryptIV, $this->debuffer);
}
return $this->paddable ? $this->unpad($plaintext) : $plaintext;
}
if ($this->engine === self::ENGINE_MCRYPT) {
set_error_handler(function () {
});
$block_size = $this->block_size;
if ($this->dechanged) {
mcrypt_generic_init($this->demcrypt, $this->key, $this->getIV($this->decryptIV));
$this->dechanged = false;
}
if ($this->mode == self::MODE_CFB && $this->continuousBuffer) {
$iv = &$this->decryptIV;
$pos = &$this->debuffer['pos'];
$len = strlen($ciphertext);
$plaintext = '';
$i = 0;
if ($pos) {
$orig_pos = $pos;
$max = $block_size - $pos;
if ($len >= $max) {
$i = $max;
$len -= $max;
$pos = 0;
} else {
$i = $len;
$pos += $len;
$len = 0;
}
// ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
$plaintext = substr($iv, $orig_pos) ^ $ciphertext;
$iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
}
if ($len >= $block_size) {
$cb = substr($ciphertext, $i, $len - $len % $block_size);
$plaintext .= mcrypt_generic($this->ecb, $iv . $cb) ^ $cb;
$iv = substr($cb, -$block_size);
$len %= $block_size;
}
if ($len) {
$iv = mcrypt_generic($this->ecb, $iv);
$plaintext .= $iv ^ substr($ciphertext, -$len);
$iv = substr_replace($iv, substr($ciphertext, -$len), 0, $len);
$pos = $len;
}
restore_error_handler();
return $plaintext;
}
$plaintext = mdecrypt_generic($this->demcrypt, $ciphertext);
if (!$this->continuousBuffer) {
mcrypt_generic_init($this->demcrypt, $this->key, $this->getIV($this->decryptIV));
}
restore_error_handler();
return $this->paddable ? $this->unpad($plaintext) : $plaintext;
}
if ($this->engine === self::ENGINE_EVAL) {
$inline = $this->inline_crypt;
return $inline('decrypt', $ciphertext);
}
$block_size = $this->block_size;
$buffer = &$this->debuffer;
$plaintext = '';
switch ($this->mode) {
case self::MODE_ECB:
for ($i = 0; $i < strlen($ciphertext); $i += $block_size) {
$plaintext .= $this->decryptBlock(substr($ciphertext, $i, $block_size));
}
break;
case self::MODE_CBC:
$xor = $this->decryptIV;
for ($i = 0; $i < strlen($ciphertext); $i += $block_size) {
$block = substr($ciphertext, $i, $block_size);
$plaintext .= $this->decryptBlock($block) ^ $xor;
$xor = $block;
}
if ($this->continuousBuffer) {
$this->decryptIV = $xor;
}
break;
case self::MODE_CTR:
$xor = $this->decryptIV;
if (strlen($buffer['ciphertext'])) {
for ($i = 0; $i < strlen($ciphertext); $i += $block_size) {
$block = substr($ciphertext, $i, $block_size);
if (strlen($block) > strlen($buffer['ciphertext'])) {
$buffer['ciphertext'] .= $this->encryptBlock($xor);
Strings::increment_str($xor);
}
$key = Strings::shift($buffer['ciphertext'], $block_size);
$plaintext .= $block ^ $key;
}
} else {
for ($i = 0; $i < strlen($ciphertext); $i += $block_size) {
$block = substr($ciphertext, $i, $block_size);
$key = $this->encryptBlock($xor);
Strings::increment_str($xor);
$plaintext .= $block ^ $key;
}
}
if ($this->continuousBuffer) {
$this->decryptIV = $xor;
if ($start = strlen($ciphertext) % $block_size) {
$buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext'];
}
}
break;
case self::MODE_CFB:
if ($this->continuousBuffer) {
$iv = &$this->decryptIV;
$pos = &$buffer['pos'];
} else {
$iv = $this->decryptIV;
$pos = 0;
}
$len = strlen($ciphertext);
$i = 0;
if ($pos) {
$orig_pos = $pos;
$max = $block_size - $pos;
if ($len >= $max) {
$i = $max;
$len -= $max;
$pos = 0;
} else {
$i = $len;
$pos += $len;
$len = 0;
}
// ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
$plaintext = substr($iv, $orig_pos) ^ $ciphertext;
$iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
}
while ($len >= $block_size) {
$iv = $this->encryptBlock($iv);
$cb = substr($ciphertext, $i, $block_size);
$plaintext .= $iv ^ $cb;
$iv = $cb;
$len -= $block_size;
$i += $block_size;
}
if ($len) {
$iv = $this->encryptBlock($iv);
$plaintext .= $iv ^ substr($ciphertext, $i);
$iv = substr_replace($iv, substr($ciphertext, $i), 0, $len);
$pos = $len;
}
break;
case self::MODE_CFB8:
$plaintext = '';
$len = strlen($ciphertext);
$iv = $this->decryptIV;
for ($i = 0; $i < $len; ++$i) {
$plaintext .= $ciphertext[$i] ^ $this->encryptBlock($iv);
$iv = substr($iv, 1) . $ciphertext[$i];
}
if ($this->continuousBuffer) {
if ($len >= $block_size) {
$this->decryptIV = substr($ciphertext, -$block_size);
} else {
$this->decryptIV = substr($this->decryptIV, $len - $block_size) . substr($ciphertext, -$len);
}
}
break;
case self::MODE_OFB8:
$plaintext = '';
$len = strlen($ciphertext);
$iv = $this->decryptIV;
for ($i = 0; $i < $len; ++$i) {
$xor = $this->encryptBlock($iv);
$plaintext .= $ciphertext[$i] ^ $xor;
$iv = substr($iv, 1) . $xor[0];
}
if ($this->continuousBuffer) {
$this->decryptIV = $iv;
}
break;
case self::MODE_OFB:
$xor = $this->decryptIV;
if (strlen($buffer['xor'])) {
for ($i = 0; $i < strlen($ciphertext); $i += $block_size) {
$block = substr($ciphertext, $i, $block_size);
if (strlen($block) > strlen($buffer['xor'])) {
$xor = $this->encryptBlock($xor);
$buffer['xor'] .= $xor;
}
$key = Strings::shift($buffer['xor'], $block_size);
$plaintext .= $block ^ $key;
}
} else {
for ($i = 0; $i < strlen($ciphertext); $i += $block_size) {
$xor = $this->encryptBlock($xor);
$plaintext .= substr($ciphertext, $i, $block_size) ^ $xor;
}
$key = $xor;
}
if ($this->continuousBuffer) {
$this->decryptIV = $xor;
if ($start = strlen($ciphertext) % $block_size) {
$buffer['xor'] = substr($key, $start) . $buffer['xor'];
}
}
break;
case self::MODE_STREAM:
$plaintext = $this->decryptBlock($ciphertext);
break;
}
return $this->paddable ? $this->unpad($plaintext) : $plaintext;
}
/**
* Get the authentication tag
*
* Only used in GCM or Poly1305 mode
*
* @see self::encrypt()
* @param int $length optional
* @return string
* @throws \LengthException if $length isn't of a sufficient length
* @throws \RuntimeException if GCM mode isn't being used
*/
public function getTag($length = 16)
{
if ($this->mode != self::MODE_GCM && !$this->usePoly1305) {
throw new \BadMethodCallException('Authentication tags are only utilized in GCM mode or with Poly1305');
}
if ($this->newtag === false) {
throw new \BadMethodCallException('A tag can only be returned after a round of encryption has been performed');
}
// the tag is 128-bits. it can't be greater than 16 bytes because that's bigger than the tag is. if it
// were 0 you might as well be doing CTR and less than 4 provides minimal security that could be trivially
// easily brute forced.
// see https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=36
// for more info
if ($length < 4 || $length > 16) {
throw new \LengthException('The authentication tag must be between 4 and 16 bytes long');
}
return $length == 16 ?
$this->newtag :
substr($this->newtag, 0, $length);
}
/**
* Sets the authentication tag
*
* Only used in GCM mode
*
* @see self::decrypt()
* @param string $tag
* @throws \LengthException if $length isn't of a sufficient length
* @throws \RuntimeException if GCM mode isn't being used
*/
public function setTag($tag)
{
if ($this->usePoly1305 && !isset($this->poly1305Key) && method_exists($this, 'createPoly1305Key')) {
$this->createPoly1305Key();
}
if ($this->mode != self::MODE_GCM && !$this->usePoly1305) {
throw new \BadMethodCallException('Authentication tags are only utilized in GCM mode or with Poly1305');
}
$length = strlen($tag);
if ($length < 4 || $length > 16) {
throw new \LengthException('The authentication tag must be between 4 and 16 bytes long');
}
$this->oldtag = $tag;
}
/**
* Get the IV
*
* mcrypt requires an IV even if ECB is used
*
* @see self::encrypt()
* @see self::decrypt()
* @param string $iv
* @return string
*/
protected function getIV($iv)
{
return $this->mode == self::MODE_ECB ? str_repeat("\0", $this->block_size) : $iv;
}
/**
* OpenSSL CTR Processor
*
* PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream
* for CTR is the same for both encrypting and decrypting this function is re-used by both SymmetricKey::encrypt()
* and SymmetricKey::decrypt(). Also, OpenSSL doesn't implement CTR for all of it's symmetric ciphers so this
* function will emulate CTR with ECB when necessary.
*
* @see self::encrypt()
* @see self::decrypt()
* @param string $plaintext
* @param string $encryptIV
* @param array $buffer
* @return string
*/
private function openssl_ctr_process($plaintext, &$encryptIV, &$buffer)
{
$ciphertext = '';
$block_size = $this->block_size;
$key = $this->key;
if ($this->openssl_emulate_ctr) {
$xor = $encryptIV;
if (strlen($buffer['ciphertext'])) {
for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
$block = substr($plaintext, $i, $block_size);
if (strlen($block) > strlen($buffer['ciphertext'])) {
$buffer['ciphertext'] .= openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
}
Strings::increment_str($xor);
$otp = Strings::shift($buffer['ciphertext'], $block_size);
$ciphertext .= $block ^ $otp;
}
} else {
for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
$block = substr($plaintext, $i, $block_size);
$otp = openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
Strings::increment_str($xor);
$ciphertext .= $block ^ $otp;
}
}
if ($this->continuousBuffer) {
$encryptIV = $xor;
if ($start = strlen($plaintext) % $block_size) {
$buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext'];
}
}
return $ciphertext;
}
if (strlen($buffer['ciphertext'])) {
$ciphertext = $plaintext ^ Strings::shift($buffer['ciphertext'], strlen($plaintext));
$plaintext = substr($plaintext, strlen($ciphertext));
if (!strlen($plaintext)) {
return $ciphertext;
}
}
$overflow = strlen($plaintext) % $block_size;
if ($overflow) {
$plaintext2 = Strings::pop($plaintext, $overflow); // ie. trim $plaintext to a multiple of $block_size and put rest of $plaintext in $plaintext2
$encrypted = openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV);
$temp = Strings::pop($encrypted, $block_size);
$ciphertext .= $encrypted . ($plaintext2 ^ $temp);
if ($this->continuousBuffer) {
$buffer['ciphertext'] = substr($temp, $overflow);
$encryptIV = $temp;
}
} elseif (!strlen($buffer['ciphertext'])) {
$ciphertext .= openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV);
$temp = Strings::pop($ciphertext, $block_size);
if ($this->continuousBuffer) {
$encryptIV = $temp;
}
}
if ($this->continuousBuffer) {
$encryptIV = openssl_decrypt($encryptIV, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
if ($overflow) {
Strings::increment_str($encryptIV);
}
}
return $ciphertext;
}
/**
* OpenSSL OFB Processor
*
* PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream
* for OFB is the same for both encrypting and decrypting this function is re-used by both SymmetricKey::encrypt()
* and SymmetricKey::decrypt().
*
* @see self::encrypt()
* @see self::decrypt()
* @param string $plaintext
* @param string $encryptIV
* @param array $buffer
* @return string
*/
private function openssl_ofb_process($plaintext, &$encryptIV, &$buffer)
{
if (strlen($buffer['xor'])) {
$ciphertext = $plaintext ^ $buffer['xor'];
$buffer['xor'] = substr($buffer['xor'], strlen($ciphertext));
$plaintext = substr($plaintext, strlen($ciphertext));
} else {
$ciphertext = '';
}
$block_size = $this->block_size;
$len = strlen($plaintext);
$key = $this->key;
$overflow = $len % $block_size;
if (strlen($plaintext)) {
if ($overflow) {
$ciphertext .= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV);
$xor = Strings::pop($ciphertext, $block_size);
if ($this->continuousBuffer) {
$encryptIV = $xor;
}
$ciphertext .= Strings::shift($xor, $overflow) ^ substr($plaintext, -$overflow);
if ($this->continuousBuffer) {
$buffer['xor'] = $xor;
}
} else {
$ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV);
if ($this->continuousBuffer) {
$encryptIV = substr($ciphertext, -$block_size) ^ substr($plaintext, -$block_size);
}
}
}
return $ciphertext;
}
/**
* phpseclib <-> OpenSSL Mode Mapper
*
* May need to be overwritten by classes extending this one in some cases
*
* @return string
*/
protected function openssl_translate_mode()
{
switch ($this->mode) {
case self::MODE_ECB:
return 'ecb';
case self::MODE_CBC:
return 'cbc';
case self::MODE_CTR:
case self::MODE_GCM:
return 'ctr';
case self::MODE_CFB:
return 'cfb';
case self::MODE_CFB8:
return 'cfb8';
case self::MODE_OFB:
return 'ofb';
}
}
/**
* Pad "packets".
*
* Block ciphers working by encrypting between their specified [$this->]block_size at a time
* If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to
* pad the input so that it is of the proper length.
*
* Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH,
* where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping
* away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is
* transmitted separately)
*
* @see self::disablePadding()
*/
public function enablePadding()
{
$this->padding = true;
}
/**
* Do not pad packets.
*
* @see self::enablePadding()
*/
public function disablePadding()
{
$this->padding = false;
}
/**
* Treat consecutive "packets" as if they are a continuous buffer.
*
* Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets
* will yield different outputs:
*
* <code>
* echo $rijndael->encrypt(substr($plaintext, 0, 16));
* echo $rijndael->encrypt(substr($plaintext, 16, 16));
* </code>
* <code>
* echo $rijndael->encrypt($plaintext);
* </code>
*
* The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates
* another, as demonstrated with the following:
*
* <code>
* $rijndael->encrypt(substr($plaintext, 0, 16));
* echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16)));
* </code>
* <code>
* echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16)));
* </code>
*
* With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different
* outputs. The reason is due to the fact that the initialization vector's change after every encryption /
* decryption round when the continuous buffer is enabled. When it's disabled, they remain constant.
*
* Put another way, when the continuous buffer is enabled, the state of the \phpseclib3\Crypt\*() object changes after each
* encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that
* continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them),
* however, they are also less intuitive and more likely to cause you problems.
*
* {@internal Could, but not must, extend by the child Crypt_* class}
*
* @see self::disableContinuousBuffer()
*/
public function enableContinuousBuffer()
{
if ($this->mode == self::MODE_ECB) {
return;
}
if ($this->mode == self::MODE_GCM) {
throw new \BadMethodCallException('This mode does not run in continuous mode');
}
$this->continuousBuffer = true;
$this->setEngine();
}
/**
* Treat consecutive packets as if they are a discontinuous buffer.
*
* The default behavior.
*
* {@internal Could, but not must, extend by the child Crypt_* class}
*
* @see self::enableContinuousBuffer()
*/
public function disableContinuousBuffer()
{
if ($this->mode == self::MODE_ECB) {
return;
}
if (!$this->continuousBuffer) {
return;
}
$this->continuousBuffer = false;
$this->setEngine();
}
/**
* Test for engine validity
*
* @see self::__construct()
* @param int $engine
* @return bool
*/
protected function isValidEngineHelper($engine)
{
switch ($engine) {
case self::ENGINE_OPENSSL:
$this->openssl_emulate_ctr = false;
$result = $this->cipher_name_openssl &&
extension_loaded('openssl');
if (!$result) {
return false;
}
$methods = openssl_get_cipher_methods();
if (in_array($this->cipher_name_openssl, $methods)) {
return true;
}
// not all of openssl's symmetric cipher's support ctr. for those
// that don't we'll emulate it
switch ($this->mode) {
case self::MODE_CTR:
if (in_array($this->cipher_name_openssl_ecb, $methods)) {
$this->openssl_emulate_ctr = true;
return true;
}
}
return false;
case self::ENGINE_MCRYPT:
set_error_handler(function () {
});
$result = $this->cipher_name_mcrypt &&
extension_loaded('mcrypt') &&
in_array($this->cipher_name_mcrypt, mcrypt_list_algorithms());
restore_error_handler();
return $result;
case self::ENGINE_EVAL:
return method_exists($this, 'setupInlineCrypt');
case self::ENGINE_INTERNAL:
return true;
}
return false;
}
/**
* Test for engine validity
*
* @see self::__construct()
* @param string $engine
* @return bool
*/
public function isValidEngine($engine)
{
static $reverseMap;
if (!isset($reverseMap)) {
$reverseMap = array_map('strtolower', self::ENGINE_MAP);
$reverseMap = array_flip($reverseMap);
}
$engine = strtolower($engine);
if (!isset($reverseMap[$engine])) {
return false;
}
return $this->isValidEngineHelper($reverseMap[$engine]);
}
/**
* Sets the preferred crypt engine
*
* Currently, $engine could be:
*
* - libsodium[very fast]
*
* - OpenSSL [very fast]
*
* - mcrypt [fast]
*
* - Eval [slow]
*
* - PHP [slowest]
*
* If the preferred crypt engine is not available the fastest available one will be used
*
* @see self::__construct()
* @param string $engine
*/
public function setPreferredEngine($engine)
{
static $reverseMap;
if (!isset($reverseMap)) {
$reverseMap = array_map('strtolower', self::ENGINE_MAP);
$reverseMap = array_flip($reverseMap);
}
$engine = is_string($engine) ? strtolower($engine) : '';
$this->preferredEngine = isset($reverseMap[$engine]) ? $reverseMap[$engine] : self::ENGINE_LIBSODIUM;
$this->setEngine();
}
/**
* Returns the engine currently being utilized
*
* @see self::setEngine()
*/
public function getEngine()
{
return self::ENGINE_MAP[$this->engine];
}
/**
* Sets the engine as appropriate
*
* @see self::__construct()
*/
protected function setEngine()
{
$this->engine = null;
$candidateEngines = [
self::ENGINE_LIBSODIUM,
self::ENGINE_OPENSSL_GCM,
self::ENGINE_OPENSSL,
self::ENGINE_MCRYPT,
self::ENGINE_EVAL
];
if (isset($this->preferredEngine)) {
$temp = [$this->preferredEngine];
$candidateEngines = array_merge(
$temp,
array_diff($candidateEngines, $temp)
);
}
foreach ($candidateEngines as $engine) {
if ($this->isValidEngineHelper($engine)) {
$this->engine = $engine;
break;
}
}
if (!$this->engine) {
$this->engine = self::ENGINE_INTERNAL;
}
if ($this->engine != self::ENGINE_MCRYPT && $this->enmcrypt) {
set_error_handler(function () {
});
// Closing the current mcrypt resource(s). _mcryptSetup() will, if needed,
// (re)open them with the module named in $this->cipher_name_mcrypt
mcrypt_module_close($this->enmcrypt);
mcrypt_module_close($this->demcrypt);
$this->enmcrypt = null;
$this->demcrypt = null;
if ($this->ecb) {
mcrypt_module_close($this->ecb);
$this->ecb = null;
}
restore_error_handler();
}
$this->changed = $this->nonIVChanged = true;
}
/**
* Encrypts a block
*
* Note: Must be extended by the child \phpseclib3\Crypt\* class
*
* @param string $in
* @return string
*/
abstract protected function encryptBlock($in);
/**
* Decrypts a block
*
* Note: Must be extended by the child \phpseclib3\Crypt\* class
*
* @param string $in
* @return string
*/
abstract protected function decryptBlock($in);
/**
* Setup the key (expansion)
*
* Only used if $engine == self::ENGINE_INTERNAL
*
* Note: Must extend by the child \phpseclib3\Crypt\* class
*
* @see self::setup()
*/
abstract protected function setupKey();
/**
* Setup the self::ENGINE_INTERNAL $engine
*
* (re)init, if necessary, the internal cipher $engine and flush all $buffers
* Used (only) if $engine == self::ENGINE_INTERNAL
*
* _setup() will be called each time if $changed === true
* typically this happens when using one or more of following public methods:
*
* - setKey()
*
* - setIV()
*
* - disableContinuousBuffer()
*
* - First run of encrypt() / decrypt() with no init-settings
*
* {@internal setup() is always called before en/decryption.}
*
* {@internal Could, but not must, extend by the child Crypt_* class}
*
* @see self::setKey()
* @see self::setIV()
* @see self::disableContinuousBuffer()
*/
protected function setup()
{
if (!$this->changed) {
return;
}
$this->changed = false;
if ($this->usePoly1305 && !isset($this->poly1305Key) && method_exists($this, 'createPoly1305Key')) {
$this->createPoly1305Key();
}
$this->enbuffer = $this->debuffer = ['ciphertext' => '', 'xor' => '', 'pos' => 0, 'enmcrypt_init' => true];
//$this->newtag = $this->oldtag = false;
if ($this->usesNonce()) {
if ($this->nonce === false) {
throw new InsufficientSetupException('No nonce has been defined');
}
if ($this->mode == self::MODE_GCM && !in_array($this->engine, [self::ENGINE_LIBSODIUM, self::ENGINE_OPENSSL_GCM])) {
$this->setupGCM();
}
} else {
$this->iv = $this->origIV;
}
if ($this->iv === false && !in_array($this->mode, [self::MODE_STREAM, self::MODE_ECB])) {
if ($this->mode != self::MODE_GCM || !in_array($this->engine, [self::ENGINE_LIBSODIUM, self::ENGINE_OPENSSL_GCM])) {
throw new InsufficientSetupException('No IV has been defined');
}
}
if ($this->key === false) {
throw new InsufficientSetupException('No key has been defined');
}
$this->encryptIV = $this->decryptIV = $this->iv;
switch ($this->engine) {
case self::ENGINE_MCRYPT:
$this->enchanged = $this->dechanged = true;
set_error_handler(function () {
});
if (!isset($this->enmcrypt)) {
static $mcrypt_modes = [
self::MODE_CTR => 'ctr',
self::MODE_ECB => MCRYPT_MODE_ECB,
self::MODE_CBC => MCRYPT_MODE_CBC,
self::MODE_CFB => 'ncfb',
self::MODE_CFB8 => MCRYPT_MODE_CFB,
self::MODE_OFB => MCRYPT_MODE_NOFB,
self::MODE_OFB8 => MCRYPT_MODE_OFB,
self::MODE_STREAM => MCRYPT_MODE_STREAM,
];
$this->demcrypt = mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], '');
$this->enmcrypt = mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], '');
// we need the $ecb mcrypt resource (only) in MODE_CFB with enableContinuousBuffer()
// to workaround mcrypt's broken ncfb implementation in buffered mode
// see: {@link http://phpseclib.sourceforge.net/cfb-demo.phps}
if ($this->mode == self::MODE_CFB) {
$this->ecb = mcrypt_module_open($this->cipher_name_mcrypt, '', MCRYPT_MODE_ECB, '');
}
} // else should mcrypt_generic_deinit be called?
if ($this->mode == self::MODE_CFB) {
mcrypt_generic_init($this->ecb, $this->key, str_repeat("\0", $this->block_size));
}
restore_error_handler();
break;
case self::ENGINE_INTERNAL:
$this->setupKey();
break;
case self::ENGINE_EVAL:
if ($this->nonIVChanged) {
$this->setupKey();
$this->setupInlineCrypt();
}
}
$this->nonIVChanged = false;
}
/**
* Pads a string
*
* Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize.
* $this->block_size - (strlen($text) % $this->block_size) bytes are added, each of which is equal to
* chr($this->block_size - (strlen($text) % $this->block_size)
*
* If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless
* and padding will, hence forth, be enabled.
*
* @see self::unpad()
* @param string $text
* @throws \LengthException if padding is disabled and the plaintext's length is not a multiple of the block size
* @return string
*/
protected function pad($text)
{
$length = strlen($text);
if (!$this->padding) {
if ($length % $this->block_size == 0) {
return $text;
} else {
throw new \LengthException("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size}). Try enabling padding.");
}
}
$pad = $this->block_size - ($length % $this->block_size);
return str_pad($text, $length + $pad, chr($pad));
}
/**
* Unpads a string.
*
* If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong
* and false will be returned.
*
* @see self::pad()
* @param string $text
* @throws \LengthException if the ciphertext's length is not a multiple of the block size
* @return string
*/
protected function unpad($text)
{
if (!$this->padding) {
return $text;
}
$length = ord($text[strlen($text) - 1]);
if (!$length || $length > $this->block_size) {
throw new BadDecryptionException("The ciphertext has an invalid padding length ($length) compared to the block size ({$this->block_size})");
}
return substr($text, 0, -$length);
}
/**
* Setup the performance-optimized function for de/encrypt()
*
* Stores the created (or existing) callback function-name
* in $this->inline_crypt
*
* Internally for phpseclib developers:
*
* _setupInlineCrypt() would be called only if:
*
* - $this->engine === self::ENGINE_EVAL
*
* - each time on _setup(), after(!) _setupKey()
*
*
* This ensures that _setupInlineCrypt() has always a
* full ready2go initializated internal cipher $engine state
* where, for example, the keys already expanded,
* keys/block_size calculated and such.
*
* It is, each time if called, the responsibility of _setupInlineCrypt():
*
* - to set $this->inline_crypt to a valid and fully working callback function
* as a (faster) replacement for encrypt() / decrypt()
*
* - NOT to create unlimited callback functions (for memory reasons!)
* no matter how often _setupInlineCrypt() would be called. At some
* point of amount they must be generic re-useable.
*
* - the code of _setupInlineCrypt() it self,
* and the generated callback code,
* must be, in following order:
* - 100% safe
* - 100% compatible to encrypt()/decrypt()
* - using only php5+ features/lang-constructs/php-extensions if
* compatibility (down to php4) or fallback is provided
* - readable/maintainable/understandable/commented and... not-cryptic-styled-code :-)
* - >= 10% faster than encrypt()/decrypt() [which is, by the way,
* the reason for the existence of _setupInlineCrypt() :-)]
* - memory-nice
* - short (as good as possible)
*
* Note: - _setupInlineCrypt() is using _createInlineCryptFunction() to create the full callback function code.
* - In case of using inline crypting, _setupInlineCrypt() must extend by the child \phpseclib3\Crypt\* class.
* - The following variable names are reserved:
* - $_* (all variable names prefixed with an underscore)
* - $self (object reference to it self. Do not use $this, but $self instead)
* - $in (the content of $in has to en/decrypt by the generated code)
* - The callback function should not use the 'return' statement, but en/decrypt'ing the content of $in only
*
* {@internal If a Crypt_* class providing inline crypting it must extend _setupInlineCrypt()}
*
* @see self::setup()
* @see self::createInlineCryptFunction()
* @see self::encrypt()
* @see self::decrypt()
*/
//protected function setupInlineCrypt();
/**
* Creates the performance-optimized function for en/decrypt()
*
* Internally for phpseclib developers:
*
* _createInlineCryptFunction():
*
* - merge the $cipher_code [setup'ed by _setupInlineCrypt()]
* with the current [$this->]mode of operation code
*
* - create the $inline function, which called by encrypt() / decrypt()
* as its replacement to speed up the en/decryption operations.
*
* - return the name of the created $inline callback function
*
* - used to speed up en/decryption
*
*
*
* The main reason why can speed up things [up to 50%] this way are:
*
* - using variables more effective then regular.
* (ie no use of expensive arrays but integers $k_0, $k_1 ...
* or even, for example, the pure $key[] values hardcoded)
*
* - avoiding 1000's of function calls of ie _encryptBlock()
* but inlining the crypt operations.
* in the mode of operation for() loop.
*
* - full loop unroll the (sometimes key-dependent) rounds
* avoiding this way ++$i counters and runtime-if's etc...
*
* The basic code architectur of the generated $inline en/decrypt()
* lambda function, in pseudo php, is:
*
* <code>
* +----------------------------------------------------------------------------------------------+
* | callback $inline = create_function: |
* | lambda_function_0001_crypt_ECB($action, $text) |
* | { |
* | INSERT PHP CODE OF: |
* | $cipher_code['init_crypt']; // general init code. |
* | // ie: $sbox'es declarations used for |
* | // encrypt and decrypt'ing. |
* | |
* | switch ($action) { |
* | case 'encrypt': |
* | INSERT PHP CODE OF: |
* | $cipher_code['init_encrypt']; // encrypt sepcific init code. |
* | ie: specified $key or $box |
* | declarations for encrypt'ing. |
* | |
* | foreach ($ciphertext) { |
* | $in = $block_size of $ciphertext; |
* | |
* | INSERT PHP CODE OF: |
* | $cipher_code['encrypt_block']; // encrypt's (string) $in, which is always: |
* | // strlen($in) == $this->block_size |
* | // here comes the cipher algorithm in action |
* | // for encryption. |
* | // $cipher_code['encrypt_block'] has to |
* | // encrypt the content of the $in variable |
* | |
* | $plaintext .= $in; |
* | } |
* | return $plaintext; |
* | |
* | case 'decrypt': |
* | INSERT PHP CODE OF: |
* | $cipher_code['init_decrypt']; // decrypt sepcific init code |
* | ie: specified $key or $box |
* | declarations for decrypt'ing. |
* | foreach ($plaintext) { |
* | $in = $block_size of $plaintext; |
* | |
* | INSERT PHP CODE OF: |
* | $cipher_code['decrypt_block']; // decrypt's (string) $in, which is always |
* | // strlen($in) == $this->block_size |
* | // here comes the cipher algorithm in action |
* | // for decryption. |
* | // $cipher_code['decrypt_block'] has to |
* | // decrypt the content of the $in variable |
* | $ciphertext .= $in; |
* | } |
* | return $ciphertext; |
* | } |
* | } |
* +----------------------------------------------------------------------------------------------+
* </code>
*
* See also the \phpseclib3\Crypt\*::_setupInlineCrypt()'s for
* productive inline $cipher_code's how they works.
*
* Structure of:
* <code>
* $cipher_code = [
* 'init_crypt' => (string) '', // optional
* 'init_encrypt' => (string) '', // optional
* 'init_decrypt' => (string) '', // optional
* 'encrypt_block' => (string) '', // required
* 'decrypt_block' => (string) '' // required
* ];
* </code>
*
* @see self::setupInlineCrypt()
* @see self::encrypt()
* @see self::decrypt()
* @param array $cipher_code
* @return string (the name of the created callback function)
*/
protected function createInlineCryptFunction($cipher_code)
{
$block_size = $this->block_size;
// optional
$init_crypt = isset($cipher_code['init_crypt']) ? $cipher_code['init_crypt'] : '';
$init_encrypt = isset($cipher_code['init_encrypt']) ? $cipher_code['init_encrypt'] : '';
$init_decrypt = isset($cipher_code['init_decrypt']) ? $cipher_code['init_decrypt'] : '';
// required
$encrypt_block = $cipher_code['encrypt_block'];
$decrypt_block = $cipher_code['decrypt_block'];
// Generating mode of operation inline code,
// merged with the $cipher_code algorithm
// for encrypt- and decryption.
switch ($this->mode) {
case self::MODE_ECB:
$encrypt = $init_encrypt . '
$_ciphertext = "";
$_plaintext_len = strlen($_text);
for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') {
$in = substr($_text, $_i, ' . $block_size . ');
' . $encrypt_block . '
$_ciphertext.= $in;
}
return $_ciphertext;
';
$decrypt = $init_decrypt . '
$_plaintext = "";
$_text = str_pad($_text, strlen($_text) + (' . $block_size . ' - strlen($_text) % ' . $block_size . ') % ' . $block_size . ', chr(0));
$_ciphertext_len = strlen($_text);
for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') {
$in = substr($_text, $_i, ' . $block_size . ');
' . $decrypt_block . '
$_plaintext.= $in;
}
return $this->unpad($_plaintext);
';
break;
case self::MODE_CTR:
$encrypt = $init_encrypt . '
$_ciphertext = "";
$_plaintext_len = strlen($_text);
$_xor = $this->encryptIV;
$_buffer = &$this->enbuffer;
if (strlen($_buffer["ciphertext"])) {
for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') {
$_block = substr($_text, $_i, ' . $block_size . ');
if (strlen($_block) > strlen($_buffer["ciphertext"])) {
$in = $_xor;
' . $encrypt_block . '
\phpseclib3\Common\Functions\Strings::increment_str($_xor);
$_buffer["ciphertext"].= $in;
}
$_key = \phpseclib3\Common\Functions\Strings::shift($_buffer["ciphertext"], ' . $block_size . ');
$_ciphertext.= $_block ^ $_key;
}
} else {
for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') {
$_block = substr($_text, $_i, ' . $block_size . ');
$in = $_xor;
' . $encrypt_block . '
\phpseclib3\Common\Functions\Strings::increment_str($_xor);
$_key = $in;
$_ciphertext.= $_block ^ $_key;
}
}
if ($this->continuousBuffer) {
$this->encryptIV = $_xor;
if ($_start = $_plaintext_len % ' . $block_size . ') {
$_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"];
}
}
return $_ciphertext;
';
$decrypt = $init_encrypt . '
$_plaintext = "";
$_ciphertext_len = strlen($_text);
$_xor = $this->decryptIV;
$_buffer = &$this->debuffer;
if (strlen($_buffer["ciphertext"])) {
for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') {
$_block = substr($_text, $_i, ' . $block_size . ');
if (strlen($_block) > strlen($_buffer["ciphertext"])) {
$in = $_xor;
' . $encrypt_block . '
\phpseclib3\Common\Functions\Strings::increment_str($_xor);
$_buffer["ciphertext"].= $in;
}
$_key = \phpseclib3\Common\Functions\Strings::shift($_buffer["ciphertext"], ' . $block_size . ');
$_plaintext.= $_block ^ $_key;
}
} else {
for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') {
$_block = substr($_text, $_i, ' . $block_size . ');
$in = $_xor;
' . $encrypt_block . '
\phpseclib3\Common\Functions\Strings::increment_str($_xor);
$_key = $in;
$_plaintext.= $_block ^ $_key;
}
}
if ($this->continuousBuffer) {
$this->decryptIV = $_xor;
if ($_start = $_ciphertext_len % ' . $block_size . ') {
$_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"];
}
}
return $_plaintext;
';
break;
case self::MODE_CFB:
$encrypt = $init_encrypt . '
$_ciphertext = "";
$_buffer = &$this->enbuffer;
if ($this->continuousBuffer) {
$_iv = &$this->encryptIV;
$_pos = &$_buffer["pos"];
} else {
$_iv = $this->encryptIV;
$_pos = 0;
}
$_len = strlen($_text);
$_i = 0;
if ($_pos) {
$_orig_pos = $_pos;
$_max = ' . $block_size . ' - $_pos;
if ($_len >= $_max) {
$_i = $_max;
$_len-= $_max;
$_pos = 0;
} else {
$_i = $_len;
$_pos+= $_len;
$_len = 0;
}
$_ciphertext = substr($_iv, $_orig_pos) ^ $_text;
$_iv = substr_replace($_iv, $_ciphertext, $_orig_pos, $_i);
}
while ($_len >= ' . $block_size . ') {
$in = $_iv;
' . $encrypt_block . ';
$_iv = $in ^ substr($_text, $_i, ' . $block_size . ');
$_ciphertext.= $_iv;
$_len-= ' . $block_size . ';
$_i+= ' . $block_size . ';
}
if ($_len) {
$in = $_iv;
' . $encrypt_block . '
$_iv = $in;
$_block = $_iv ^ substr($_text, $_i);
$_iv = substr_replace($_iv, $_block, 0, $_len);
$_ciphertext.= $_block;
$_pos = $_len;
}
return $_ciphertext;
';
$decrypt = $init_encrypt . '
$_plaintext = "";
$_buffer = &$this->debuffer;
if ($this->continuousBuffer) {
$_iv = &$this->decryptIV;
$_pos = &$_buffer["pos"];
} else {
$_iv = $this->decryptIV;
$_pos = 0;
}
$_len = strlen($_text);
$_i = 0;
if ($_pos) {
$_orig_pos = $_pos;
$_max = ' . $block_size . ' - $_pos;
if ($_len >= $_max) {
$_i = $_max;
$_len-= $_max;
$_pos = 0;
} else {
$_i = $_len;
$_pos+= $_len;
$_len = 0;
}
$_plaintext = substr($_iv, $_orig_pos) ^ $_text;
$_iv = substr_replace($_iv, substr($_text, 0, $_i), $_orig_pos, $_i);
}
while ($_len >= ' . $block_size . ') {
$in = $_iv;
' . $encrypt_block . '
$_iv = $in;
$cb = substr($_text, $_i, ' . $block_size . ');
$_plaintext.= $_iv ^ $cb;
$_iv = $cb;
$_len-= ' . $block_size . ';
$_i+= ' . $block_size . ';
}
if ($_len) {
$in = $_iv;
' . $encrypt_block . '
$_iv = $in;
$_plaintext.= $_iv ^ substr($_text, $_i);
$_iv = substr_replace($_iv, substr($_text, $_i), 0, $_len);
$_pos = $_len;
}
return $_plaintext;
';
break;
case self::MODE_CFB8:
$encrypt = $init_encrypt . '
$_ciphertext = "";
$_len = strlen($_text);
$_iv = $this->encryptIV;
for ($_i = 0; $_i < $_len; ++$_i) {
$in = $_iv;
' . $encrypt_block . '
$_ciphertext .= ($_c = $_text[$_i] ^ $in);
$_iv = substr($_iv, 1) . $_c;
}
if ($this->continuousBuffer) {
if ($_len >= ' . $block_size . ') {
$this->encryptIV = substr($_ciphertext, -' . $block_size . ');
} else {
$this->encryptIV = substr($this->encryptIV, $_len - ' . $block_size . ') . substr($_ciphertext, -$_len);
}
}
return $_ciphertext;
';
$decrypt = $init_encrypt . '
$_plaintext = "";
$_len = strlen($_text);
$_iv = $this->decryptIV;
for ($_i = 0; $_i < $_len; ++$_i) {
$in = $_iv;
' . $encrypt_block . '
$_plaintext .= $_text[$_i] ^ $in;
$_iv = substr($_iv, 1) . $_text[$_i];
}
if ($this->continuousBuffer) {
if ($_len >= ' . $block_size . ') {
$this->decryptIV = substr($_text, -' . $block_size . ');
} else {
$this->decryptIV = substr($this->decryptIV, $_len - ' . $block_size . ') . substr($_text, -$_len);
}
}
return $_plaintext;
';
break;
case self::MODE_OFB8:
$encrypt = $init_encrypt . '
$_ciphertext = "";
$_len = strlen($_text);
$_iv = $this->encryptIV;
for ($_i = 0; $_i < $_len; ++$_i) {
$in = $_iv;
' . $encrypt_block . '
$_ciphertext.= $_text[$_i] ^ $in;
$_iv = substr($_iv, 1) . $in[0];
}
if ($this->continuousBuffer) {
$this->encryptIV = $_iv;
}
return $_ciphertext;
';
$decrypt = $init_encrypt . '
$_plaintext = "";
$_len = strlen($_text);
$_iv = $this->decryptIV;
for ($_i = 0; $_i < $_len; ++$_i) {
$in = $_iv;
' . $encrypt_block . '
$_plaintext.= $_text[$_i] ^ $in;
$_iv = substr($_iv, 1) . $in[0];
}
if ($this->continuousBuffer) {
$this->decryptIV = $_iv;
}
return $_plaintext;
';
break;
case self::MODE_OFB:
$encrypt = $init_encrypt . '
$_ciphertext = "";
$_plaintext_len = strlen($_text);
$_xor = $this->encryptIV;
$_buffer = &$this->enbuffer;
if (strlen($_buffer["xor"])) {
for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') {
$_block = substr($_text, $_i, ' . $block_size . ');
if (strlen($_block) > strlen($_buffer["xor"])) {
$in = $_xor;
' . $encrypt_block . '
$_xor = $in;
$_buffer["xor"].= $_xor;
}
$_key = \phpseclib3\Common\Functions\Strings::shift($_buffer["xor"], ' . $block_size . ');
$_ciphertext.= $_block ^ $_key;
}
} else {
for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') {
$in = $_xor;
' . $encrypt_block . '
$_xor = $in;
$_ciphertext.= substr($_text, $_i, ' . $block_size . ') ^ $_xor;
}
$_key = $_xor;
}
if ($this->continuousBuffer) {
$this->encryptIV = $_xor;
if ($_start = $_plaintext_len % ' . $block_size . ') {
$_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"];
}
}
return $_ciphertext;
';
$decrypt = $init_encrypt . '
$_plaintext = "";
$_ciphertext_len = strlen($_text);
$_xor = $this->decryptIV;
$_buffer = &$this->debuffer;
if (strlen($_buffer["xor"])) {
for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') {
$_block = substr($_text, $_i, ' . $block_size . ');
if (strlen($_block) > strlen($_buffer["xor"])) {
$in = $_xor;
' . $encrypt_block . '
$_xor = $in;
$_buffer["xor"].= $_xor;
}
$_key = \phpseclib3\Common\Functions\Strings::shift($_buffer["xor"], ' . $block_size . ');
$_plaintext.= $_block ^ $_key;
}
} else {
for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') {
$in = $_xor;
' . $encrypt_block . '
$_xor = $in;
$_plaintext.= substr($_text, $_i, ' . $block_size . ') ^ $_xor;
}
$_key = $_xor;
}
if ($this->continuousBuffer) {
$this->decryptIV = $_xor;
if ($_start = $_ciphertext_len % ' . $block_size . ') {
$_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"];
}
}
return $_plaintext;
';
break;
case self::MODE_STREAM:
$encrypt = $init_encrypt . '
$_ciphertext = "";
' . $encrypt_block . '
return $_ciphertext;
';
$decrypt = $init_decrypt . '
$_plaintext = "";
' . $decrypt_block . '
return $_plaintext;
';
break;
// case self::MODE_CBC:
default:
$encrypt = $init_encrypt . '
$_ciphertext = "";
$_plaintext_len = strlen($_text);
$in = $this->encryptIV;
for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') {
$in = substr($_text, $_i, ' . $block_size . ') ^ $in;
' . $encrypt_block . '
$_ciphertext.= $in;
}
if ($this->continuousBuffer) {
$this->encryptIV = $in;
}
return $_ciphertext;
';
$decrypt = $init_decrypt . '
$_plaintext = "";
$_text = str_pad($_text, strlen($_text) + (' . $block_size . ' - strlen($_text) % ' . $block_size . ') % ' . $block_size . ', chr(0));
$_ciphertext_len = strlen($_text);
$_iv = $this->decryptIV;
for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') {
$in = $_block = substr($_text, $_i, ' . $block_size . ');
' . $decrypt_block . '
$_plaintext.= $in ^ $_iv;
$_iv = $_block;
}
if ($this->continuousBuffer) {
$this->decryptIV = $_iv;
}
return $this->unpad($_plaintext);
';
break;
}
// Before discrediting this, please read the following:
// @see https://github.com/phpseclib/phpseclib/issues/1293
// @see https://github.com/phpseclib/phpseclib/pull/1143
eval('$func = function ($_action, $_text) { ' . $init_crypt . 'if ($_action == "encrypt") { ' . $encrypt . ' } else { ' . $decrypt . ' }};');
return \Closure::bind($func, $this, static::class);
}
/**
* Convert float to int
*
* On ARM CPUs converting floats to ints doesn't always work
*
* @param string $x
* @return int
*/
protected static function safe_intval($x)
{
if (is_int($x)) {
return $x;
}
if (self::$use_reg_intval) {
return PHP_INT_SIZE == 4 && PHP_VERSION_ID >= 80100 ? intval($x) : $x;
}
return (fmod($x, 0x80000000) & 0x7FFFFFFF) |
((fmod(floor($x / 0x80000000), 2) & 1) << 31);
}
/**
* eval()'able string for in-line float to int
*
* @return string
*/
protected static function safe_intval_inline()
{
if (self::$use_reg_intval) {
return PHP_INT_SIZE == 4 && PHP_VERSION_ID >= 80100 ? 'intval(%s)' : '%s';
}
$safeint = '(is_int($temp = %s) ? $temp : (fmod($temp, 0x80000000) & 0x7FFFFFFF) | ';
return $safeint . '((fmod(floor($temp / 0x80000000), 2) & 1) << 31))';
}
/**
* Sets up GCM parameters
*
* See steps 1-2 of https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=23
* for more info
*
*/
private function setupGCM()
{
// don't keep on re-calculating $this->h
if (!$this->h || $this->hKey != $this->key) {
$cipher = new static('ecb');
$cipher->setKey($this->key);
$cipher->disablePadding();
$this->h = self::$gcmField->newInteger(
Strings::switchEndianness($cipher->encrypt("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"))
);
$this->hKey = $this->key;
}
if (strlen($this->nonce) == 12) {
$this->iv = $this->nonce . "\0\0\0\1";
} else {
$this->iv = $this->ghash(
self::nullPad128($this->nonce) . str_repeat("\0", 8) . self::len64($this->nonce)
);
}
}
/**
* Performs GHASH operation
*
* See https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=20
* for more info
*
* @see self::decrypt()
* @see self::encrypt()
* @param string $x
* @return string
*/
private function ghash($x)
{
$h = $this->h;
$y = ["\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"];
$x = str_split($x, 16);
$n = 0;
// the switchEndianness calls are necessary because the multiplication algorithm in BinaryField/Integer
// interprets strings as polynomials in big endian order whereas in GCM they're interpreted in little
// endian order per https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=19.
// big endian order is what binary field elliptic curves use per http://www.secg.org/sec1-v2.pdf#page=18.
// we could switchEndianness here instead of in the while loop but doing so in the while loop seems like it
// might be slightly more performant
//$x = Strings::switchEndianness($x);
foreach ($x as $xn) {
$xn = Strings::switchEndianness($xn);
$t = $y[$n] ^ $xn;
$temp = self::$gcmField->newInteger($t);
$y[++$n] = $temp->multiply($h)->toBytes();
$y[$n] = substr($y[$n], 1);
}
$y[$n] = Strings::switchEndianness($y[$n]);
return $y[$n];
}
/**
* Returns the bit length of a string in a packed format
*
* @see self::decrypt()
* @see self::encrypt()
* @see self::setupGCM()
* @param string $str
* @return string
*/
private static function len64($str)
{
return "\0\0\0\0" . pack('N', 8 * strlen($str));
}
/**
* NULL pads a string to be a multiple of 128
*
* @see self::decrypt()
* @see self::encrypt()
* @see self::setupGCM()
* @param string $str
* @return string
*/
protected static function nullPad128($str)
{
$len = strlen($str);
return $str . str_repeat("\0", 16 * ceil($len / 16) - $len);
}
/**
* Calculates Poly1305 MAC
*
* On my system ChaCha20, with libsodium, takes 0.5s. With this custom Poly1305 implementation
* it takes 1.2s.
*
* @see self::decrypt()
* @see self::encrypt()
* @param string $text
* @return string
*/
protected function poly1305($text)
{
$s = $this->poly1305Key; // strlen($this->poly1305Key) == 32
$r = Strings::shift($s, 16);
$r = strrev($r);
$r &= "\x0f\xff\xff\xfc\x0f\xff\xff\xfc\x0f\xff\xff\xfc\x0f\xff\xff\xff";
$s = strrev($s);
$r = self::$poly1305Field->newInteger(new BigInteger($r, 256));
$s = self::$poly1305Field->newInteger(new BigInteger($s, 256));
$a = self::$poly1305Field->newInteger(new BigInteger());
$blocks = str_split($text, 16);
foreach ($blocks as $block) {
$n = strrev($block . chr(1));
$n = self::$poly1305Field->newInteger(new BigInteger($n, 256));
$a = $a->add($n);
$a = $a->multiply($r);
}
$r = $a->toBigInteger()->add($s->toBigInteger());
$mask = "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
return strrev($r->toBytes()) & $mask;
}
/**
* Return the mode
*
* You can do $obj instanceof AES or whatever to get the cipher but you can't do that to get the mode
*
* @return string
*/
public function getMode()
{
return array_flip(self::MODE_MAP)[$this->mode];
}
/**
* Is the continuous buffer enabled?
*
* @return boolean
*/
public function continuousBufferEnabled()
{
return $this->continuousBuffer;
}
}