diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index fe1f97b2dd..1ad40d8f29 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -23,6 +23,7 @@
'Sources/minify',
'Sources/ReCaptcha',
'Sources/ZxcvbnPhp',
+ 'Sources/Phpseclib',
'Themes',
])
// Skip ssi_example.php.
diff --git a/Languages/en_US/Packages.php b/Languages/en_US/Packages.php
index 5997292a8a..d411a4c258 100644
--- a/Languages/en_US/Packages.php
+++ b/Languages/en_US/Packages.php
@@ -154,27 +154,34 @@
$txt['package_uninstall_cannot'] = 'This package cannot be uninstalled, because there is no uninstaller.
Please contact the mod author for more information.';
$txt['package_install_options'] = 'Installation Options';
-$txt['package_install_options_desc'] = 'Set various options for how the package manager installs modifications, including backups and FTP access';
-$txt['package_install_options_ftp_why'] = 'Using the package manager’s FTP functionality is the easiest way to avoid having to manually chmod the files writable through FTP yourself for the package manager to work.
Here you can set the default values for some fields.';
-$txt['package_install_options_ftp_server'] = 'FTP Server';
-$txt['package_install_options_ftp_port'] = 'Port';
-$txt['package_install_options_ftp_user'] = 'Username';
-$txt['package_install_options_make_backups'] = 'Create Backup versions of replaced files with a tilde (~) on the end of their names.';
-$txt['package_install_options_make_full_backups'] = 'Create a backup of key SMF files whenever a package is installed or uninstalled.';
-
-$txt['package_ftp_necessary'] = 'FTP Information Required';
-$txt['package_ftp_why'] = 'Some of the files the package manager needs to modify are not writable. This needs to be changed by logging into FTP and using it to chmod or create the files and directories. Your FTP information may be temporarily cached for proper operation of the package manager. Note you can also do this manually using an FTP client - to view a list of the affected files please click here.';
-$txt['package_ftp_why_file_list'] = 'The following files need to made writable to continue installation:';
-$txt['package_ftp_why_download'] = 'In order to download packages, the Packages directory, and any files in it, must be writable. Currently the system does not have the needed permissions to write to this directory. The package manager can use your FTP information to attempt to fix this problem.';
-$txt['package_ftp_server'] = 'FTP Server';
-$txt['package_ftp_port'] = 'Port';
-$txt['package_ftp_username'] = 'Username';
-$txt['package_ftp_password'] = 'Password';
-$txt['package_ftp_path'] = 'Local path to SMF';
-$txt['package_ftp_test'] = 'Test';
-$txt['package_ftp_test_connection'] = 'Test Connection';
-$txt['package_ftp_test_success'] = 'FTP connection established.';
-$txt['package_ftp_test_failed'] = 'Could not contact server.';
+$txt['package_install_options_desc'] = 'Set various options for how the package manager installs modifications, including backups and File System access';
+$txt['package_install_options_fs_why'] = 'Using the package manager’s File System Handler functionality is the easiest way to avoid having to manually chmod the files writable through File System yourself for the package manager to work.
Here you can set the default values for some fields.';
+$txt['package_make_backups'] = 'Create Backup versions of replaced files with a tilde (~) on the end of their names.';
+$txt['package_make_full_backups'] = 'Create a backup of key SMF files whenever a package is installed or uninstalled.';
+
+$txt['package_fs_necessary'] = 'File System Information Required';
+$txt['package_fs_why'] = 'Some of the files the package manager needs to modify are not writable. This needs to be changed by logging into the File System and using it to chmod or create the files and directories. Your credentials information may be temporarily cached for proper operation of the package manager. Note you can also do this manually using a client, such as FTP, - to view a list of the affected files please click here.';
+$txt['package_fs_why_file_list'] = 'The following files need to made writable to continue installation:';
+$txt['package_fs_why_download'] = 'In order to download packages, the Packages directory, and any files in it, must be writable. Currently the system does not have the needed permissions to write to this directory. The package manager can use your File System credentials information to attempt to fix this problem.';
+$txt['filesystem_type'] = 'File System';
+$txt['filesystem_server'] = 'Server Name';
+$txt['filesystem_port'] = 'Port';
+$txt['filesystem_port_desc'] = 'Use 0 to use default';
+$txt['filesystem_username'] = 'Username';
+$txt['filesystem_password'] = 'Password';
+$txt['filesystem_path'] = 'Local path to SMF';
+$txt['package_fs_test'] = 'Test';
+$txt['package_fs_test_connection'] = 'Test Connection';
+$txt['package_fs_test_success'] = 'Connection established.';
+$txt['package_fs_test_failed'] = 'Could not contact server.';
+$txt['filesystem_error_bad_server'] = 'Invalid Server';
+$txt['filesystem_error_bad_response'] = 'Invalid Response';
+$txt['filesystem_error_bad_credentials'] = 'Invalid Username or Password';
+$txt['filesystem_error_bad_path'] = 'Invalid Path';
+$txt['filesystem_error_bad_file'] = 'Invalid File (Does file exist?)';
+$txt['filesystem_error_no_connection'] = 'No connection found';
+$txt['filesystem_error_delete_filedirectory'] = 'Attempted to delete a file using directory commands';
+$txt['filesystem_error_delete_directoryfile'] = 'Attempted to delete a directory using file commands';
// For a break, use -n- instead of
... and don't use entities.
$txt['package_delete_bad'] = 'The package you are about to delete is currently installed!-n-If you delete it, you may not be able to uninstall it later.-n-Are you sure?';
@@ -233,7 +240,7 @@
$txt['operation_description_ignore'] = '{desc}, ignoring errors';
$txt['operation_invalid'] = 'The operation that you selected is invalid.';
-$txt['package_file_perms_desc'] = 'You can use this section to review the writable status of critical files and folders within your forum directory. Note this only considers key forum folders and files - use an FTP client for additional options.';
+$txt['package_file_perms_desc'] = 'You can use this section to review the writable status of critical files and folders within your forum directory. Note this only considers key forum folders and files - use a client for additional options.';
$txt['package_file_perms_name'] = 'File/Directory Name';
$txt['package_file_perms_status'] = 'Current Status';
$txt['package_file_perms_new_status'] = 'New Status';
@@ -255,8 +262,8 @@
$txt['package_file_perms_pre_restricted'] = 'Restricted - minimum files writable';
$txt['package_file_perms_pre_standard'] = 'Standard - key files writable';
$txt['package_file_perms_pre_free'] = 'Free - all files writable';
-$txt['package_file_perms_ftp_details'] = 'On most servers it is only possible to change file permissions using an FTP account. Please enter your FTP details below';
-$txt['package_file_perms_ftp_retain'] = 'Note, SMF will only retain the password information temporarily to aid operation of the package manager.';
+$txt['package_file_perms_fs_details'] = 'On most servers it is only possible to change file permissions using an File System account. Please enter your File System credentials details below';
+$txt['package_file_perms_fs_retain'] = 'Note, SMF will only retain the password information temporarily to aid operation of the package manager.';
$txt['package_file_perms_go'] = 'Make Changes';
$txt['package_file_perms_applying'] = 'Applying Changes';
@@ -264,7 +271,7 @@
one {{0, number, integer} of # item completed}
other {{0, number, integer} of # items completed}
}';
-$txt['package_file_perms_skipping_ftp'] = 'Warning: Failed to connect to FTP server, attempting to change permissions without. This is likely to fail - please check the results upon completion and try again with correct FTP details if necessary.';
+$txt['package_file_perms_skipping_fs'] = 'Warning: Failed to connect to server, attempting to change permissions without. This is likely to fail - please check the results upon completion and try again with correct credentials if necessary.';
$txt['package_file_perms_dirs_done'] = '{1, plural,
one {{0, number, integer} of # directory completed}
@@ -297,7 +304,7 @@
On some server configurations selecting the wrong permissions may stop SMF from operating.
Certain directories such as attachments need to be writable to use that functionality.
This functionality is mainly applicable on non-Windows based servers - it will not work as expected on Windows in regards to permission flags.
- Before proceeding make sure you have an FTP client installed in case you do make an error and need to FTP into the server to remedy it.';
+ Before proceeding make sure you have an File System client installed in case you do make an error and need to access the server to remedy it.';
$txt['package_confirm_view_package_content'] = 'Are you sure you want to view the package contents from this location:
{0}';
$txt['package_confirm_proceed'] = 'Proceed';
diff --git a/Sources/Autoloader.php b/Sources/Autoloader.php
index 312fac40e9..1dd1af3618 100644
--- a/Sources/Autoloader.php
+++ b/Sources/Autoloader.php
@@ -25,6 +25,7 @@
static $class_map = [
// Some special cases.
+ 'phpseclib3\\' => '{$sourcedir}/Phpseclib/',
'ReCaptcha\\' => '{$sourcedir}/ReCaptcha/',
'MatthiasMullie\\Minify\\' => '{$sourcedir}/minify/src/',
'MatthiasMullie\\PathConverter\\' => '{$sourcedir}/minify/path-converter/src/',
diff --git a/Sources/Maintenance/Migration/v3_0/FileSystemHandler.php b/Sources/Maintenance/Migration/v3_0/FileSystemHandler.php
new file mode 100644
index 0000000000..035a41145d
--- /dev/null
+++ b/Sources/Maintenance/Migration/v3_0/FileSystemHandler.php
@@ -0,0 +1,84 @@
+ 'filesystem_server',
+ 'package_port' => 'filesystem_port',
+ 'package_username' => 'filesystem_username',
+ 'package_path' => 'filesystem_path',
+ ];
+
+ /****************
+ * Public methods
+ ****************/
+
+ /**
+ *
+ */
+ public function isCandidate(): bool
+ {
+ return !empty(Config::$modSettings['package_server']);
+ }
+
+ /**
+ *
+ */
+ public function execute(): bool
+ {
+ $newSettings = [];
+
+ foreach ($this->renames as $oldKey => $newKey) {
+ if (!isset(Config::$modSettings[$oldKey])) {
+ continue;
+ }
+
+ $newSettings[$newKey] = Config::$modSettings[$oldKey];
+ $newSettings[$oldKey] = null;
+ }
+
+ // Hold on, do we have a 'ssl://' or 'ftps://' in the server name?
+ if (str_starts_with(Config::$modSettings['package_server'], 'ssl://') || str_starts_with(Config::$modSettings['package_server'], 'ftps://')) {
+ $server_addr = preg_replace('~^((ft|htt)ps?|ssl)?://~i', '', Config::$modSettings['package_server']);
+ $server_addr = strtr($server_addr, ['/' => '', ':' => '', '@' => '']);
+
+ $newSettings['filesystem_server'] = $server_addr;
+ $newSettings['filesystem_type'] = 'FtpSSL';
+ }
+
+ Config::updateModSettings($newSettings);
+
+ return true;
+ }
+}
diff --git a/Sources/Maintenance/Tools/ToolsBase.php b/Sources/Maintenance/Tools/ToolsBase.php
index a15017d253..c55d527e36 100644
--- a/Sources/Maintenance/Tools/ToolsBase.php
+++ b/Sources/Maintenance/Tools/ToolsBase.php
@@ -20,7 +20,8 @@
use SMF\Lang;
use SMF\Maintenance\Maintenance;
use SMF\Maintenance\Step;
-use SMF\PackageManager\FtpConnection;
+use SMF\PackageManager\FileSystem\FileSystem;
+use SMF\PackageManager\FileSystem\FileSystemInterface;
use SMF\Sapi;
use SMF\Security;
use SMF\SecurityToken;
@@ -70,11 +71,11 @@ abstract class ToolsBase
private ?Step $current_step;
/**
- * @var FtpConnection
+ * @var \SMF\PackageManager\FileSystem\FileSystemInterface
*
- * Object container for the FTP session.
+ * Object container for the File System Handler.
*/
- private FtpConnection $ftp;
+ private FileSystemInterface $fs;
/****************
* Public methods
@@ -272,7 +273,7 @@ public function canDeleteTool(): bool
!empty($this->script_file)
&& file_exists(Config::$boarddir . '/' . $this->script_file)
&& (
- !empty($_SESSION['ftp'])
+ !empty($_SESSION['fs'])
|| is_writable(Config::$boarddir)
|| is_writable(Config::$boarddir . '/' . $this->script_file)
)
@@ -290,22 +291,23 @@ public function canDeleteTool(): bool
public function deleteTool(): void
{
if ($this->canDeleteTool()) {
- if (!empty($_SESSION['ftp'])) {
- $ftp = new FtpConnection($_SESSION['ftp']['server'], $_SESSION['ftp']['port'], $_SESSION['ftp']['username'], $_SESSION['ftp']['password']);
- $ftp->chdir($_SESSION['ftp']['path']);
+ if (!empty($_SESSION['fs'])) {
+ $fs = FileSystem::load($_SESSION['fs']['type']);
+ $fs->connect($_SESSION['fs']['server'], $_SESSION['fs']['username'], $_SESSION['fs']['password'], $_SESSION['fs']['port'], $_SESSION['fs']['path']);
+ $fs->changeDirectory($_SESSION['fs']['path']);
}
- if (isset($ftp)) {
- $ftp->unlink($this->script_file);
+ if (isset($fs) && $fs->isConnected()) {
+ $fs->deleteFile($this->script_file);
} else {
@unlink(Config::$boarddir . '/' . $this->script_file);
}
- $this->deleteOldSchemaAndMaintenanceFiles($ftp ?? null);
+ $this->deleteOldSchemaAndMaintenanceFiles($fs ?? null);
- if (isset($ftp)) {
- unset($_SESSION['ftp']);
- $ftp->close();
+ if (isset($fs)) {
+ unset($_SESSION['fs']);
+ $fs->disconnect();
}
// Now just redirect to a blank.png...
@@ -314,7 +316,7 @@ public function deleteTool(): void
}
/**
- * Make files writable. First try to use regular chmod, but if that fails, try to use FTP.
+ * Make files writable. First try to use regular chmod, but if that fails, try to use a File System handler.
*
* @param array $files List of files to make writable.
* @return bool True if succesfull, false otherwise.
@@ -375,50 +377,53 @@ final public function makeFilesWritable(array &$files): bool
return false;
}
- // We're going to have to use... FTP!
+ // We're going to have to use... File Systems!
if (!empty($files)) {
// Load any session data we might have...
- if (!isset($_POST['ftp_username']) && isset($_SESSION['temp_ftp'])) {
- Maintenance::$context['chmod']['server'] = $_SESSION['temp_ftp']['server'];
- Maintenance::$context['chmod']['port'] = $_SESSION['temp_ftp']['port'];
- Maintenance::$context['chmod']['username'] = $_SESSION['temp_ftp']['username'];
- Maintenance::$context['chmod']['password'] = $_SESSION['temp_ftp']['password'];
- Maintenance::$context['chmod']['path'] = $_SESSION['temp_ftp']['path'];
+ if (!isset($_POST['fs']['username']) && isset($_SESSION['temp_fs'])) {
+ Maintenance::$context['chmod']['type'] = $_SESSION['temp_fs']['type'];
+ Maintenance::$context['chmod']['server'] = $_SESSION['temp_fs']['server'];
+ Maintenance::$context['chmod']['port'] = $_SESSION['temp_fs']['port'];
+ Maintenance::$context['chmod']['username'] = $_SESSION['temp_fs']['username'];
+ Maintenance::$context['chmod']['password'] = $_SESSION['temp_fs']['password'];
+ Maintenance::$context['chmod']['path'] = $_SESSION['temp_fs']['path'];
}
// Or have we submitted?
- elseif (isset($_POST['ftp_username'])) {
- Maintenance::$context['chmod']['server'] = $_POST['ftp_server'];
- Maintenance::$context['chmod']['port'] = $_POST['ftp_port'];
- Maintenance::$context['chmod']['username'] = $_POST['ftp_username'];
- Maintenance::$context['chmod']['password'] = $_POST['ftp_password'];
- Maintenance::$context['chmod']['path'] = $_POST['ftp_path'];
+ elseif (isset($_POST['fs']['username'])) {
+ Maintenance::$context['chmod']['type'] = $_POST['filesystem']['type'];
+ Maintenance::$context['chmod']['server'] = $_POST['filesystem']['server'];
+ Maintenance::$context['chmod']['port'] = $_POST['filesystem']['port'];
+ Maintenance::$context['chmod']['username'] = $_POST['filesystem']['username'];
+ Maintenance::$context['chmod']['password'] = $_POST['filesystem']['password'];
+ Maintenance::$context['chmod']['path'] = $_POST['filesystem']['path'];
}
if (isset(Maintenance::$context['chmod']['username'])) {
- $ftp = new FtpConnection(Maintenance::$context['chmod']['server'], Maintenance::$context['chmod']['port'], Maintenance::$context['chmod']['username'], Maintenance::$context['chmod']['password']);
+ $fs = FileSystem::load(Maintenance::$context['chmod']['type']);
+ $fs->connect(Maintenance::$context['chmod']['server'], Maintenance::$context['chmod']['username'], Maintenance::$context['chmod']['password'], Maintenance::$context['chmod']['port'], Maintenance::$context['chmod']['path']);
- if ($ftp->error === false) {
+ if ($fs->isConnected() && $fs->getLastError() === false) {
// Try it without /home/abc just in case they messed up.
- if (!$ftp->chdir(Maintenance::$context['chmod']['path'])) {
- Maintenance::$context['chmod']['ftp_error'] = $ftp->last_message;
- $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', Maintenance::$context['chmod']['path']));
+ if (!$fs->changeDirectory(Maintenance::$context['chmod']['path'])) {
+ Maintenance::$context['chmod']['fs_error'] = $fs->getLastMessage();
+ $fs->changeDirectory(preg_replace('~^/home[2]?/[^/]+?~', '', Maintenance::$context['chmod']['path']));
}
}
}
- if (!isset($ftp) || $ftp->error !== false) {
- if (!isset($ftp)) {
- $ftp = new FtpConnection(null);
+ if (!isset($fs) || $fs->getLastError() !== false) {
+ if (!isset($fs)) {
+ $fs = FileSystem::load();
}
// Save the error so we can mess with listing...
elseif (
- $ftp->error !== false
- && !isset(Maintenance::$context['chmod']['ftp_error'])
+ $fs->getLastError() !== false
+ && !isset(Maintenance::$context['chmod']['fs_error'])
) {
- Maintenance::$context['chmod']['ftp_error'] = $ftp->last_message === null ? '' : $ftp->last_message;
+ Maintenance::$context['chmod']['fs_error'] = $fs->getLastMessage() === null ? '' : $fs->getLastMessage();
}
- list($username, $detect_path, $found_path) = $ftp->detect_path(\dirname(__FILE__));
+ list($username, $detect_path, $found_path) = $fs->detectForumPath(\dirname(__FILE__));
if ($found_path || !isset(Maintenance::$context['chmod']['path'])) {
Maintenance::$context['chmod']['path'] = $detect_path;
@@ -434,56 +439,34 @@ final public function makeFilesWritable(array &$files): bool
return false;
}
- // We want to do a relative path for FTP.
- if (!\in_array(Maintenance::$context['chmod']['path'], ['', '/'])) {
- $ftp_root = strtr(Config::$boarddir, [Maintenance::$context['chmod']['path'] => '']);
-
- if (substr($ftp_root, -1) == '/' && (Maintenance::$context['chmod']['path'] == '' || Maintenance::$context['chmod']['path'][0] === '/')) {
- $ftp_root = substr($ftp_root, 0, -1);
- }
- } else {
- $ftp_root = Config::$boarddir;
- }
-
// Save the info for next time!
- $_SESSION['temp_ftp'] = [
+ $_SESSION['temp_fs'] = [
+ 'type' => Maintenance::$context['chmod']['type'],
'server' => Maintenance::$context['chmod']['server'],
'port' => Maintenance::$context['chmod']['port'],
'username' => Maintenance::$context['chmod']['username'],
'password' => Maintenance::$context['chmod']['password'],
'path' => Maintenance::$context['chmod']['path'],
- 'root' => $ftp_root,
];
foreach ($files as $k => $file) {
- $this->logProgress(Lang::getTxt('log_ensuring_file_writable_ftp', ['file' => $file], file: 'Maintenance'), true);
+ $this->logProgress(Lang::getTxt('log_ensuring_file_writable_fs', ['file' => $file], file: 'Maintenance'), true);
if (!is_writable($file)) {
- $ftp->chmod($file, 0755);
+ $fs->changePermissions($file, '0755');
}
if (!is_writable($file)) {
- $ftp->chmod($file, 0777);
+ $fs->changePermissions($file, '0777');
}
// Assuming that didn't work calculate the path without the boarddir.
if (!is_writable($file)) {
if (strpos($file, Config::$boarddir) === 0) {
- $ftp_file = strtr($file, [$_SESSION['installer_temp_ftp']['root'] => '']);
- $ftp->chmod($ftp_file, 0755);
-
- if (!is_writable($file)) {
- $ftp->chmod($ftp_file, 0777);
- }
- // Sometimes an extra slash can help...
- $ftp_file = '/' . $ftp_file;
-
- if (!is_writable($file)) {
- $ftp->chmod($ftp_file, 0755);
- }
+ $fs->changePermissions($file, '0755');
if (!is_writable($file)) {
- $ftp->chmod($ftp_file, 0777);
+ $fs->changePermissions($file, '0777');
}
}
}
@@ -496,7 +479,7 @@ final public function makeFilesWritable(array &$files): bool
}
}
- $ftp->close();
+ $fs->disconnect();
}
// What remains?
@@ -627,7 +610,7 @@ public function updateModSettings(array $change_array, bool $update = false): bo
*
* This should be done only when the install or upgrade process is complete.
*/
- protected function deleteOldSchemaAndMaintenanceFiles(?FtpConnection $ftp): void
+ protected function deleteOldSchemaAndMaintenanceFiles(?FileSystemInterface $fs): void
{
if (!isset(Config::$modSettings['smf_version'])) {
Config::reloadModSettings();
@@ -656,16 +639,16 @@ protected function deleteOldSchemaAndMaintenanceFiles(?FtpConnection $ftp): void
$file_list = new \GlobIterator($dir->getPathname() . '/*', \FilesystemIterator::NEW_CURRENT_AND_KEY);
foreach ($file_list as $file) {
- if (isset($ftp)) {
- $ftp->unlink(str_replace(Config::$boarddir . '/', '', $file->getPathname()));
+ if (isset($fs)) {
+ $fs->deleteFile($file->getPathname());
} else {
Utils::makeWritable($file->getPathname());
@unlink($file->getPathname());
}
}
- if (isset($ftp)) {
- $ftp->unlink(str_replace(Config::$boarddir . '/', '', $dir->getPathname()));
+ if (isset($fs)) {
+ $fs->deleteDirectory($dir->getPathname());
} else {
@rmdir($dir->getPathname());
}
diff --git a/Sources/Maintenance/Tools/Upgrade.php b/Sources/Maintenance/Tools/Upgrade.php
index 52deeb2095..97243d1405 100644
--- a/Sources/Maintenance/Tools/Upgrade.php
+++ b/Sources/Maintenance/Tools/Upgrade.php
@@ -175,6 +175,7 @@ class Upgrade extends ToolsBase implements ToolsInterface
Migration\v3_0\MailType::class,
Migration\v3_0\RemoveCookieTime::class,
Migration\v3_0\PermissionChanges::class,
+ Migration\v3_0\FileSystemHandler::class,
],
];
diff --git a/Sources/PackageManager/FileSystem/APIs/Ftp.php b/Sources/PackageManager/FileSystem/APIs/Ftp.php
new file mode 100644
index 0000000000..f606787ad0
--- /dev/null
+++ b/Sources/PackageManager/FileSystem/APIs/Ftp.php
@@ -0,0 +1,700 @@
+server_addr = $this->getServerAddress($server);
+
+ if ($root !== null) {
+ $this->forum_root = $root;
+ }
+
+ // Connect to the FTP server.
+ $this->connection = @fsockopen($this->server_addr, $port, $err, $errMsg, self::RESPONSE_TIMEOUT);
+ $this->last_message = $errMsg;
+
+ if (!$this->connection) {
+ $this->error = 'bad_server';
+
+ return false;
+ }
+
+ // Get the welcome message...
+ if (!$this->checkResponse(220)) {
+ $this->error = 'bad_response';
+
+ return false;
+ }
+
+ // Send the username, it should ask for a password.
+ fwrite($this->connection, 'USER ' . $username . "\r\n");
+
+ if (!$this->checkResponse(331)) {
+ $this->error = 'bad_credentials';
+
+ return false;
+ }
+
+ // Now send the password... and hope it goes okay.
+ fwrite($this->connection, 'PASS ' . $password . "\r\n");
+
+ if (!$this->checkResponse(230)) {
+ $this->error = 'bad_credentials';
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Close the ftp connection
+ *
+ * @return bool Always returns true
+ */
+ public function disconnect(): bool
+ {
+ if (\is_resource($this->connection)) {
+ fwrite($this->connection, 'QUIT' . "\r\n");
+ fclose($this->connection);
+ }
+
+ $this->connection = null;
+ $this->error = null;
+ $this->last_message = null;
+
+ return true;
+ }
+
+ /**
+ * Determines if we have a connection to the ftp server.
+ *
+ * @return bool
+ */
+ public function isConnected(): bool
+ {
+ return $this->connection !== null && empty($this->error);
+ }
+
+ /**
+ * Changes to a directory (chdir) via the ftp connection
+ *
+ * @param string $directory The path to the directory we want to change to
+ * @return bool Whether or not the operation was successful
+ */
+ public function changeDirectory(string $directory): bool
+ {
+ $directory = $this->normalizeFilename($directory);
+
+ if (!\is_resource($this->connection)) {
+ return false;
+ }
+
+ // No slash on the end, please...
+ $directory = rtrim($directory, '/');
+
+ $directory = $this->normalizeFilename($directory);
+
+ // If we are trying to chdir to the same directory, we most likely meant the forum root.
+ if ($directory === '.') {
+ $directory = $this->forum_root;
+ }
+
+ fwrite($this->connection, 'CWD ' . $directory . "\r\n");
+
+ if (!$this->checkResponse(250)) {
+ $this->error = 'bad_path';
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Changes a files attributes (chmod)
+ *
+ * @param string $filename The file to CHMOD
+ * @param int|string $chmod The value for the CHMOD operation
+ * @return bool Whether or not the operation was successful
+ */
+ public function changePermissions(string $filename, string $chmod): bool
+ {
+ if (!\is_resource($this->connection)) {
+ return false;
+ }
+
+ $filename = $this->normalizeFilename($filename);
+
+ // Convert the chmod value from octal (0777) to text ("777").
+ fwrite($this->connection, 'SITE CHMOD ' . decoct((int) $chmod) . ' ' . $filename . "\r\n");
+
+ if (!$this->checkResponse(200)) {
+ $this->error = 'bad_file';
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Creates a new directory on the server
+ *
+ * @param string $directory The name of the directory to create
+ * @return bool Whether or not the operation was successful
+ */
+ public function createDirectory(string $directory): bool
+ {
+ if (!\is_resource($this->connection)) {
+ return false;
+ }
+
+ $directory = $this->normalizeFilename($directory);
+
+ // Make this new beautiful directory!
+ fwrite($this->connection, 'MKD ' . $directory . "\r\n");
+
+ if (!$this->checkResponse(257)) {
+ $this->error = 'bad_file';
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Deletes a directory.
+ * If this fails, we attempt to delete as a file.
+ *
+ * @param string $directory The directory to delete
+ * @return bool Whether or not the operation was successful
+ */
+ public function deleteDirectory(string $directory): bool
+ {
+ if (!\is_resource($this->connection)) {
+ return false;
+ }
+
+ $directory = $this->normalizeFilename($directory);
+
+ // Delete directory.
+ fwrite($this->connection, 'RMD ' . $directory . "\r\n");
+
+ // If this failed, its possible they passed a file, not a directory.
+ if (!$this->checkResponse(250)) {
+ fwrite($this->connection, 'DELE ' . $directory . "\r\n");
+
+ // Still no love?
+ if (!$this->checkResponse(250)) {
+ $this->error = 'bad_file';
+
+ return false;
+ }
+ ErrorHandler::log(Lang::getTxt('filesystem_error_delete_filedirectory', [], 'Packages'));
+
+ }
+
+ return true;
+ }
+
+ /**
+ * Creates a new file on the server
+ *
+ * @param string $filename The file to create
+ * @return bool Whether or not the file was created successfully
+ */
+ public function createFile(string $filename): bool
+ {
+ return $this->writeFile($filename, null);
+ }
+
+ /**
+ * Deletes a file
+ * If this fails, we attempt to delete as a directory
+ *
+ * @param string $filename The file to delete
+ * @return bool Whether or not the operation was successful
+ */
+ public function deleteFile(string $filename): bool
+ {
+ if (!\is_resource($this->connection)) {
+ return false;
+ }
+
+ $filename = $this->normalizeFilename($filename);
+
+ // Delete file X.
+ fwrite($this->connection, 'DELE ' . $filename . "\r\n");
+
+ // If this failed, its possible they passed a directory, not a file.
+ if (!$this->checkResponse(250)) {
+ fwrite($this->connection, 'RMD ' . $filename . "\r\n");
+
+ // Still no love?
+ if (!$this->checkResponse(250)) {
+ $this->error = 'bad_file';
+
+ return false;
+ }
+ ErrorHandler::log(Lang::getTxt('filesystem_error_delete_directoryfile', [], 'Packages'));
+
+ }
+
+ return true;
+ }
+
+ /**
+ * Writes contents to a file.
+ * If file does not exist, we create it first.
+ *
+ * @param string $filename The file to create
+ * @return bool Whether or not the file was created successfully
+ */
+ public function writeFile(string $filename, ?string $contents): bool
+ {
+ if (!\is_resource($this->connection)) {
+ return false;
+ }
+
+ if (!$this->enterPassiveMode()) {
+ return false;
+ }
+
+ $filename = $this->normalizeFilename($filename);
+
+ // Seems logical enough, so far...
+ fwrite($this->connection, 'STOR ' . $filename . "\r\n");
+
+ // Okay, now we connect to the data port. If it doesn't work out, it's probably "file already exists", etc.
+ $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $errMsg, 5);
+ $this->last_message = $errMsg;
+
+ if (!\is_resource($fp) || !$this->checkResponse(150)) {
+ $this->error = 'bad_file';
+
+ if (\is_resource($fp)) {
+ fclose($fp);
+ }
+
+ return false;
+ }
+
+ // Write in chunks.
+ if ($contents !== null) {
+ $pieces = str_split($contents, 1024 * 4);
+
+ foreach ($pieces as $piece) {
+ fwrite($fp, $piece, \strlen($piece));
+ }
+ }
+ fclose($fp);
+
+ if (!$this->checkResponse(226)) {
+ $this->error = 'bad_response';
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public function detectForumPath(string $directory, ?string $lookup_file = null): array
+ {
+ // Start of with the base logic.
+ [$username, $path] = parent::detectForumPath($directory, $lookup_file);
+
+ if ($this->connection === null) {
+ return [$username, $path, false];
+ }
+
+ // Determine if we can find this.
+ $found_path = false;
+
+ if ($this->listDirectory($path) == '') {
+ $data = $this->listDirectory('', true);
+
+ if ($lookup_file === null) {
+ $lookup_file = $_SERVER['PHP_SELF'];
+ }
+
+ $found_path = \dirname($this->locate('*' . basename(\dirname($lookup_file)) . '/' . basename($lookup_file), $data));
+
+ if ($found_path == false) {
+ $found_path = \dirname($this->locate(basename($lookup_file)));
+ }
+
+ if ($found_path != false) {
+ $path = $found_path;
+ }
+ } elseif (\is_resource($this->connection)) {
+ $found_path = true;
+ }
+
+ return [$username, $path, $found_path];
+ }
+
+ /**
+ * For our deprecated FtpConnection class, returns the passive mode data.
+ *
+ * @return array{ip: string, port: int}
+ */
+ public function getPassiveMode(): array
+ {
+ return $this->pasv;
+ }
+
+ /**
+ * Currently this just handles some support for legacy FtpConnection.
+ */
+ public function __construct()
+ {
+ $this->legacy_pasv = &$this->pasv;
+ $this->legacy_error = &$this->error;
+ $this->legacy_last_message = &$this->last_message;
+ }
+
+ /**
+ * If we destroy this class, ensure we are disconnected.
+ */
+ public function __destruct()
+ {
+ $this->disconnect();
+ }
+
+ /**
+ * Determine the server address by cleansing of anything we don't like from the url.
+ * This is unable to use parse_url as we don't pass enough of a url looking string.
+ *
+ * @param string $server_addr
+ * @return string
+ */
+ public function getServerAddress(string $server_addr): string
+ {
+ $server_addr = preg_replace('~^((ft|htt)ps?|ssl)?://~i', '', $server_addr);
+ $server_addr = strtr($server_addr, ['/' => '', ':' => '', '@' => '']);
+
+ return $server_addr;
+ }
+
+ /**
+ * Used to create a passive connection.
+ * Attempts to use Extended Passive mode for IPv6 support.
+ * Falls back to PASV, which fails with IPv6 connections.
+ *
+ * @todo When Deprecated class FtpConnection is removed, this can become protected.
+ * @return bool Whether the passive connection was created successfully
+ */
+ public function enterPassiveMode(): bool
+ {
+ if (!\is_resource($this->connection)) {
+ $this->error = 'no_connection';
+
+ return false;
+ }
+
+ // Lets try EPSV, it supports IPv6.
+ @fwrite($this->connection, 'EPSV' . "\r\n");
+ $time = time();
+
+ do {
+ $response = fgets($this->connection, 1024);
+ } while (strpos($response, ' ', 3) !== 3 && time() - $time < 5);
+
+ // If it's not 229, we weren't given an port, which means it failed.
+ if (str_starts_with($response, '229 ')) {
+ // Snatch the port information.
+ if (preg_match('~\(\|{3}(\d+)\|\)~', $response, $match) !== 0) {
+ // EPSV assumes that the IP is the address you connected to.
+ $this->pasv = ['ip' => $this->server_addr, 'port' => (int) $match[1]];
+
+ return true;
+ }
+ }
+
+ // Request a passive connection - this means, we'll talk to you, you don't talk to us.
+ @fwrite($this->connection, 'PASV' . "\r\n");
+ $time = time();
+
+ do {
+ $response = fgets($this->connection, 1024);
+ } while (strpos($response, ' ', 3) !== 3 && time() - $time < self::RESPONSE_TIMEOUT);
+
+ // If it's not 227, we weren't given an IP and port, which means it failed.
+ if (!str_starts_with($response, '227 ')) {
+ $this->error = 'bad_response';
+
+ return false;
+ }
+
+ // Snatch the IP and port information, or die horribly trying...
+ if (preg_match('~\((\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))\)~', $response, $match) == 0) {
+ $this->error = 'bad_response';
+
+ return false;
+ }
+
+ // This is pretty simple - store it for later use ;).
+ $this->pasv = ['ip' => $match[1] . '.' . $match[2] . '.' . $match[3] . '.' . $match[4], 'port' => $match[5] * 256 + $match[6]];
+
+ return true;
+ }
+
+ /**
+ * Generates a directory listing for the current directory
+ *
+ * @todo When Deprecated class FtpConnection is removed, this can become private.
+ * @param string $ftp_path The path to the directory
+ * @param bool $search Whether or not to get a recursive directory listing
+ * @return string|bool The results of the command or false if unsuccessful
+ */
+ public function listDirectory(string $ftp_path = '', bool $search = false): string|bool
+ {
+ if (!\is_resource($this->connection)) {
+ return false;
+ }
+
+ // Passive... non-aggressive...
+ if (!$this->enterPassiveMode()) {
+ return false;
+ }
+
+ // Get the listing!
+ fwrite($this->connection, 'LIST -1' . ($search ? 'R' : '') . ($ftp_path == '' ? '' : ' ' . $ftp_path) . "\r\n");
+
+ // Connect, assuming we've got a connection.
+ $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err_msg, 5);
+
+ if (!empty($err_msg)) {
+ $this->last_message = $err_msg;
+
+ return false;
+ }
+
+ if (!\is_resource($fp) || !$this->checkResponse([150, 125])) {
+ $this->error = 'bad_response';
+
+ if (\is_resource($fp)) {
+ fclose($fp);
+ }
+
+ return false;
+ }
+
+ // Read in the file listing.
+ $data = '';
+
+ while (!feof($fp)) {
+ $data .= fread($fp, 4096);
+ }
+ fclose($fp);
+
+ // Everything go okay?
+ if (!$this->checkResponse(226)) {
+ $this->error = 'bad_response';
+
+ return false;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Determines the current directory we are in
+ *
+ * @todo When Deprecated class FtpConnection is removed, this can become private.
+ * @param string $file The name of a file
+ * @param null|string $listing A directory listing or null to generate one
+ * @return string|bool The name of the file or false if it wasn't found
+ */
+ public function locate(string $file, ?string $listing = null): string|bool
+ {
+ if ($listing === null) {
+ $listing = $this->listDirectory('', true);
+ }
+ $listing = explode("\n", $listing);
+
+ @fwrite($this->connection, 'PWD' . "\r\n");
+ $time = time();
+
+ do {
+ $response = fgets($this->connection, 1024);
+ } while ($response[3] != ' ' && time() - $time < 5);
+
+ // Check for 257!
+ if (preg_match('~^257 "(.+?)" ~', $response, $match) != 0) {
+ $current_dir = strtr($match[1], ['""' => '"']);
+ } else {
+ $current_dir = '';
+ }
+
+ for ($i = 0, $n = \count($listing); $i < $n; $i++) {
+ if (trim($listing[$i]) == '' && isset($listing[$i + 1])) {
+ $current_dir = substr(trim($listing[++$i]), 0, -1);
+ $i++;
+ }
+
+ // Okay, this file's name is:
+ $listing[$i] = $current_dir . '/' . trim(\strlen($listing[$i]) > 30 ? strrchr($listing[$i], ' ') : $listing[$i]);
+
+ if ($file[0] == '*' && substr($listing[$i], -(\strlen($file) - 1)) == substr($file, 1)) {
+ return $listing[$i];
+ }
+
+ if (str_ends_with($file, '*') && substr($listing[$i], 0, \strlen($file) - 1) == substr($file, 0, -1)) {
+ return $listing[$i];
+ }
+
+ if (basename($listing[$i]) == $file || $listing[$i] == $file) {
+ return $listing[$i];
+ }
+ }
+
+ return false;
+ }
+
+ /******************
+ * Internal methods
+ ******************/
+
+ /**
+ * Reads the response to the command from the server
+ *
+ * @param int|string|array $desired The desired response
+ * @return bool Whether or not we got the desired response
+ */
+ protected function checkResponse(int|string|array $desired): bool
+ {
+ // Wait for a response that isn't continued with -, but don't wait too long.
+ $time = time();
+
+ do {
+ $this->last_message = fgets($this->connection, 1024);
+
+ if ($this->last_message === false) {
+ return false;
+ }
+ } while ((\strlen($this->last_message) < 4 || str_starts_with($this->last_message, ' ') || strpos($this->last_message, ' ', 3) !== 3) && time() - $time < self::RESPONSE_TIMEOUT);
+
+ // Was the desired response returned?
+ return \is_array($desired) ? \in_array(substr($this->last_message, 0, 3), $desired) : substr($this->last_message, 0, 3) == $desired;
+ }
+}
diff --git a/Sources/PackageManager/FileSystem/APIs/FtpSSL.php b/Sources/PackageManager/FileSystem/APIs/FtpSSL.php
new file mode 100644
index 0000000000..f7b063bd5d
--- /dev/null
+++ b/Sources/PackageManager/FileSystem/APIs/FtpSSL.php
@@ -0,0 +1,363 @@
+forum_root = $root;
+ }
+
+ $this->connection = ftp_ssl_connect(
+ $this->getServerAddress($server),
+ $port,
+ self::RESPONSE_TIMEOUT,
+ );
+
+ if (($this->connection ?? false) === false) {
+ $this->error = 'bad_server';
+
+ return false;
+ }
+
+ // login with username and password
+ if (!$this->tryCall('login', $this->connection, $username, $password)) {
+ $this->connection = null;
+ $this->error = 'bad_credentials';
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Close the ftp connection
+ *
+ * @return bool Always returns true
+ */
+ public function disconnect(): bool
+ {
+ if ($this->connection ?? false !== false) {
+ $this->tryCall('close', $this->connection);
+ }
+
+ $this->connection = null;
+ $this->error = null;
+ $this->last_message = null;
+
+ return true;
+ }
+
+ /**
+ * Determines if we have a connection to the ftp server.
+ *
+ * @return bool
+ */
+ public function isConnected(): bool
+ {
+ return $this->connection !== null && empty($this->error);
+ }
+
+ /**
+ * Changes to a directory (chdir) via the ftp connection
+ *
+ * @param string $directory The path to the directory we want to change to
+ * @return bool Whether or not the operation was successful
+ */
+ public function changeDirectory(string $directory): bool
+ {
+ if (($this->connection ?? false) === false) {
+ return false;
+ }
+
+ $directory = $this->normalizeFilename($directory);
+
+ return $this->tryCall('chdir', $this->connection, $directory);
+ }
+
+ /**
+ * Changes a files attributes (chmod)
+ *
+ * @param string $filename The file to CHMOD
+ * @param int|string $chmod The value for the CHMOD operation
+ * @return bool Whether or not the operation was successful
+ */
+ public function changePermissions(string $filename, string $chmod): bool
+ {
+ if (($this->connection ?? false) === false) {
+ return false;
+ }
+
+ $filename = $this->normalizeFilename($filename);
+
+ return $this->tryCall('chmod', $this->connection, decoct((int) $chmod), $filename);
+ }
+
+ /**
+ * Creates a new directory on the server
+ *
+ * @param string $directory The name of the directory to create
+ * @return bool Whether or not the operation was successful
+ */
+ public function createDirectory(string $directory): bool
+ {
+ if (($this->connection ?? false) === false) {
+ return false;
+ }
+
+ $directory = $this->normalizeFilename($directory);
+
+ return $this->tryCall('mkdir', $this->connection, $directory);
+ }
+
+ /**
+ * Deletes a directory.
+ * If this fails, we attempt to delete as a file.
+ *
+ * @param string $directory The directory to delete
+ * @return bool Whether or not the operation was successful
+ */
+ public function deleteDirectory(string $directory): bool
+ {
+ if (($this->connection ?? false) === false) {
+ return false;
+ }
+
+ $directory = $this->normalizeFilename($directory);
+
+ if ($this->tryCall('rmdir', $this->connection, $directory)) {
+ return true;
+ }
+
+ // Try to just unlink it, maybe we sent a file.
+ if ($this->tryCall('delete', $this->connection, $directory)) {
+ ErrorHandler::log(Lang::getTxt('filesystem_error_delete_filedirectory', [], 'Packages'));
+
+ return true;
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Creates a new file on the server
+ *
+ * @param string $filename The file to create
+ * @return bool Whether or not the file was created successfully
+ */
+ public function createFile(string $filename): bool
+ {
+ return $this->writeFile($filename, null);
+ }
+
+ /**
+ * Deletes a file
+ * If this fails, we attempt to delete as a directory
+ *
+ * @param string $filename The file to delete
+ * @return bool Whether or not the operation was successful
+ */
+ public function deleteFile(string $filename): bool
+ {
+ if (($this->connection ?? false) === false) {
+ return false;
+ }
+
+ $filename = $this->normalizeFilename($filename);
+
+ if ($this->tryCall('delete', $this->connection, $filename)) {
+ return true;
+ }
+
+ // It failed, its possible this is a directory.
+ if ($this->tryCall('rmdir', $this->connection, $filename)) {
+ ErrorHandler::log(Lang::getTxt('filesystem_error_delete_directoryfile', [], 'Packages'));
+
+ return true;
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Writes contents to a file.
+ * If file does not exist, we create it first.
+ *
+ * @param string $filename The file to create
+ * @return bool Whether or not the file was created successfully
+ */
+ public function writeFile(string $filename, ?string $contents): bool
+ {
+ if (($this->connection ?? false) === false) {
+ return false;
+ }
+
+ if (!$this->tryCall('pasv', $this->connection, true)) {
+ return false;
+ }
+
+ $filename = $this->normalizeFilename($filename);
+
+ $stream = fopen('data://text/plain,' . $contents, 'r');
+
+ $result = $this->tryCall('fput', $this->connection, $filename, $stream, FTP_BINARY);
+
+ $this->tryCall('pasv', $this->connection, false);
+
+ return $result;
+ }
+
+ public function detectForumPath(string $directory, ?string $lookup_file = null): array
+ {
+ // Start of with the base logic.
+ [$username, $path] = parent::detectForumPath($directory, $lookup_file);
+
+ if (($this->connection ?? false) === false) {
+ return [$username, $path, false];
+ }
+
+ $lookup_file ??= $_SERVER['PHP_SELF'];
+ $files = $this->tryCall('mlsd', $this->connection, $directory);
+
+ $found_path = array_search($lookup_file, array_column($files, 'name')) !== false;
+
+ return [$username, $path, $found_path];
+ }
+
+ /**
+ * If we destroy this class, ensure we are disconnected.
+ */
+ public function __destruct()
+ {
+ $this->disconnect();
+ }
+
+ /******************
+ * Internal methods
+ ******************/
+
+ /**
+ * Determine the server address by cleansing of anything we don't like from the url.
+ * This is unable to use parse_url as we don't pass enough of a url looking string.
+ *
+ * @param string $server_addr
+ * @return string
+ */
+ private function getServerAddress(string $server_addr): string
+ {
+ $server_addr = preg_replace('~^((ft|htt)ps?|ssl)?://~i', '', $server_addr);
+ $server_addr = strtr($server_addr, ['/' => '', ':' => '', '@' => '']);
+
+ return $server_addr;
+ }
+
+ /**
+ * Wraps the ftp functions, capturing the errors and rethrowing them.
+ * The errors are then captured and put into the error and last mesaage handlers.
+ *
+ * @param mixed $method
+ * @param array $args
+ * @return bool|array
+ */
+ private function tryCall(
+ $method,
+ #[\SensitiveParameter]
+ ...$args,
+ ): bool|array {
+ try {
+ set_error_handler(static function ($severity, $message, $file, $line) {
+ throw new \ErrorException($message, 0, $severity, $file, $line);
+ });
+
+ return \call_user_func('ftp_' . $method, ...$args);
+ } catch (\Throwable $e) {
+ $this->error = $e->getCode();
+ $this->last_message = $e->getMessage();
+
+ return false;
+ } finally {
+ restore_error_handler();
+ }
+ }
+}
diff --git a/Sources/PackageManager/FileSystem/APIs/SFTP.php b/Sources/PackageManager/FileSystem/APIs/SFTP.php
new file mode 100644
index 0000000000..c47c208684
--- /dev/null
+++ b/Sources/PackageManager/FileSystem/APIs/SFTP.php
@@ -0,0 +1,383 @@
+forum_root = $root;
+ }
+
+ try {
+ $this->connection = new PhpSFTP($this->getServerAddress($server), $port, self::RESPONSE_TIMEOUT);
+
+ if ($this->connection->getLastSFTPError() !== '') {
+ $this->error = 'bad_server';
+ $this->last_message = $this->connection->getLastSFTPError();
+
+ return false;
+ }
+ } catch (\Exception $e) {
+ $this->error = 'bad_server';
+ $this->last_message = $e->getMessage();
+ }
+
+ try {
+ // login with username and password
+ if (!$this->connection->login($username, $password)) {
+ $this->connection = null;
+ $this->error = 'bad_credentials';
+ $this->last_message = $this->connection->getLastSFTPError();
+
+ return false;
+ }
+ } catch (\Exception $e) {
+ $this->error = 'bad_credentials';
+ $this->last_message = $e->getMessage();
+ }
+
+ return true;
+ }
+
+ /**
+ * Close the ftp connection
+ *
+ * @return bool Always returns true
+ */
+ public function disconnect(): bool
+ {
+ if ($this->connection instanceof PhpSFTP) {
+ $this->connection->disconnect();
+ }
+
+ $this->connection = null;
+ $this->error = null;
+ $this->last_message = null;
+
+ return true;
+ }
+
+ /**
+ * Determines if we have a connection to the sftp server.
+ *
+ * @return bool
+ */
+ public function isConnected(): bool
+ {
+ return $this->connection instanceof PhpSFTP && empty($this->error);
+ }
+
+ /**
+ * Changes to a directory (chdir) via the ftp connection
+ *
+ * @param string $directory The path to the directory we want to change to
+ * @return bool Whether or not the operation was successful
+ */
+ public function changeDirectory(string $directory): bool
+ {
+ if (!$this->connection instanceof PhpSFTP || !$this->connection->isAuthenticated()) {
+ return false;
+ }
+
+ $directory = $this->normalizeFilename($directory);
+
+ try {
+ return $this->connection->chdir($directory);
+ } catch (\Exception $e) {
+ $this->error = 'bad_path';
+ $this->last_message = $e->getMessage();
+ }
+
+ return false;
+ }
+
+ /**
+ * Changes a files attributes (chmod)
+ *
+ * @param string $filename The file to CHMOD
+ * @param int|string $chmod The value for the CHMOD operation
+ * @return bool Whether or not the operation was successful
+ */
+ public function changePermissions(string $filename, string $chmod): bool
+ {
+ if (!$this->connection instanceof PhpSFTP || !$this->connection->isAuthenticated()) {
+ return false;
+ }
+
+ $filename = $this->normalizeFilename($filename);
+
+ try {
+ return $this->connection->chmod($chmod, $filename) !== false;
+ } catch (\Exception $e) {
+ $this->error = 'bad_file';
+ $this->last_message = $e->getMessage();
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates a new directory on the server
+ *
+ * @param string $directory The name of the directory to create
+ * @return bool Whether or not the operation was successful
+ */
+ public function createDirectory(string $directory): bool
+ {
+ if (!$this->connection instanceof PhpSFTP || !$this->connection->isAuthenticated()) {
+ return false;
+ }
+
+ $directory = $this->normalizeFilename($directory);
+
+ try {
+ return $this->connection->mkdir($directory);
+ } catch (\Exception $e) {
+ $this->error = 'bad_path';
+ $this->last_message = $e->getMessage();
+ }
+
+ return false;
+ }
+
+ /**
+ * Deletes a directory.
+ * If this fails, we attempt to delete as a file.
+ *
+ * @param string $directory The directory to delete
+ * @return bool Whether or not the operation was successful
+ */
+ public function deleteDirectory(string $directory): bool
+ {
+ if (!$this->connection instanceof PhpSFTP || !$this->connection->isAuthenticated()) {
+ return false;
+ }
+
+ $directory = $this->normalizeFilename($directory);
+
+ try {
+ if ($this->connection->rmdir($directory)) {
+ return true;
+ }
+
+ // Try to just unlink it, maybe we sent a file.
+ if ($this->connection->delete($directory)) {
+ ErrorHandler::log(Lang::getTxt('filesystem_error_delete_filedirectory', [], 'Packages'));
+
+ return true;
+ }
+
+ return false;
+
+ } catch (\Exception $e) {
+ $this->error = 'bad_path';
+ $this->last_message = $e->getMessage();
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates a new file on the server
+ *
+ * @param string $filename The file to create
+ * @return bool Whether or not the file was created successfully
+ */
+ public function createFile(string $filename): bool
+ {
+ return $this->writeFile($filename, null);
+ }
+
+ /**
+ * Deletes a file
+ * If this fails, we attempt to delete as a directory
+ *
+ * @param string $filename The file to delete
+ * @return bool Whether or not the operation was successful
+ */
+ public function deleteFile(string $filename): bool
+ {
+ if (!$this->connection instanceof PhpSFTP || !$this->connection->isAuthenticated()) {
+ return false;
+ }
+
+ $filename = $this->normalizeFilename($filename);
+
+ try {
+ if ($this->connection->delete($filename)) {
+ return true;
+ }
+
+ // It failed, its possible this is a directory.
+ if ($this->connection->rmdir($filename)) {
+ ErrorHandler::log(Lang::getTxt('filesystem_error_delete_directoryfile', [], 'Packages'));
+
+ return true;
+ }
+
+ return false;
+
+ } catch (\Exception $e) {
+ $this->error = 'bad_file';
+ $this->last_message = $e->getMessage();
+ }
+
+ return false;
+ }
+
+ /**
+ * Writes contents to a file.
+ * If file does not exist, we create it first.
+ *
+ * @param string $filename The file to create
+ * @return bool Whether or not the file was created successfully
+ */
+ public function writeFile(string $filename, ?string $contents): bool
+ {
+ if (!$this->connection instanceof PhpSFTP || !$this->connection->isAuthenticated()) {
+ return false;
+ }
+
+ $filename = $this->normalizeFilename($filename);
+
+ try {
+ return $this->connection->put($filename, $contents);
+ } catch (\Exception $e) {
+ $this->error = 'bad_file';
+ $this->last_message = $e->getMessage();
+ }
+
+ return false;
+ }
+
+ public function detectForumPath(string $directory, ?string $lookup_file = null): array
+ {
+ // Start of with the base logic.
+ [$username, $path] = parent::detectForumPath($directory, $lookup_file);
+ $found_path = false;
+
+ if (!$this->connection instanceof PhpSFTP || !$this->connection->isAuthenticated()) {
+ return [$username, $path, false];
+ }
+
+ try {
+ $lookup_file ??= $_SERVER['PHP_SELF'];
+ $files = $this->connection->nlist($directory);
+
+ if (\is_array($files)) {
+ $found_path = array_search($lookup_file, $files) !== false;
+ }
+ } catch (\Exception $e) {
+ $this->error = 'bad_response';
+ $this->last_message = $e->getMessage();
+ }
+
+ return [$username, $path, $found_path];
+ }
+
+ /**
+ * If we destroy this class, ensure we are disconnected.
+ */
+ public function __destruct()
+ {
+ $this->disconnect();
+ }
+
+ /******************
+ * Internal methods
+ ******************/
+
+ /**
+ * Determine the server address by cleansing of anything we don't like from the url.
+ * This is unable to use parse_url as we don't pass enough of a url looking string.
+ *
+ * @param string $server_addr
+ * @return string
+ */
+ private function getServerAddress(string $server_addr): string
+ {
+ $server_addr = preg_replace('~^(s(ftp|s[h|l]))?://~i', '', $server_addr);
+ $server_addr = strtr($server_addr, ['/' => '', ':' => '', '@' => '']);
+
+ return $server_addr;
+ }
+}
diff --git a/Sources/PackageManager/FileSystem/APIs/index.php b/Sources/PackageManager/FileSystem/APIs/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/PackageManager/FileSystem/APIs/index.php
@@ -0,0 +1,8 @@
+min_smf_version, $smfVersion, '<=') && version_compare($smfVersion, $this->version_compatible, '>=');
+ }
+
+ /**
+ * Provides additional settings for the settings page.
+ *
+ * @param array $config_vars Current configuration settings, passed by reference. Append to add more.
+ */
+ public function getConfigVars(array &$config_vars): void {}
+
+ /**
+ * Gets the min version that we support.
+ *
+ * @return string the value of $key.
+ */
+ public function getMinimumVersion(): string
+ {
+ return $this->min_smf_version;
+ }
+
+ /**
+ * Gets the max version that we support.
+ *
+ * @return string the value of $key.
+ */
+ public function getVersion(): string
+ {
+ return $this->min_smf_version;
+ }
+
+ /**
+ * Gets the class identifier of the current file system implementation.
+ *
+ * @return string the unique identifier for the current class implementation.
+ */
+ public function getImplementationClassKeyName(): string
+ {
+ $class_name = \get_class($this);
+
+ if ($position = strrpos($class_name, '\\')) {
+ return substr($class_name, $position + 1);
+ }
+
+ return $class_name;
+ }
+
+ final public function setForumRoot(string $root): void
+ {
+ $this->forum_root = $root;
+ var_dump(value: $this->forum_root);
+
+ die;
+ }
+
+ final public function normalizeFilename(string $filename): string
+ {
+ if (empty($this->forum_root)) {
+ return $filename;
+ }
+
+ $normalized = strtr($filename, [rtrim(Config::$boarddir, '/') => rtrim($this->forum_root, '/')]);
+
+ if ($normalized === '') {
+ return '.';
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Returns the last error message seen by the file system.
+ * This typically returns a string that is later prepended with "filesystem_error_" and looked up for a translation.
+ *
+ * @return string
+ */
+ public function getLastError(): ?string
+ {
+ return $this->error;
+ }
+
+ /**
+ * Returns the last message seen by the file system, may be informational.
+ *
+ * @return string
+ */
+ public function getLastMessage(): ?string
+ {
+ return $this->last_message;
+ }
+
+ /**
+ * Detects the current path
+ *
+ * @param string $directory The full path from the filesystem
+ * @param null|string $lookup_file The name of a file in the specified path
+ * @return array An array of detected info - username, path from FTP root and whether or not the current path was found
+ */
+ public function detectForumPath(string $directory, ?string $lookup_file = null): array
+ {
+ $username = '';
+
+ if (isset($_SERVER['DOCUMENT_ROOT'])) {
+ if (preg_match('~^/home[2]?/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match)) {
+ $username = $match[1];
+
+ $path = strtr($_SERVER['DOCUMENT_ROOT'], ['/home/' . $match[1] . '/' => '', '/home2/' . $match[1] . '/' => '']);
+
+ if (str_ends_with($path, '/')) {
+ $path = substr($path, 0, -1);
+ }
+
+ if (\strlen(\dirname($_SERVER['PHP_SELF'])) > 1) {
+ $path .= \dirname($_SERVER['PHP_SELF']);
+ }
+ } elseif (str_starts_with($directory, '/var/www/')) {
+ $path = substr($directory, 8);
+ } else {
+ $path = strtr(strtr($directory, ['\\' => '/']), [$_SERVER['DOCUMENT_ROOT'] => '']);
+ }
+ } else {
+ $path = '';
+ }
+
+ return [$username, $path, false];
+ }
+
+ /***********************
+ * Public static methods
+ ***********************/
+
+ /**
+ * Try to load up a supported file system method.
+ *
+ * @param string|FileSystemInterface|null $api Load the specified API, otherwise the assigned one or default.
+ * @return FileSystemInterface&FileSystem|false An instance of a child class of this class, or false on failure.
+ */
+ final public static function load(string|FileSystemInterface|null $api = null): bool|FileSystemInterface
+ {
+ // Already loaded the API from the default.
+ if ($api == null && self::$loaded_api !== null && self::$loaded_api instanceof FileSystemInterface) {
+ return self::$loaded_api;
+ }
+
+ // API to load.
+ $api ??= Config::$modSettings['filesystem_type'] ?? Ftp::class;
+
+ // What api we are going to try.
+ if ($api instanceof FileSystemInterface) {
+ $fully_qualified_class_name = \get_class($api);
+ } elseif (strpos($api, self::APIS_NAMESPACE) !== 0) {
+ $fully_qualified_class_name = self::APIS_NAMESPACE . $api;
+ } else {
+ $fully_qualified_class_name = $api;
+ }
+
+ // Do some basic tests.
+ $api = false;
+
+ if (class_exists($fully_qualified_class_name)) {
+ /* @var MailAgentInterface $agent_api */
+ $api = new $fully_qualified_class_name();
+
+ // There are rules you know...
+ if (!($api instanceof FileSystemInterface) || !($api instanceof FileSystem)) {
+ $api = false;
+ }
+
+ // No Support? NEXT!
+ if ($api && !$api->isSupported() && !$api->isConfigured()) {
+ // Can we save ourselves?
+ if ($fully_qualified_class_name !== self::APIS_DEFAULT) {
+ return self::load(null);
+ }
+
+ $api = false;
+ }
+ }
+
+ return $api;
+ }
+
+ final public static function getSelectOptions(): array
+ {
+ $detected_apis = self::detect();
+ $apis_names = [];
+
+ foreach ($detected_apis as $class_name => $agent) {
+ $class_name_txt_key = strtolower($agent->getImplementationClassKeyName());
+
+ $apis_names[$class_name] = Lang::txtExists($class_name_txt_key . '_filesystem', file: 'Packages') ? Lang::getTxt($class_name_txt_key . '_filesystem', file: 'PackageManager') : $class_name;
+ }
+
+ return $apis_names;
+ }
+
+ /**
+ * Get the installed File System implementations.
+ *
+ * @return FileSystemInterface[] An array of mail agents
+ */
+ final public static function detect(): array
+ {
+ $loaded_apis = [];
+
+ $api_classes = new \GlobIterator(self::APIS_FOLDER . '/*.php', \FilesystemIterator::NEW_CURRENT_AND_KEY);
+
+ foreach ($api_classes as $file_path => $file_info) {
+ $class_name = $file_info->getBasename('.php');
+ $fully_qualified_class_name = self::APIS_NAMESPACE . $class_name;
+
+ if (!class_exists($fully_qualified_class_name)) {
+ continue;
+ }
+
+ $agent_api = new $fully_qualified_class_name();
+
+ // Deal with it!
+ if (!($agent_api instanceof FileSystemInterface) || !($agent_api instanceof FileSystem)) {
+ continue;
+ }
+
+ // No Support? NEXT!
+ if (!$agent_api->isSupported()) {
+ continue;
+ }
+
+ $loaded_apis[$class_name] = $agent_api;
+ }
+
+ IntegrationHook::call('integrate_load_filesystems', [&$loaded_apis]);
+
+ return $loaded_apis;
+ }
+}
diff --git a/Sources/PackageManager/FileSystem/FileSystemInterface.php b/Sources/PackageManager/FileSystem/FileSystemInterface.php
new file mode 100644
index 0000000000..eb9c7f205f
--- /dev/null
+++ b/Sources/PackageManager/FileSystem/FileSystemInterface.php
@@ -0,0 +1,141 @@
+pasv = [];
if ($ftp_server !== null) {
+ $this->ftp = new Ftp();
$this->connect($ftp_server, $ftp_port, $ftp_user, $ftp_pass);
}
+
+ $this->error = &$this->ftp->legacy_error;
+ $this->last_message = &$this->ftp->legacy_last_message;
+ $this->pasv = &$this->ftp->legacy_pasv;
}
/**
@@ -91,57 +110,12 @@ public function connect(
#[\SensitiveParameter]
string $ftp_pass = 'ftpclient@simplemachines.org',
): void {
- if (str_starts_with($ftp_server, 'ftp://')) {
- $ftp_server = substr($ftp_server, 6);
- } elseif (str_starts_with($ftp_server, 'ftps://')) {
- $ftp_server = 'ssl://' . substr($ftp_server, 7);
- }
-
- if (str_starts_with($ftp_server, 'http://')) {
- $ftp_server = substr($ftp_server, 7);
- } elseif (str_starts_with($ftp_server, 'https://')) {
- $ftp_server = substr($ftp_server, 8);
- }
- $ftp_server = strtr($ftp_server, ['/' => '', ':' => '', '@' => '']);
-
- // Connect to the FTP server.
- $this->connection = @fsockopen($ftp_server, $ftp_port, $err, $err, 5);
-
- if (!$this->connection) {
- $this->error = 'bad_server';
- $this->last_message = 'Invalid Server';
-
- return;
- }
-
- // Get the welcome message...
- if (!$this->check_response(220)) {
- $this->error = 'bad_response';
- $this->last_message = 'Bad Response';
-
- return;
- }
-
- // Send the username, it should ask for a password.
- fwrite($this->connection, 'USER ' . $ftp_user . "\r\n");
-
- if (!$this->check_response(331)) {
- $this->error = 'bad_username';
- $this->last_message = 'Invalid Username';
-
- return;
+ if (str_starts_with($ftp_server, 'ftps://') || str_starts_with($ftp_server, 'ssl://')) {
+ $this->ftp = new FtpSSL();
}
- // Now send the password... and hope it goes okay.
-
- fwrite($this->connection, 'PASS ' . $ftp_pass . "\r\n");
-
- if (!$this->check_response(230)) {
- $this->error = 'bad_password';
- $this->last_message = 'Invalid Password';
-
- return;
- }
+ // Simply connect, ignore the response.
+ $this->ftp->connect($ftp_server, $ftp_user, $ftp_pass, $ftp_port);
}
/**
@@ -152,28 +126,16 @@ public function connect(
*/
public function chdir(string $ftp_path): bool
{
- if (!\is_resource($this->connection)) {
+ if (!\is_resource($this->ftp)) {
return false;
}
- // No slash on the end, please...
- if ($ftp_path !== '/' && str_ends_with($ftp_path, '/')) {
- $ftp_path = substr($ftp_path, 0, -1);
- }
-
- fwrite($this->connection, 'CWD ' . $ftp_path . "\r\n");
-
- if (!$this->check_response(250)) {
- $this->error = 'bad_path';
-
- return false;
- }
-
- return true;
+ return $this->ftp->changeDirectory($ftp_path);
}
/**
* Changes a files attributes (chmod)
+ * Do not attempt to check is_dir, is_writable, etc. $ftp_file contains the local FTP path.
*
* @param string $ftp_file The file to CHMOD
* @param int|string $chmod The value for the CHMOD operation
@@ -181,38 +143,11 @@ public function chdir(string $ftp_path): bool
*/
public function chmod(string $ftp_file, int|string $chmod): bool
{
- if (!\is_resource($this->connection)) {
+ if (!\is_resource($this->ftp)) {
return false;
}
- if ($ftp_file == '') {
- $ftp_file = '.';
- }
-
- // Do we have a file or a dir?
- $is_dir = is_dir($ftp_file);
- $is_writable = false;
-
- // Set different modes.
- $chmod_values = $is_dir ? [0750, 0755, 0775, 0777] : [0644, 0664, 0666];
-
- foreach ($chmod_values as $val) {
- // If it's writable, break out of the loop.
- if (is_writable($ftp_file)) {
- $is_writable = true;
- break;
- }
-
- // Convert the chmod value from octal (0777) to text ("777").
- fwrite($this->connection, 'SITE CHMOD ' . decoct($val) . ' ' . $ftp_file . "\r\n");
-
- if (!$this->check_response(200)) {
- $this->error = 'bad_file';
- break;
- }
- }
-
- return $is_writable;
+ return $this->ftp->changePermissions($ftp_file, $chmod);
}
/**
@@ -224,25 +159,12 @@ public function chmod(string $ftp_file, int|string $chmod): bool
public function unlink(string $ftp_file): bool
{
// We are actually connected, right?
- if (!\is_resource($this->connection)) {
+ if (!\is_resource($this->ftp)) {
return false;
}
- // Delete file X.
- fwrite($this->connection, 'DELE ' . $ftp_file . "\r\n");
-
- if (!$this->check_response(250)) {
- fwrite($this->connection, 'RMD ' . $ftp_file . "\r\n");
-
- // Still no love?
- if (!$this->check_response(250)) {
- $this->error = 'bad_file';
-
- return false;
- }
- }
-
- return true;
+ // The old way just deleted a file and when failed, deleted as a directory.
+ return $this->ftp->deleteFile($ftp_file);
}
/**
@@ -253,12 +175,9 @@ public function unlink(string $ftp_file): bool
*/
public function check_response(int|string|array $desired): bool
{
- // Wait for a response that isn't continued with -, but don't wait too long.
- $time = time();
-
- do {
- $this->last_message = fgets($this->connection, 1024);
- } while ((\strlen($this->last_message) < 4 || str_starts_with($this->last_message, ' ') || strpos($this->last_message, ' ', 3) !== 3) && time() - $time < 5);
+ if (!\is_resource($this->ftp)) {
+ return false;
+ }
// Was the desired response returned?
return \is_array($desired) ? \in_array(substr($this->last_message, 0, 3), $desired) : substr($this->last_message, 0, 3) == $desired;
@@ -271,39 +190,11 @@ public function check_response(int|string|array $desired): bool
*/
public function passive(): bool
{
- // We can't create a passive data connection without a primary one first being there.
- if (!\is_resource($this->connection)) {
- $this->error = 'no_connection';
-
- return false;
- }
-
- // Request a passive connection - this means, we'll talk to you, you don't talk to us.
- @fwrite($this->connection, 'PASV' . "\r\n");
- $time = time();
-
- do {
- $response = fgets($this->connection, 1024);
- } while (strpos($response, ' ', 3) !== 3 && time() - $time < 5);
-
- // If it's not 227, we weren't given an IP and port, which means it failed.
- if (!str_starts_with($response, '227 ')) {
- $this->error = 'bad_response';
-
+ if (!\is_resource($this->ftp)) {
return false;
}
- // Snatch the IP and port information, or die horribly trying...
- if (preg_match('~\((\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))\)~', $response, $match) == 0) {
- $this->error = 'bad_response';
-
- return false;
- }
-
- // This is pretty simple - store it for later use ;).
- $this->pasv = ['ip' => $match[1] . '.' . $match[2] . '.' . $match[3] . '.' . $match[4], 'port' => $match[5] * 256 + $match[6]];
-
- return true;
+ return $this->ftp->enterPassiveMode();
}
/**
@@ -315,38 +206,11 @@ public function passive(): bool
public function create_file(string $ftp_file): bool
{
// First, we have to be connected... very important.
- if (!\is_resource($this->connection)) {
- return false;
- }
-
- // I'd like one passive mode, please!
- if (!$this->passive()) {
+ if (!\is_resource($this->ftp)) {
return false;
}
- // Seems logical enough, so far...
- fwrite($this->connection, 'STOR ' . $ftp_file . "\r\n");
-
- // Okay, now we connect to the data port. If it doesn't work out, it's probably "file already exists", etc.
- $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err, 5);
-
- if (!$fp || !$this->check_response(150)) {
- $this->error = 'bad_file';
- @fclose($fp);
-
- return false;
- }
-
- // This may look strange, but we're just closing it to indicate a zero-byte upload.
- fclose($fp);
-
- if (!$this->check_response(226)) {
- $this->error = 'bad_response';
-
- return false;
- }
-
- return true;
+ return $this->ftp->createFile($ftp_file);
}
/**
@@ -359,44 +223,11 @@ public function create_file(string $ftp_file): bool
public function list_dir(string $ftp_path = '', bool $search = false): string|bool
{
// Are we even connected...?
- if (!\is_resource($this->connection)) {
- return false;
- }
-
- // Passive... non-aggressive...
- if (!$this->passive()) {
- return false;
- }
-
- // Get the listing!
- fwrite($this->connection, 'LIST -1' . ($search ? 'R' : '') . ($ftp_path == '' ? '' : ' ' . $ftp_path) . "\r\n");
-
- // Connect, assuming we've got a connection.
- $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err, 5);
-
- if (!$fp || !$this->check_response([150, 125])) {
- $this->error = 'bad_response';
- @fclose($fp);
-
+ if (!\is_resource($this->ftp)) {
return false;
}
- // Read in the file listing.
- $data = '';
-
- while (!feof($fp)) {
- $data .= fread($fp, 4096);
- }
- fclose($fp);
-
- // Everything go okay?
- if (!$this->check_response(226)) {
- $this->error = 'bad_response';
-
- return false;
- }
-
- return $data;
+ return $this->ftp->listDirectory($ftp_path, $search);
}
/**
@@ -408,48 +239,11 @@ public function list_dir(string $ftp_path = '', bool $search = false): string|bo
*/
public function locate(string $file, ?string $listing = null): string|bool
{
- if ($listing === null) {
- $listing = $this->list_dir('', true);
- }
- $listing = explode("\n", $listing);
-
- @fwrite($this->connection, 'PWD' . "\r\n");
- $time = time();
-
- do {
- $response = fgets($this->connection, 1024);
- } while ($response[3] != ' ' && time() - $time < 5);
-
- // Check for 257!
- if (preg_match('~^257 "(.+?)" ~', $response, $match) != 0) {
- $current_dir = strtr($match[1], ['""' => '"']);
- } else {
- $current_dir = '';
- }
-
- for ($i = 0, $n = \count($listing); $i < $n; $i++) {
- if (trim($listing[$i]) == '' && isset($listing[$i + 1])) {
- $current_dir = substr(trim($listing[++$i]), 0, -1);
- $i++;
- }
-
- // Okay, this file's name is:
- $listing[$i] = $current_dir . '/' . trim(\strlen($listing[$i]) > 30 ? strrchr($listing[$i], ' ') : $listing[$i]);
-
- if ($file[0] == '*' && substr($listing[$i], -(\strlen($file) - 1)) == substr($file, 1)) {
- return $listing[$i];
- }
-
- if (str_ends_with($file, '*') && substr($listing[$i], 0, \strlen($file) - 1) == substr($file, 0, -1)) {
- return $listing[$i];
- }
-
- if (basename($listing[$i]) == $file || $listing[$i] == $file) {
- return $listing[$i];
- }
+ if (!\is_resource($this->ftp)) {
+ return false;
}
- return false;
+ return $this->ftp->locate($file, $listing);
}
/**
@@ -461,76 +255,11 @@ public function locate(string $file, ?string $listing = null): string|bool
public function create_dir(string $ftp_dir): bool
{
// We must be connected to the server to do something.
- if (!\is_resource($this->connection)) {
+ if (!\is_resource($this->ftp)) {
return false;
}
- // Make this new beautiful directory!
- fwrite($this->connection, 'MKD ' . $ftp_dir . "\r\n");
-
- if (!$this->check_response(257)) {
- $this->error = 'bad_file';
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Detects the current path
- *
- * @param string $filesystem_path The full path from the filesystem
- * @param null|string $lookup_file The name of a file in the specified path
- * @return array An array of detected info - username, path from FTP root and whether or not the current path was found
- */
- public function detect_path(string $filesystem_path, ?string $lookup_file = null): array
- {
- $username = '';
-
- if (isset($_SERVER['DOCUMENT_ROOT'])) {
- if (preg_match('~^/home[2]?/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match)) {
- $username = $match[1];
-
- $path = strtr($_SERVER['DOCUMENT_ROOT'], ['/home/' . $match[1] . '/' => '', '/home2/' . $match[1] . '/' => '']);
-
- if (str_ends_with($path, '/')) {
- $path = substr($path, 0, -1);
- }
-
- if (\strlen(\dirname($_SERVER['PHP_SELF'])) > 1) {
- $path .= \dirname($_SERVER['PHP_SELF']);
- }
- } elseif (str_starts_with($filesystem_path, '/var/www/')) {
- $path = substr($filesystem_path, 8);
- } else {
- $path = strtr(strtr($filesystem_path, ['\\' => '/']), [$_SERVER['DOCUMENT_ROOT'] => '']);
- }
- } else {
- $path = '';
- }
-
- if (\is_resource($this->connection) && $this->list_dir($path) == '') {
- $data = $this->list_dir('', true);
-
- if ($lookup_file === null) {
- $lookup_file = $_SERVER['PHP_SELF'];
- }
-
- $found_path = \dirname($this->locate('*' . basename(\dirname($lookup_file)) . '/' . basename($lookup_file), $data));
-
- if ($found_path == false) {
- $found_path = \dirname($this->locate(basename($lookup_file)));
- }
-
- if ($found_path != false) {
- $path = $found_path;
- }
- } elseif (\is_resource($this->connection)) {
- $found_path = true;
- }
-
- return [$username, $path, isset($found_path)];
+ return $this->ftp->createDirectory($ftp_dir);
}
/**
@@ -540,10 +269,10 @@ public function detect_path(string $filesystem_path, ?string $lookup_file = null
*/
public function close(): bool
{
- // Goodbye!
- fwrite($this->connection, 'QUIT' . "\r\n");
- fclose($this->connection);
+ if (!\is_resource($this->ftp)) {
+ return false;
+ }
- return true;
+ return $this->ftp->disconnect();
}
}
diff --git a/Sources/PackageManager/PackageManager.php b/Sources/PackageManager/PackageManager.php
index 4e382f73cc..2c2ee3488f 100644
--- a/Sources/PackageManager/PackageManager.php
+++ b/Sources/PackageManager/PackageManager.php
@@ -13,6 +13,7 @@
namespace SMF\PackageManager;
+use SMF\Actions\Admin\ACP;
use SMF\Cache\CacheApi;
use SMF\Config;
use SMF\Db\DatabaseApi as Db;
@@ -23,6 +24,9 @@
use SMF\Logging;
use SMF\Menu;
use SMF\Msg;
+use SMF\OutputTypeInterface;
+use SMF\OutputTypes;
+use SMF\PackageManager\FileSystem\FileSystem;
use SMF\Parser;
use SMF\Sapi;
use SMF\Security;
@@ -51,7 +55,7 @@ class PackageManager
'browse' => 'browse',
'remove' => 'remove',
'list' => 'list',
- 'ftptest' => 'ftpTest',
+ 'fstest' => 'fsTest',
'install' => 'installTest',
'install2' => 'install',
'uninstall' => 'installTest',
@@ -174,7 +178,7 @@ public function installTest(): void
// Do we have an existing id, for uninstalls and the like.
Utils::$context['install_id'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 0;
- // Load up the package FTP information?
+ // Load up the package File System information?
PackageUtils::createChmodControl();
// Make sure temp directory exists and is empty.
@@ -366,7 +370,7 @@ public function installTest(): void
}
Utils::$context['actions'] = [];
- Utils::$context['ftp_needed'] = false;
+ Utils::$context['fs_needed'] = false;
Utils::$context['has_failure'] = false;
$chmod_files = [];
@@ -844,11 +848,15 @@ public function installTest(): void
}
if (!empty($chmod_files)) {
- $ftp_status = PackageUtils::createChmodControl($chmod_files);
- Utils::$context['ftp_needed'] = !empty($ftp_status['files']['notwritable']) && !empty(Utils::$context['package_ftp']);
+ $fs_status = PackageUtils::createChmodControl($chmod_files);
+ Utils::$context['fs_needed'] = !empty($fs_status['files']['notwritable']) && !empty(Utils::$context['package_fs']);
+
+ if (Utils::$context['fs_needed']) {
+ Utils::$context['filesystem_types'] = FileSystem::getSelectOptions();
+ }
}
- Utils::$context['post_url'] = Config::$scripturl . '?action=admin;area=packages;sa=' . (Utils::$context['uninstalling'] ? 'uninstall' : 'install') . (Utils::$context['ftp_needed'] ? '' : '2') . ';package=' . Utils::$context['filename'] . ';pid=' . Utils::$context['install_id'];
+ Utils::$context['post_url'] = Config::$scripturl . '?action=admin;area=packages;sa=' . (Utils::$context['uninstalling'] ? 'uninstall' : 'install') . (Utils::$context['fs_needed'] ? '' : '2') . ';package=' . Utils::$context['filename'] . ';pid=' . Utils::$context['install_id'];
Security::checkSubmitOnce('register');
}
@@ -887,7 +895,7 @@ public function install(): void
ErrorHandler::fatalLang('package_no_file', false);
}
- // Load up the package FTP information?
+ // Load up the package File System information?
PackageUtils::createChmodControl([], ['destination_url' => Config::$scripturl . '?action=admin;area=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package']]);
// Make sure temp directory exists and is empty!
@@ -1725,44 +1733,32 @@ public function browse(): void
}
/**
- * Used when a temp FTP access is needed to package functions
+ * Used when a temp File System access is needed to package functions
*/
public function options(): void
{
- if (isset($_POST['save'])) {
+ $config_vars = self::getConfigVars();
+
+ if (isset($_GET['save'])) {
User::$me->checkSession();
- Config::updateModSettings([
- 'package_server' => trim(Utils::htmlspecialchars($_POST['pack_server'])),
- 'package_port' => trim(Utils::htmlspecialchars($_POST['pack_port'])),
- 'package_username' => trim(Utils::htmlspecialchars($_POST['pack_user'])),
- 'package_make_backups' => !empty($_POST['package_make_backups']),
- 'package_make_full_backups' => !empty($_POST['package_make_full_backups']),
- ]);
- $_SESSION['adm-save'] = true;
+ IntegrationHook::call('integrate_save_package_settings');
- Utils::redirectexit('action=admin;area=packages;sa=options');
- }
+ $_POST['filesystem_path'] = rtrim($_POST['filesystem_path'], '/') . '/';
- if (preg_match('~^/home\d*/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match)) {
- $default_username = $match[1];
- } else {
- $default_username = '';
- }
-
- Utils::$context['page_title'] = Lang::getTxt('package_settings', file: 'Admin');
- Utils::$context['sub_template'] = 'install_options';
+ // If we changed settings, destory any session we have.
+ $_SESSION['pack_fs'] = null;
- Utils::$context['package_ftp_server'] = Config::$modSettings['package_server'] ?? 'localhost';
- Utils::$context['package_ftp_port'] = Config::$modSettings['package_port'] ?? '21';
- Utils::$context['package_ftp_username'] = Config::$modSettings['package_username'] ?? $default_username;
- Utils::$context['package_make_backups'] = !empty(Config::$modSettings['package_make_backups']);
- Utils::$context['package_make_full_backups'] = !empty(Config::$modSettings['package_make_full_backups']);
+ ACP::saveDBSettings($config_vars);
- if (!empty($_SESSION['adm-save'])) {
- Utils::$context['saved_successful'] = true;
- unset($_SESSION['adm-save']);
+ Utils::redirectexit('action=admin;area=packages;sa=options');
}
+
+ Utils::$context['settings_title'] = Lang::getTxt('package_settings', file: 'Admin');
+ Utils::$context['post_url'] = Config::$scripturl . '?action=admin;area=packages;sa=options;save';
+ Utils::$context['sub_template'] = 'show_settings';
+
+ ACP::prepareDBSettingContext($config_vars);
}
/**
@@ -1911,22 +1907,29 @@ public function permissions(): void
Sapi::setMemoryLimit('128M');
Sapi::setTimeLimit();
- // Load up some FTP stuff.
+ // Load up some File System stuff.
PackageUtils::createChmodControl();
- if (empty(PackageUtils::$package_ftp) && !isset($_POST['skip_ftp'])) {
- $ftp = new FtpConnection(null);
- list($username, $detect_path, $found_path) = $ftp->detect_path(Config::$boarddir);
-
- Utils::$context['package_ftp'] = [
- 'server' => Config::$modSettings['package_server'] ?? 'localhost',
- 'port' => Config::$modSettings['package_port'] ?? '21',
- 'username' => empty($username) ? (Config::$modSettings['package_username'] ?? '') : $username,
- 'path' => $detect_path,
+ if (!isset($_POST['skip_fs']) && (empty(PackageUtils::$package_fs) || !PackageUtils::$package_fs->isConnected())) {
+ PackageUtils::$package_fs ??= FileSystem::load();
+ list($username, $detect_path, $found_path) = PackageUtils::$package_fs->detectForumPath(Config::$boarddir);
+
+ Utils::$context['package_fs'] = [
+ 'type' => Config::$modSettings['filesystem_type'] ?? FileSystem::APIS_DEFAULT,
+ 'server' => Config::$modSettings['filesystem_server'] ?? 'localhost',
+ 'port' => Config::$modSettings['filesystem_port'] ?? 0,
+ 'username' => empty($username) ? (Config::$modSettings['filesystem_username'] ?? '') : $username,
+ 'path' => empty(Config::$modSettings['filesystem_path']) && $found_path ? $detect_path : Config::$modSettings['filesystem_path'] ?? '',
+ 'error' => Lang::txtExists('filesystem_error_' . PackageUtils::$package_fs->getLastError()) ? Lang::getTxt('filesystem_error_' . PackageUtils::$package_fs->getLastError()) : PackageUtils::$package_fs->getLastMessage(),
+ 'message' => PackageUtils::$package_fs->getLastMessage(),
'form_elements_only' => true,
];
+
+ Utils::$context['filesystem_types'] = FileSystem::getSelectOptions();
} else {
- Utils::$context['ftp_connected'] = true;
+ Utils::$context['package_fs']['error'] = Lang::txtExists('filesystem_error_' . PackageUtils::$package_fs->getLastError()) ? Lang::getTxt('filesystem_error_' . PackageUtils::$package_fs->getLastError()) : PackageUtils::$package_fs->getLastMessage();
+ Utils::$context['package_fs']['message'] = PackageUtils::$package_fs->getLastMessage();
+ Utils::$context['fs_connected'] = PackageUtils::$package_fs->isConnected();
}
// Define the template.
@@ -2123,7 +2126,7 @@ public function permissions(): void
Db::$db->free_result($request);
// If we're submitting then let's move on to another function to keep things cleaner..
- if (isset($_POST['action_changes'])) {
+ if (isset($_POST['action_changes']) && (isset($_POST['skip_fs']) || (!empty(PackageUtils::$package_fs) && PackageUtils::$package_fs->isConnected()))) {
$this->PackagePermissionsAction();
return;
@@ -2218,9 +2221,9 @@ public function PackagePermissionsAction(): ?bool
Utils::$context['page_title'] = Lang::getTxt('package_file_perms_applying', file: 'Packages');
Utils::$context['back_look_data'] = $_POST['back_look'] ?? [];
- // Skipping use of FTP?
- if (empty(PackageUtils::$package_ftp)) {
- Utils::$context['skip_ftp'] = true;
+ // Skipping use of FileSystem Handler?
+ if (empty(PackageUtils::$package_fs)) {
+ Utils::$context['skip_fs'] = true;
}
// We'll start off in a good place, security. Make sure that if we're dealing with individual files that they seem in the right place.
@@ -2294,10 +2297,9 @@ public function PackagePermissionsAction(): ?bool
if (\in_array($status, ['execute', 'writable', 'read'])) {
PackageUtils::chmod($path, $status);
} elseif ($status == 'custom' && !empty($custom_value)) {
- // Use FTP if we have it.
- if (!empty(PackageUtils::$package_ftp) && !empty($_SESSION['pack_ftp'])) {
- $ftp_file = strtr($path, [$_SESSION['pack_ftp']['root'] => '']);
- PackageUtils::$package_ftp->chmod($ftp_file, $custom_value);
+ // Use the File System Handler if we have it.
+ if (!empty(PackageUtils::$package_fs) && !empty($_SESSION['pack_fs'])) {
+ PackageUtils::$package_fs->changePermissions($path, $custom_value);
} else {
Utils::makeWritable($path, $custom_value);
}
@@ -2420,34 +2422,27 @@ public function PackagePermissionsAction(): ?bool
}
/**
- * Test an FTP connection.
+ * Test an File System connection.
*/
- public function ftpTest(): void
+ public function fsTest(): void
{
User::$me->checkSession('get');
- // Try to make the FTP connection.
- PackageUtils::createChmodControl([], ['force_find_error' => true]);
-
- // Deal with the template stuff.
- Theme::loadTemplate('Xml');
- Utils::$context['sub_template'] = 'generic_xml';
- Utils::$context['template_layers'] = [];
+ // Try to make the File System connection.
+ PackageUtils::$package_fs = FileSystem::load($_POST['filesystem']['type']);
+ PackageUtils::$package_fs->connect(
+ server: $_POST['filesystem']['server'],
+ username: $_POST['filesystem']['username'],
+ password: $_POST['filesystem']['password'],
+ port: $_POST['filesystem']['port'],
+ root: $_POST['filesystem']['path'],
+ );
- // Define the return data, this is simple.
- Utils::$context['xml_data'] = [
- 'results' => [
- 'identifier' => 'result',
- 'children' => [
- [
- 'attributes' => [
- 'success' => !empty(PackageUtils::$package_ftp) ? 1 : 0,
- ],
- 'value' => !empty(PackageUtils::$package_ftp) ? Lang::getTxt('package_ftp_test_success', file: 'Packages') : (isset(Utils::$context['package_ftp'], Utils::$context['package_ftp']['error']) ? Utils::$context['package_ftp']['error'] : Lang::getTxt('package_ftp_test_failed', file: 'Packages')),
- ],
- ],
- ],
- ];
+ Utils::serverResponse(Utils::jsonEncode([
+ 'success' => PackageUtils::$package_fs->isConnected() && empty(PackageUtils::$package_fs->getLastError()) ? 1 : 0,
+ 'error' => Lang::txtExists('filesystem_error_' . PackageUtils::$package_fs->getLastError()) ? Lang::getTxt('filesystem_error_' . PackageUtils::$package_fs->getLastError()) : PackageUtils::$package_fs->getLastError(),
+ 'message' => PackageUtils::$package_fs->getLastMessage(),
+ ]));
}
/**
@@ -2486,47 +2481,52 @@ public function servers(): void
Utils::$context['package_download_broken'] = !is_writable(Config::$packagesdir);
if (Utils::$context['package_download_broken']) {
- if (isset($_POST['ftp_username'])) {
- $ftp = new FtpConnection($_POST['ftp_server'], $_POST['ftp_port'], $_POST['ftp_username'], $_POST['ftp_password']);
+ if (isset($_POST['filesystem']['username'])) {
+ $fs = FileSystem::load($_POST['filesystem']['type']);
+ $fs->connect($_POST['filesystem']['server'], $_POST['filesystem']['username'], $_POST['filesystem']['password'], $_POST['filesystem']['port']);
- if ($ftp->error === false) {
+ if ($fs->getLastError() === false) {
// I know, I know... but a lot of people want to type /home/xyz/... which is wrong, but logical.
- if (!$ftp->chdir($_POST['ftp_path'])) {
- $ftp_error = $ftp->error;
- $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp_path']));
- }
+ if (!$fs->changeDirectory($_POST['filesystem']['path'])) {
+ $fs_error = $fs->getLastError();
+ $fs->changeDirectory(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['filesystem']['path']));
+ $fs->setForumRoot(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['filesystem']['path']));
+ } else {
+ $fs->setForumRoot($_POST['filesystem']['path']);
+ }
}
}
- if (!isset($ftp) || $ftp->error !== false) {
- if (!isset($ftp)) {
- $ftp = new FtpConnection(null);
- } elseif ($ftp->error !== false && !isset($ftp_error)) {
- $ftp_error = $ftp->last_message === null ? '' : $ftp->last_message;
+ if (!isset($fs) || $fs->getLastError() !== false) {
+ if (!isset($fs)) {
+ $fs = FileSystem::load();
+ } elseif ($fs->getLastMessage() !== false && !isset($fs_error)) {
+ $fs_error = $fs->getLastError() === null ? $fs->getLastMessage() : $fs->getLastError();
}
- list($username, $detect_path, $found_path) = $ftp->detect_path(Config::$packagesdir);
+ list($username, $detect_path, $found_path) = $fs->detectForumPath(Config::$packagesdir);
- if ($found_path || !isset($_POST['ftp_path'])) {
- $_POST['ftp_path'] = $detect_path;
+ if (!isset($_POST['filesystem']['path']) && empty(Config::$modSettings['filesystem_path']) && $found_path) {
+ $_POST['filesystem']['path'] = $detect_path;
}
- if (!isset($_POST['ftp_username'])) {
- $_POST['ftp_username'] = $username;
+ if (!isset($_POST['filesystem']['username'])) {
+ $_POST['filesystem']['username'] = $username;
}
- Utils::$context['package_ftp'] = [
- 'server' => $_POST['ftp_server'] ?? (Config::$modSettings['package_server'] ?? 'localhost'),
- 'port' => $_POST['ftp_port'] ?? (Config::$modSettings['package_port'] ?? '21'),
- 'username' => $_POST['ftp_username'] ?? (Config::$modSettings['package_username'] ?? ''),
- 'path' => $_POST['ftp_path'],
- 'error' => empty($ftp_error) ? null : $ftp_error,
+ Utils::$context['package_fs'] = [
+ 'type' => $_POST['filesystem']['type'] ?? Config::$modSettings['filesystem_type'] ?? FileSystem::APIS_DEFAULT,
+ 'server' => $_POST['filesystem']['server'] ?? Config::$modSettings['filesystem_server'] ?? 'localhost',
+ 'port' => $_POST['filesystem']['port'] ?? Config::$modSettings['filesystem_port'] ?? 0,
+ 'username' => $_POST['filesystem']['username'] ?? Config::$modSettings['filesystem_username'] ?? '',
+ 'path' => $_POST['filesystem']['path'] ?? Config::$modSettings['filesystem_path'] ?? '',
+ 'error' => empty($fs_error) ? null : $fs_error,
];
} else {
Utils::$context['package_download_broken'] = false;
- $ftp->chmod('.', 0777);
- $ftp->close();
+ $fs->changePermissions('.', 0777);
+ $fs->disconnect();
}
}
@@ -2986,7 +2986,7 @@ public function download(): void
$package_name = $package_name . $i . $ext;
}
- // Use FTP if necessary.
+ // Use File System if necessary.
PackageUtils::createChmodControl([Config::$packagesdir . '/' . $package_name], ['destination_url' => Config::$scripturl . '?action=admin;area=packages;get;sa=download' . (isset($_GET['server']) ? ';server=' . $_GET['server'] : '') . (isset($_REQUEST['auto']) ? ';auto' : '') . ';package=' . $_REQUEST['package'] . (isset($_REQUEST['conflict']) ? ';conflict' : '') . ';' . Utils::$context['session_var'] . '=' . Utils::$context['session_id'], 'crash_on_error' => true]);
PackageUtils::packagePutContents(Config::$packagesdir . '/' . $package_name, WebFetchApi::fetch($url . $_REQUEST['package']));
@@ -3030,7 +3030,7 @@ public function upload(): void
// Setup the correct template, even though I'll admit we ain't downloading ;)
Utils::$context['sub_template'] = 'downloaded';
- // @todo Use FTP if the Packages directory is not writable.
+ // @todo Use File System if the Packages directory is not writable.
// Check the file was even sent!
if (!isset($_FILES['package']['name']) || $_FILES['package']['name'] == '') {
@@ -3403,10 +3403,43 @@ public function list_getPackages(int $start, int $items_per_page, string $sort,
return $packages;
}
+ public function getOutputType(): OutputTypeInterface
+ {
+ return isset($_GET['sa']) && \in_array($_GET['sa'], ['fstest']) ? new OutputTypes\Json() : new OutputTypes\Html();
+ }
+
/***********************
* Public static methods
***********************/
+ /**
+ * Gets the configuration variables for this admin area.
+ *
+ * @return array $config_vars for the news area.
+ */
+ public static function getConfigVars(): array
+ {
+ if (preg_match('~^/home\d*/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match)) {
+ $default_username = $match[1];
+ } else {
+ $default_username = '';
+ }
+
+ $config_vars = [
+ ['select', 'filesystem_type', FileSystem::getSelectOptions()],
+ ['text', 'filesystem_server'],
+ ['int', 'filesystem_port', 'subtext' => Lang::getTxt('filesystem_port_desc', file: 'Packages')],
+ ['text', 'filesystem_username', 'default' => $default_username],
+ ['text', 'filesystem_path'],
+ ['check', 'package_make_backups'],
+ ['check', 'package_make_full_backups'],
+ ];
+
+ IntegrationHook::call('integrate_modify_package_settings', [&$config_vars]);
+
+ return $config_vars;
+ }
+
/**
* Instantiates this class, but never more than once.
*
diff --git a/Sources/PackageManager/PackageUtils.php b/Sources/PackageManager/PackageUtils.php
index d1c844f3ad..eda7799f9e 100644
--- a/Sources/PackageManager/PackageUtils.php
+++ b/Sources/PackageManager/PackageUtils.php
@@ -19,6 +19,8 @@
use SMF\ErrorHandler;
use SMF\ItemList;
use SMF\Lang;
+use SMF\PackageManager\FileSystem\FileSystem;
+use SMF\PackageManager\FileSystem\FileSystemInterface;
use SMF\Sapi;
use SMF\Theme;
use SMF\Time;
@@ -38,11 +40,10 @@ class PackageUtils
**************************/
/**
- * @var mixed
+ * @var null|FileSystemInterface&FileSystem
*
- * An instance of SMF\PackageManger\FtpConnection.
*/
- public static $package_ftp;
+ public static $package_fs;
/**
* @var mixed
@@ -605,7 +606,7 @@ public static function getPackageInfo(string $gzfilename): array|string
public static function createChmodControl(array $chmodFiles = [], array $chmodOptions = [], bool $restore_write_status = false): array
{
// If we're restoring the status of existing files prepare the data.
- if ($restore_write_status && isset($_SESSION['pack_ftp']) && !empty($_SESSION['pack_ftp']['original_perms'])) {
+ if ($restore_write_status && isset($_SESSION['pack_fs']) && !empty($_SESSION['pack_fs']['original_perms'])) {
$listOptions = [
'id' => 'restore_file_permissions',
'title' => Lang::getTxt('package_restore_permissions', file: 'Packages'),
@@ -642,7 +643,7 @@ public static function createChmodControl(array $chmodFiles = [], array $chmodOp
'function' => function ($rowData) {
$formatTxt = Lang::getTxt($rowData['result'] == '' || $rowData['result'] == 'skipped' ? 'package_restore_permissions_pre_change' : 'package_restore_permissions_post_change', file: 'Packages');
- return Lang::formatText($formatTxt, $rowData['cur_perms'], $rowData['new_perms'], $rowData['writable_message']);
+ return Lang::formatText($formatTxt, [$rowData['cur_perms'], $rowData['new_perms'], $rowData['writable_message']]);
},
'class' => 'smalltext',
],
@@ -724,50 +725,61 @@ public static function createChmodControl(array $chmodFiles = [], array $chmodOp
return $return_data;
}
- // If we have some FTP information already, then let's assume it was required and try to get ourselves connected.
- if (!empty($_SESSION['pack_ftp']['connected'])) {
- self::$package_ftp = new FtpConnection($_SESSION['pack_ftp']['server'], $_SESSION['pack_ftp']['port'], $_SESSION['pack_ftp']['username'], self::crypt($_SESSION['pack_ftp']['password']));
+ // If we have some File system information already, then let's assume it was required and try to get ourselves connected.
+ if (!empty($_SESSION['pack_fs']['connected']) && isset($_SESSION['pack_fs']['type'])) {
+ self::$package_fs = FileSystem::load($_SESSION['pack_fs']['type']);
+
+ if (self::$package_fs->connect($_SESSION['pack_fs']['server'], $_SESSION['pack_fs']['username'], $_SESSION['pack_fs']['password'], $_SESSION['pack_fs']['port'], $_SESSION['pack_fs']['path'])) {
+ self::$package_fs->changeDirectory($_SESSION['pack_fs']['path']);
+ } else {
+ // Login failed, ensure we can try to login again.
+ $_SESSION['pack_fs'] = null;
+ }
}
// Just got a submission did we?
- if (empty(self::$package_ftp) && isset($_POST['ftp_username'])) {
- $ftp = new FtpConnection($_POST['ftp_server'], $_POST['ftp_port'], $_POST['ftp_username'], $_POST['ftp_password']);
+ if ((empty(self::$package_fs) || !self::$package_fs->isConnected()) && isset($_POST['filesystem']['username'])) {
+ self::$package_fs = FileSystem::load($_POST['filesystem']['type']);
+ self::$package_fs->connect(
+ server: $_POST['filesystem']['server'],
+ username: $_POST['filesystem']['username'],
+ password: $_POST['filesystem']['password'],
+ port: $_POST['filesystem']['port'],
+ );
// We're connected, jolly good!
- if ($ftp->error === false) {
+ if (self::$package_fs->isConnected()) {
+ $_POST['filesystem']['path'] = rtrim($_POST['filesystem']['path'], '/') . '/';
+
// Common mistake, so let's try to remedy it...
- if (!$ftp->chdir($_POST['ftp_path'])) {
- $ftp_error = $ftp->last_message;
- $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp_path']));
- }
+ if (!self::$package_fs->changeDirectory($_POST['filesystem']['path'])) {
+ $fs_error = self::$package_fs->getLastError();
- if (!\in_array($_POST['ftp_path'], ['', '/'])) {
- $ftp_root = strtr(Config::$boarddir, [$_POST['ftp_path'] => '']);
+ $test_path = preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['filesystem']['path']);
- if (str_ends_with($ftp_root, '/') && ($_POST['ftp_path'] == '' || str_starts_with($_POST['ftp_path'], '/'))) {
- $ftp_root = substr($ftp_root, 0, -1);
+ if (self::$package_fs->changeDirectory($test_path)) {
+ $_POST['filesystem']['path'] = $test_path;
}
- } else {
- $ftp_root = Config::$boarddir;
}
- $_SESSION['pack_ftp'] = [
- 'server' => $_POST['ftp_server'],
- 'port' => $_POST['ftp_port'],
- 'username' => $_POST['ftp_username'],
- 'password' => self::crypt($_POST['ftp_password']),
- 'path' => $_POST['ftp_path'],
- 'root' => $ftp_root,
+ $_SESSION['pack_fs'] = [
+ 'type' => $_POST['filesystem']['type'],
+ 'server' => $_POST['filesystem']['server'],
+ 'port' => $_POST['filesystem']['port'],
+ 'username' => $_POST['filesystem']['username'],
+ 'password' => $_POST['filesystem']['password'],
+ 'path' => $_POST['filesystem']['path'],
'connected' => true,
];
- if (!isset(Config::$modSettings['package_path']) || Config::$modSettings['package_path'] != $_POST['ftp_path']) {
- Config::updateModSettings(['package_path' => $_POST['ftp_path']]);
+ if (!isset(Config::$modSettings['filesystem_path']) || Config::$modSettings['filesystem_path'] != $_POST['filesystem']['path']) {
+ Config::updateModSettings(['filesystem_path' => $_POST['filesystem']['path']]);
}
- // This is now the primary connection.
- self::$package_ftp = $ftp;
+ self::$package_fs->setForumRoot($_POST['filesystem']['path']);
}
+ } else {
+ Utils::$context['filesystem_types'] = FileSystem::getSelectOptions();
}
// Now try to simply make the files writable, with whatever we might have.
@@ -787,34 +799,33 @@ public static function createChmodControl(array $chmodFiles = [], array $chmodOp
}
}
- // Have we still got nasty files which ain't writable? Dear me we need more FTP good sir.
- if (empty(self::$package_ftp) && (!empty($return_data['files']['notwritable']) || !empty($chmodOptions['force_find_error']))) {
- if (!isset($ftp) || $ftp->error !== false) {
- if (!isset($ftp)) {
- $ftp = new FtpConnection(null);
- } elseif ($ftp->error !== false && !isset($ftp_error)) {
- $ftp_error = $ftp->last_message === null ? '' : $ftp->last_message;
+ // Have we still got nasty files which ain't writable? Dear me we need more File System good sir.
+ if (empty(self::$package_fs) && (!empty($return_data['files']['notwritable']) || !empty($chmodOptions['force_find_error']))) {
+ if (!isset(self::$package_fs) || !self::$package_fs instanceof FileSystemInterface || !self::$package_fs instanceof FileSystem || self::$package_fs->getLastError() !== false) {
+ if (!isset(self::$package_fs) || !self::$package_fs instanceof FileSystemInterface) {
+ self::$package_fs = FileSystem::load();
+ } elseif (self::$package_fs->getLastError() !== false && !isset($fs_error)) {
+ $fs_error = self::$package_fs->getLastMessage() === null ? '' : self::$package_fs->getLastMessage();
}
- list($username, $detect_path, $found_path) = $ftp->detect_path(Config::$boarddir);
+ list($username, $detect_path, $found_path) = self::$package_fs->detectForumPath(Config::$boarddir);
- if ($found_path) {
- $_POST['ftp_path'] = $detect_path;
- } elseif (!isset($_POST['ftp_path'])) {
- $_POST['ftp_path'] = Config::$modSettings['package_path'] ?? $detect_path;
+ if (empty(Config::$modSettings['filesystem_path']) && $found_path) {
+ $_POST['filesystem']['path'] = $detect_path;
+ } else {
+ $_POST['filesystem']['path'] ??= Config::$modSettings['filesystem_path'] ?? $detect_path;
}
- if (!isset($_POST['ftp_username'])) {
- $_POST['ftp_username'] = $username;
- }
+ $_POST['filesystem']['username'] ??= $username;
}
- Utils::$context['package_ftp'] = [
- 'server' => $_POST['ftp_server'] ?? (Config::$modSettings['package_server'] ?? 'localhost'),
- 'port' => $_POST['ftp_port'] ?? (Config::$modSettings['package_port'] ?? '21'),
- 'username' => $_POST['ftp_username'] ?? (Config::$modSettings['package_username'] ?? ''),
- 'path' => $_POST['ftp_path'],
- 'error' => empty($ftp_error) ? null : $ftp_error,
+ Utils::$context['package_fs'] = [
+ 'type' => $_POST['filesystem']['type'] ?? (Config::$modSettings['package_type'] ?? FileSystem::APIS_DEFAULT),
+ 'server' => $_POST['filesystem']['server'] ?? (Config::$modSettings['package_server'] ?? 'localhost'),
+ 'port' => $_POST['filesystem']['port'] ?? (Config::$modSettings['package_port'] ?? '21'),
+ 'username' => $_POST['filesystem']['username'] ?? (Config::$modSettings['package_username'] ?? ''),
+ 'path' => $_POST['filesystem']['path'] ?? Config::$modSettings['filesystem_path'] ?? '',
+ 'error' => empty($fs_error) ? null : $fs_error,
'destination' => !empty($chmodOptions['destination_url']) ? $chmodOptions['destination_url'] : '',
];
@@ -826,8 +837,8 @@ public static function createChmodControl(array $chmodFiles = [], array $chmodOp
// Sent here to die?
if (!empty($chmodOptions['crash_on_error'])) {
- Utils::$context['page_title'] = Lang::getTxt('package_ftp_necessary', file: 'Packages');
- Utils::$context['sub_template'] = 'ftp_required';
+ Utils::$context['page_title'] = Lang::getTxt('package_fs_necessary', file: 'Packages');
+ Utils::$context['sub_template'] = 'fs_required';
Utils::obExit();
}
}
@@ -848,33 +859,32 @@ public static function list_restoreFiles(mixed $dummy1, mixed $dummy2, mixed $du
{
$restore_files = [];
- foreach ($_SESSION['pack_ftp']['original_perms'] as $file => $perms) {
+ foreach ($_SESSION['pack_fs']['original_perms'] as $file => $perms) {
// Check the file still exists, and the permissions were indeed different than now.
$file_permissions = @fileperms($file);
if (!file_exists($file) || $file_permissions == $perms) {
- unset($_SESSION['pack_ftp']['original_perms'][$file]);
+ unset($_SESSION['pack_fs']['original_perms'][$file]);
continue;
}
// Are we wanting to change the permission?
if ($do_change && isset($_POST['restore_files']) && \in_array($file, $_POST['restore_files'])) {
- // Use FTP if we have it.
- if (!empty(self::$package_ftp)) {
- $ftp_file = strtr($file, [$_SESSION['pack_ftp']['root'] => '']);
- self::$package_ftp->chmod($ftp_file, $perms);
+ // Use File System if we have it.
+ if (self::$package_fs instanceof FileSystemInterface) {
+ self::$package_fs->changePermissions($file, $perms);
} else {
Utils::makeWritable($file, $perms);
}
$new_permissions = @fileperms($file);
$result = $new_permissions == $perms ? 'success' : 'failure';
- unset($_SESSION['pack_ftp']['original_perms'][$file]);
+ unset($_SESSION['pack_fs']['original_perms'][$file]);
} elseif ($do_change) {
$new_permissions = '';
$result = 'skipped';
- unset($_SESSION['pack_ftp']['original_perms'][$file]);
+ unset($_SESSION['pack_fs']['original_perms'][$file]);
}
// Record the results!
@@ -893,14 +903,14 @@ public static function list_restoreFiles(mixed $dummy1, mixed $dummy2, mixed $du
}
/**
- * Use FTP functions to work with a package download/install
+ * Use File System functions to work with a package download/install
*
* @param string $destination_url The destination URL
* @param null|array $files The files to CHMOD
* @param bool $return Whether to return an array of file info if there's an error
* @return array An array of file info
*/
- public static function packageRequireFTP(string $destination_url, ?array $files = null, bool $return = false): array
+ public static function packageRequireFileSystem(string $destination_url, ?array $files = null, bool $return = false): array
{
// Try to make them writable the manual way.
if ($files !== null) {
@@ -925,14 +935,14 @@ public static function packageRequireFTP(string $destination_url, ?array $files
}
}
- // No FTP required!
+ // No File System required!
if (empty($files)) {
return [];
}
}
- // They've opted to not use FTP, and try anyway.
- if (isset($_SESSION['pack_ftp']) && $_SESSION['pack_ftp'] == false) {
+ // They've opted to not use File System, and try anyway.
+ if (isset($_SESSION['pack_fs']) && $_SESSION['pack_fs'] == false) {
if ($files === null) {
return [];
}
@@ -952,29 +962,28 @@ public static function packageRequireFTP(string $destination_url, ?array $files
return $files;
}
- if (isset($_SESSION['pack_ftp'])) {
- self::$package_ftp = new FtpConnection($_SESSION['pack_ftp']['server'], $_SESSION['pack_ftp']['port'], $_SESSION['pack_ftp']['username'], self::crypt($_SESSION['pack_ftp']['password']));
+ if (isset($_SESSION['pack_fs'], $_SESSION['pack_fs']['type'])) {
+ self::$package_fs = FileSystem::load($_SESSION['pack_fs']['type']);
+ self::$package_fs->connect($_SESSION['pack_fs']['server'], $_SESSION['pack_fs']['username'], $_SESSION['pack_fs']['password'], $_SESSION['pack_ftp']['port']);
if ($files === null) {
return [];
}
foreach ($files as $k => $file) {
- $ftp_file = strtr($file, [$_SESSION['pack_ftp']['root'] => '']);
-
// This looks odd, but it's an attempt to work around PHP suExec.
if (!file_exists($file)) {
self::mktree(\dirname($file), 0755);
- self::$package_ftp->create_file($ftp_file);
- self::$package_ftp->chmod($ftp_file, 0755);
+ self::$package_fs->createFile($file);
+ self::$package_fs->changePermissions($file, 0755);
}
if (!@is_writable($file)) {
- self::$package_ftp->chmod($ftp_file, 0777);
+ self::$package_fs->changePermissions($file, 0777);
}
if (!@is_writable(\dirname($file))) {
- self::$package_ftp->chmod(\dirname($ftp_file), 0777);
+ self::$package_fs->changePermissions(\dirname($file), 0777);
}
if (@is_writable($file)) {
@@ -985,51 +994,53 @@ public static function packageRequireFTP(string $destination_url, ?array $files
return $files;
}
- if (isset($_POST['ftp_none'])) {
- $_SESSION['pack_ftp'] = false;
+ if (isset($_POST['filesystem_none'])) {
+ $_SESSION['pack_fs'] = [];
- $files = self::packageRequireFTP($destination_url, $files, $return);
+ $files = self::packageRequireFileSystem($destination_url, $files, $return);
return $files;
}
- if (isset($_POST['ftp_username'])) {
- $ftp = new FtpConnection($_POST['ftp_server'], $_POST['ftp_port'], $_POST['ftp_username'], $_POST['ftp_password']);
+ if (isset($_POST['filesystem']['username'])) {
+ $fs = FileSystem::load($_POST['filesystem']['type']);
+ $fs->connect($_POST['filesystem']['server'], $_POST['filesystem']['username'], $_POST['filesystem']['password'], $_POST['filesystem']['port']);
- if ($ftp->error === false) {
+ if ($fs->getLastError() === false) {
// Common mistake, so let's try to remedy it...
- if (!$ftp->chdir($_POST['ftp_path'])) {
- $ftp_error = $ftp->last_message;
- $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp_path']));
+ if (!$fs->changeDirectory($_POST['filesystem']['path'])) {
+ $fs_error = $fs->getLastMessage();
+ $fs->changeDirectory(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['filesystem']['path']));
+ $fs->setForumRoot(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['filesystem']['path']));
+ } else {
+ $fs->setForumRoot($_POST['filesystem']['path']);
}
}
}
- if (!isset($ftp) || $ftp->error !== false) {
- if (!isset($ftp)) {
- $ftp = new FtpConnection(null);
- } elseif ($ftp->error !== false && !isset($ftp_error)) {
- $ftp_error = $ftp->last_message === null ? '' : $ftp->last_message;
+ if (!isset($fs) || $fs->getLastError() !== false) {
+ if (!isset($fs)) {
+ $fs = FileSystem::load();
+ } elseif ($fs->getLastError() !== false && !isset($fs_error)) {
+ $fs_error = $fs->getLastMessage() === null ? '' : $fs->getLastMessage();
}
- list($username, $detect_path, $found_path) = $ftp->detect_path(Config::$boarddir);
+ list($username, $detect_path, $found_path) = $fs->detectForumPath(Config::$boarddir);
- if ($found_path) {
- $_POST['ftp_path'] = $detect_path;
- } elseif (!isset($_POST['ftp_path'])) {
- $_POST['ftp_path'] = Config::$modSettings['package_path'] ?? $detect_path;
+ if (empty(Config::$modSettings['filesystem_path']) && $found_path) {
+ $_POST['filesystem']['path'] = $detect_path;
+ } else {
+ $_POST['filesystem']['path'] ??= Config::$modSettings['filesystem_path'] ?? $detect_path;
}
- if (!isset($_POST['ftp_username'])) {
- $_POST['ftp_username'] = $username;
- }
+ $_POST['filesystem']['username'] ??= $username;
- Utils::$context['package_ftp'] = [
- 'server' => $_POST['ftp_server'] ?? (Config::$modSettings['package_server'] ?? 'localhost'),
- 'port' => $_POST['ftp_port'] ?? (Config::$modSettings['package_port'] ?? '21'),
- 'username' => $_POST['ftp_username'] ?? (Config::$modSettings['package_username'] ?? ''),
- 'path' => $_POST['ftp_path'],
- 'error' => empty($ftp_error) ? null : $ftp_error,
+ Utils::$context['package_fs'] = [
+ 'server' => $_POST['filesystem']['server'] ?? (Config::$modSettings['package_server'] ?? 'localhost'),
+ 'port' => $_POST['filesystem']['port'] ?? (Config::$modSettings['package_port'] ?? '21'),
+ 'username' => $_POST['filesystem']['username'] ?? (Config::$modSettings['package_username'] ?? ''),
+ 'path' => $_POST['filesystem']['path'] ?? Config::$modSettings['filesystem_path'] ?? '',
+ 'error' => empty($fs_error) ? null : $fs_error,
'destination' => $destination_url,
];
@@ -1038,34 +1049,24 @@ public static function packageRequireFTP(string $destination_url, ?array $files
return $files;
}
- Utils::$context['page_title'] = Lang::getTxt('package_ftp_necessary', file: 'Packages');
- Utils::$context['sub_template'] = 'ftp_required';
+ Utils::$context['page_title'] = Lang::getTxt('package_fs_necessary', file: 'Packages');
+ Utils::$context['sub_template'] = 'fs_required';
Utils::obExit();
} else {
- if (!\in_array($_POST['ftp_path'], ['', '/'])) {
- $ftp_root = strtr(Config::$boarddir, [$_POST['ftp_path'] => '']);
-
- if (str_ends_with($ftp_root, '/') && ($_POST['ftp_path'] == '' || $_POST['ftp_path'][0] == '/')) {
- $ftp_root = substr($ftp_root, 0, -1);
- }
- } else {
- $ftp_root = Config::$boarddir;
- }
-
- $_SESSION['pack_ftp'] = [
- 'server' => $_POST['ftp_server'],
- 'port' => $_POST['ftp_port'],
- 'username' => $_POST['ftp_username'],
- 'password' => self::crypt($_POST['ftp_password']),
- 'path' => $_POST['ftp_path'],
- 'root' => $ftp_root,
+ $_SESSION['pack_fs'] = [
+ 'type' => $_POST['filesystem']['type'],
+ 'server' => $_POST['filesystem']['server'],
+ 'port' => $_POST['filesystem']['port'],
+ 'username' => $_POST['filesystem']['username'],
+ 'password' => $_POST['filesystem']['password'],
+ 'path' => $_POST['filesystem']['path'],
];
- if (!isset(Config::$modSettings['package_path']) || Config::$modSettings['package_path'] != $_POST['ftp_path']) {
- Config::updateModSettings(['package_path' => $_POST['ftp_path']]);
+ if (!isset(Config::$modSettings['filesystem_path']) || Config::$modSettings['filesystem_path'] != $_POST['filesystem']['path']) {
+ Config::updateModSettings(['filesystem_path' => $_POST['filesystem']['path']]);
}
- $files = self::packageRequireFTP($destination_url, $files, $return);
+ $files = self::packageRequireFileSystem($destination_url, $files, $return);
}
return $files;
@@ -1733,13 +1734,11 @@ public static function deltree(string $dir, bool $delete_dir = true): void
$current_dir = @opendir($dir);
if ($current_dir == false) {
- if ($delete_dir && isset(self::$package_ftp)) {
- $ftp_file = strtr($dir, [$_SESSION['pack_ftp']['root'] => '']);
-
+ if ($delete_dir && isset(self::$package_fs)) {
if (!is_dir($dir)) {
- self::$package_ftp->chmod($ftp_file, 0777);
+ self::$package_fs->changePermissions($dir, 0777);
}
- self::$package_ftp->unlink($ftp_file);
+ self::$package_fs->deleteFile($dir);
}
return;
@@ -1754,13 +1753,11 @@ public static function deltree(string $dir, bool $delete_dir = true): void
self::deltree($dir . '/' . $entryname);
} else {
// Here, 755 doesn't really matter since we're deleting it anyway.
- if (isset(self::$package_ftp)) {
- $ftp_file = strtr($dir . '/' . $entryname, [$_SESSION['pack_ftp']['root'] => '']);
-
+ if (isset(self::$package_fs)) {
if (!is_writable($dir . '/' . $entryname)) {
- self::$package_ftp->chmod($ftp_file, 0777);
+ self::$package_fs->changePermissions($dir . '/' . $entryname, 0777);
}
- self::$package_ftp->unlink($ftp_file);
+ self::$package_fs->deleteFile($dir . '/' . $entryname);
} else {
Utils::makeWritable($dir . '/' . $entryname);
unlink($dir . '/' . $entryname);
@@ -1771,14 +1768,12 @@ public static function deltree(string $dir, bool $delete_dir = true): void
closedir($current_dir);
if ($delete_dir) {
- if (isset(self::$package_ftp)) {
- $ftp_file = strtr($dir, [$_SESSION['pack_ftp']['root'] => '']);
-
+ if (isset(self::$package_fs)) {
if (!is_writable($dir . '/' . $entryname)) {
- self::$package_ftp->chmod($ftp_file, 0777);
+ self::$package_fs->changePermissions($dir, 0777);
}
- self::$package_ftp->unlink($ftp_file);
+ self::$package_fs->deleteDirectory($dir . '/' . $entryname);
} else {
Utils::makeWritable($dir);
@rmdir($dir);
@@ -1798,8 +1793,8 @@ public static function mktree(string $strPath, int $mode): bool
{
if (is_dir($strPath)) {
if (!is_writable($strPath) && $mode !== false) {
- if (isset(self::$package_ftp)) {
- self::$package_ftp->chmod(strtr($strPath, [$_SESSION['pack_ftp']['root'] => '']), $mode);
+ if (isset(self::$package_fs)) {
+ self::$package_fs->changePermissions($strPath, $mode);
} else {
Utils::makeWritable($strPath, $mode);
}
@@ -1822,15 +1817,15 @@ public static function mktree(string $strPath, int $mode): bool
}
if (!is_writable(\dirname($strPath)) && $mode !== false) {
- if (isset(self::$package_ftp)) {
- self::$package_ftp->chmod(\dirname(strtr($strPath, [$_SESSION['pack_ftp']['root'] => ''])), $mode);
+ if (isset(self::$package_fs)) {
+ self::$package_fs->changePermissions(\dirname($strPath), $mode);
} else {
Utils::makeWritable(\dirname($strPath), $mode);
}
}
- if ($mode !== false && isset(self::$package_ftp)) {
- return self::$package_ftp->create_dir(strtr($strPath, [$_SESSION['pack_ftp']['root'] => '']));
+ if ($mode !== false && isset(self::$package_fs)) {
+ return self::$package_fs->createDirectory($strPath);
}
if ($mode === false) {
@@ -1885,13 +1880,9 @@ public static function copytree(string $source, string $destination): void
continue;
}
- if (isset(self::$package_ftp)) {
- $ftp_file = strtr($destination . '/' . $entryname, [$_SESSION['pack_ftp']['root'] => '']);
- }
-
if (is_file($source . '/' . $entryname)) {
- if (isset(self::$package_ftp) && !file_exists($destination . '/' . $entryname)) {
- self::$package_ftp->create_file($ftp_file);
+ if (isset(self::$package_fs) && !file_exists($destination . '/' . $entryname)) {
+ self::$package_fs->createFile($destination . '/' . $entryname);
} elseif (!file_exists($destination . '/' . $entryname)) {
@touch($destination . '/' . $entryname);
}
@@ -2258,7 +2249,7 @@ public static function parseDiff(string $raw_diff, bool $testing = true, bool $u
self::chmod($target_path);
// If the target still isn't writable, add it to the list so that we
- // can try again using FTP access.
+ // can try again using File System access.
if (
(
file_exists($target_path)
@@ -2614,7 +2605,7 @@ public static function parseModification(string $file, bool $testing = true, boo
$actions[] = [
'type' => 'failure',
'filename' => $working_file,
- 'search' => $search['search'],
+ 'search' => $actual_operation['searches'][0]['search'],
'is_custom' => $theme > 1 ? $theme : 0,
];
@@ -3146,7 +3137,7 @@ public static function packageGetContents(string $filename): string
/**
* Writes data to a file, almost exactly like the file_put_contents() function.
- * uses FTP to create/chmod the file when necessary and available.
+ * uses File System to create/chmod the file when necessary and available.
* uses text mode for text mode file extensions.
* returns the number of bytes written.
*
@@ -3170,12 +3161,8 @@ public static function packagePutContents(string $filename, string $data, bool $
}
}
- if (isset(self::$package_ftp)) {
- $ftp_file = strtr($filename, [$_SESSION['pack_ftp']['root'] => '']);
- }
-
- if (!file_exists($filename) && isset(self::$package_ftp)) {
- self::$package_ftp->create_file($ftp_file);
+ if (!file_exists($filename) && isset(self::$package_fs)) {
+ self::$package_fs->createFile($filename);
} elseif (!file_exists($filename)) {
@touch($filename);
}
@@ -3224,12 +3211,8 @@ public static function flushCache(bool $trash = false): void
// First, let's check permissions!
foreach (self::$package_cache as $filename => $data) {
- if (isset(self::$package_ftp)) {
- $ftp_file = strtr($filename, [$_SESSION['pack_ftp']['root'] => '']);
- }
-
- if (!file_exists($filename) && isset(self::$package_ftp)) {
- self::$package_ftp->create_file($ftp_file);
+ if (!file_exists($filename) && self::$package_fs instanceof FileSystemInterface) {
+ self::$package_fs->createFile($filename);
} elseif (!file_exists($filename)) {
@touch($filename);
}
@@ -3285,8 +3268,8 @@ public static function chmod(string $filename, string $perm_state = 'writable',
return true;
}
- // Start off checking without FTP.
- if (!isset(self::$package_ftp) || self::$package_ftp === false) {
+ // Start off checking without File System.
+ if (!isset(self::$package_fs) || self::$package_fs === false) {
for ($i = 0; $i < 2; $i++) {
$chmod_file = $filename;
@@ -3330,13 +3313,13 @@ public static function chmod(string $filename, string $perm_state = 'writable',
// It worked!
if ($track_change) {
- $_SESSION['pack_ftp']['original_perms'][$chmod_file] = $file_permissions;
+ $_SESSION['pack_fs']['original_perms'][$chmod_file] = $file_permissions;
}
return true;
}
- } elseif ($perm_state != 'writable' && isset($_SESSION['pack_ftp']['original_perms'][$chmod_file])) {
- unset($_SESSION['pack_ftp']['original_perms'][$chmod_file]);
+ } elseif ($perm_state != 'writable' && isset($_SESSION['pack_fs']['original_perms'][$chmod_file])) {
+ unset($_SESSION['pack_fs']['original_perms'][$chmod_file]);
}
}
@@ -3344,43 +3327,47 @@ public static function chmod(string $filename, string $perm_state = 'writable',
return false;
}
- // Otherwise we do have FTP?
- if (self::$package_ftp !== false && !empty($_SESSION['pack_ftp'])) {
- $ftp_file = strtr($filename, [$_SESSION['pack_ftp']['root'] => '']);
-
+ // Otherwise we do have File System handler?
+ if (self::$package_fs !== false && !empty($_SESSION['pack_fs'])) {
// This looks odd, but it's an attempt to work around PHP suExec.
if (!file_exists($filename) && $perm_state == 'writable') {
$file_permissions = @fileperms(\dirname($filename));
self::mktree(\dirname($filename), 0755);
- self::$package_ftp->create_file($ftp_file);
- self::$package_ftp->chmod($ftp_file, 0755);
- } else {
+ self::$package_fs->createFile($filename);
+
+ foreach (FileSystem::CHMOD_DIR as $chmod) {
+ self::$package_fs->changePermissions($filename, $chmod);
+
+ if (is_writable($filename)) {
+ break;
+ }
+ }
$file_permissions = @fileperms($filename);
}
if ($perm_state != 'writable') {
- self::$package_ftp->chmod($ftp_file, $perm_state == 'execute' ? 0755 : 0644);
+ self::$package_fs->changePermissions($filename, $perm_state == 'execute' ? 0755 : 0644);
} else {
- if (!@is_writable($filename)) {
- self::$package_ftp->chmod($ftp_file, 0777);
- }
+ foreach (FileSystem::CHMOD_FILE as $chmod) {
+ self::$package_fs->changePermissions($filename, $chmod);
- if (!@is_writable(\dirname($filename))) {
- self::$package_ftp->chmod(\dirname($ftp_file), 0777);
+ if (is_writable($filename)) {
+ break;
+ }
}
}
if (@is_writable($filename)) {
if ($track_change) {
- $_SESSION['pack_ftp']['original_perms'][$filename] = $file_permissions;
+ $_SESSION['pack_fs']['original_perms'][$filename] = $file_permissions;
}
return true;
}
- if ($perm_state != 'writable' && isset($_SESSION['pack_ftp']['original_perms'][$filename])) {
- unset($_SESSION['pack_ftp']['original_perms'][$filename]);
+ if ($perm_state != 'writable' && isset($_SESSION['pack_fs']['original_perms'][$filename])) {
+ unset($_SESSION['pack_fs']['original_perms'][$filename]);
}
}
@@ -3389,41 +3376,7 @@ public static function chmod(string $filename, string $perm_state = 'writable',
}
/**
- * Used to crypt the supplied ftp password in this session
- *
- * @param string $pass The password
- * @return string The encrypted password
- */
- public static function crypt(
- #[\SensitiveParameter]
- string $pass,
- ): string {
- $n = \strlen($pass);
-
- $salt = session_id();
-
- while (\strlen($salt) < $n) {
- $salt .= session_id();
- }
-
- for ($i = 0; $i < $n; $i++) {
- $pass[$i] = \chr(\ord($pass[$i]) ^ (\ord($salt[$i]) - 32));
- }
-
- return $pass;
- }
-
- /**
- * Generates a unique filename by appending a numeric suffix if a file already exists in the specified directory.
- *
- * This method checks for the existence of a file in the given directory and, if necessary, appends a number
- * to the base filename to ensure uniqueness.
- *
- * @param string $dir The directory where the file will be located. Must be a valid directory.
- * @param string $filename The base filename (without extension).
- * @param string $ext The file extension (without leading dot).
- *
- * @return string A unique filename **without** the extension. The caller should append it if needed.
+ * Generates a unique filename for the specified file in the specified directory
*
* @since 2.1
*/
diff --git a/Sources/Phpseclib/Common/Functions/Strings.php b/Sources/Phpseclib/Common/Functions/Strings.php
new file mode 100644
index 0000000000..ad8f63b65d
--- /dev/null
+++ b/Sources/Phpseclib/Common/Functions/Strings.php
@@ -0,0 +1,507 @@
+
+ * @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
+ */
+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
+ $unpacked = unpack('Nupper/Nlower', self::shift($data, 8));
+ $upper = $unpacked['upper'];
+ $lower = $unpacked['lower'];
+ $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_NO_PADDING) :
+ 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);
+ }
+}
diff --git a/Sources/Phpseclib/Common/Functions/index.php b/Sources/Phpseclib/Common/Functions/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Common/Functions/index.php
@@ -0,0 +1,8 @@
+
+ * setKey('abcdefghijklmnop');
+ *
+ * $size = 10 * 1024;
+ * $plaintext = '';
+ * for ($i = 0; $i < $size; $i++) {
+ * $plaintext.= 'a';
+ * }
+ *
+ * echo $aes->decrypt($aes->encrypt($plaintext));
+ * ?>
+ *
+ *
+ * @author Jim Wigginton
+ * @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
+ */
+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);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Blowfish.php b/Sources/Phpseclib/Crypt/Blowfish.php
new file mode 100644
index 0000000000..998cf8bb39
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Blowfish.php
@@ -0,0 +1,835 @@
+ 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:
+ *
+ * setKey('12345678901234567890123456789012');
+ *
+ * $plaintext = str_repeat('a', 1024);
+ *
+ * echo $blowfish->decrypt($blowfish->encrypt($plaintext));
+ * ?>
+ *
+ *
+ * @author Jim Wigginton
+ * @author Hans-Juergen Petrich
+ * @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
+ * @author Hans-Juergen Petrich
+ */
+class Blowfish extends BlockCipher
+{
+ /**
+ * Block Length of the cipher
+ *
+ * @see Common\SymmetricKey::block_size
+ * @var int
+ */
+ protected $block_size = 8;
+
+ /**
+ * The mcrypt specific name of the cipher
+ *
+ * @see Common\SymmetricKey::cipher_name_mcrypt
+ * @var string
+ */
+ protected $cipher_name_mcrypt = 'blowfish';
+
+ /**
+ * Optimizing value while CFB-encrypting
+ *
+ * @see Common\SymmetricKey::cfb_init_len
+ * @var int
+ */
+ protected $cfb_init_len = 500;
+
+ /**
+ * The fixed subkeys boxes
+ *
+ * S-Box
+ *
+ * @var array
+ */
+ private static $sbox = [
+ 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,
+
+ 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,
+
+ 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,
+
+ 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 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 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 (defined('OPENSSL_VERSION_TEXT') && 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 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::$sbox
+ ];
+
+ // 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 < 0x400; $i += 0x100) {
+ 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::$sbox[0x200])) {
+ self::$sbox = array_map('intval', self::$sbox);
+ 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;
+ $sbox = self::$sbox;
+
+ $cdata = array_values(unpack('N*', 'OxychromaticBlowfishSwatDynamite'));
+ $sha2pass = array_values(unpack('N*', $sha2pass));
+ $sha2salt = array_values(unpack('N*', $sha2salt));
+
+ self::expandstate($sha2salt, $sha2pass, $sbox, $p);
+ for ($i = 0; $i < 64; $i++) {
+ self::expand0state($sha2salt, $sbox, $p);
+ self::expand0state($sha2pass, $sbox, $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], $sbox, $p);
+ }
+ }
+
+ return pack('V*', ...$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[] $sbox
+ * @param int[] $p
+ * @see self::_bcrypt_hash()
+ */
+ private static function expand0state(array $key, array &$sbox, 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, $sbox, $p);
+ list( $p[2], $p[3]) = self::encryptBlockHelperFast($p[ 0], $p[ 1], $sbox, $p);
+ list( $p[4], $p[5]) = self::encryptBlockHelperFast($p[ 2], $p[ 3], $sbox, $p);
+ list( $p[6], $p[7]) = self::encryptBlockHelperFast($p[ 4], $p[ 5], $sbox, $p);
+ list( $p[8], $p[9]) = self::encryptBlockHelperFast($p[ 6], $p[ 7], $sbox, $p);
+ list($p[10], $p[11]) = self::encryptBlockHelperFast($p[ 8], $p[ 9], $sbox, $p);
+ list($p[12], $p[13]) = self::encryptBlockHelperFast($p[10], $p[11], $sbox, $p);
+ list($p[14], $p[15]) = self::encryptBlockHelperFast($p[12], $p[13], $sbox, $p);
+ list($p[16], $p[17]) = self::encryptBlockHelperFast($p[14], $p[15], $sbox, $p);
+ // @codingStandardsIgnoreEnd
+
+ list($sbox[0], $sbox[1]) = self::encryptBlockHelperFast($p[16], $p[17], $sbox, $p);
+ for ($i = 2; $i < 1024; $i += 2) {
+ list($sbox[$i], $sbox[$i + 1]) = self::encryptBlockHelperFast($sbox[$i - 2], $sbox[$i - 1], $sbox, $p);
+ }
+ }
+
+ /**
+ * Key expansion with salt
+ *
+ * @access private
+ * @param int[] $data
+ * @param int[] $key
+ * @param int[] $sbox
+ * @param int[] $p
+ * @see self::_bcrypt_hash()
+ */
+ private static function expandstate(array $data, array $key, array &$sbox, 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] , $sbox, $p);
+ list( $p[2], $p[3]) = self::encryptBlockHelperFast($data[ 2] ^ $p[ 0], $data[ 3] ^ $p[ 1], $sbox, $p);
+ list( $p[4], $p[5]) = self::encryptBlockHelperFast($data[ 4] ^ $p[ 2], $data[ 5] ^ $p[ 3], $sbox, $p);
+ list( $p[6], $p[7]) = self::encryptBlockHelperFast($data[ 6] ^ $p[ 4], $data[ 7] ^ $p[ 5], $sbox, $p);
+ list( $p[8], $p[9]) = self::encryptBlockHelperFast($data[ 8] ^ $p[ 6], $data[ 9] ^ $p[ 7], $sbox, $p);
+ list($p[10], $p[11]) = self::encryptBlockHelperFast($data[10] ^ $p[ 8], $data[11] ^ $p[ 9], $sbox, $p);
+ list($p[12], $p[13]) = self::encryptBlockHelperFast($data[12] ^ $p[10], $data[13] ^ $p[11], $sbox, $p);
+ list($p[14], $p[15]) = self::encryptBlockHelperFast($data[14] ^ $p[12], $data[15] ^ $p[13], $sbox, $p);
+ list($p[16], $p[17]) = self::encryptBlockHelperFast($data[ 0] ^ $p[14], $data[ 1] ^ $p[15], $sbox, $p);
+ // @codingStandardsIgnoreEnd
+
+ list($sbox[0], $sbox[1]) = self::encryptBlockHelperFast($data[2] ^ $p[16], $data[3] ^ $p[17], $sbox, $p);
+ for ($i = 2, $j = 4; $i < 1024; $i += 2, $j = ($j + 2) % 16) { // instead of 16 maybe count($data) would be better?
+ list($sbox[$i], $sbox[$i + 1]) = self::encryptBlockHelperFast($data[$j] ^ $sbox[$i - 2], $data[$j + 1] ^ $sbox[$i - 1], $sbox, $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 = $this->bctx['sb'];
+
+ $in = unpack('N*', $in);
+ $l = $in[1];
+ $r = $in[2];
+
+ list($r, $l) = PHP_INT_SIZE == 4 ?
+ self::encryptBlockHelperSlow($l, $r, $sb, $p) :
+ self::encryptBlockHelperFast($l, $r, $sb, $p);
+
+ return pack("N*", $r, $l);
+ }
+
+ /**
+ * Fast helper function for block encryption
+ *
+ * @access private
+ * @param int $x0
+ * @param int $x1
+ * @param int[] $sbox
+ * @param int[] $p
+ * @return int[]
+ */
+ private static function encryptBlockHelperFast($x0, $x1, array $sbox, array $p)
+ {
+ $x0 ^= $p[0];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[1];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[2];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[3];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[4];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[5];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[6];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[7];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[8];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[9];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[10];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[11];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[12];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[13];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[14];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[15];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($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[] $sbox
+ * @param int[] $p
+ * @return int[]
+ */
+ private static function encryptBlockHelperSlow($x0, $x1, array $sbox, array $p)
+ {
+ // -16777216 == intval(0xFF000000) on 32-bit PHP installs
+ $x0 ^= $p[0];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[1];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[2];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[3];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[4];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[5];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[6];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[7];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[8];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[9];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[10];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[11];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[12];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[13];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[14];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[15];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($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 = $this->bctx['sb'];
+
+ $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[$l >> 24 & 0xff] + $sb[0x100 + ($l >> 16 & 0xff)]) ^
+ $sb[0x200 + ($l >> 8 & 0xff)]) +
+ $sb[0x300 + ($l & 0xff)]);
+
+ $r ^= $p[$i - 1];
+ $l ^= self::safe_intval((self::safe_intval($sb[$r >> 24 & 0xff] + $sb[0x100 + ($r >> 16 & 0xff)]) ^
+ $sb[0x200 + ($r >> 8 & 0xff)]) +
+ $sb[0x300 + ($r & 0xff)]);
+ }
+ return pack('N*', $r ^ $p[0], $l ^ $p[1]);
+ }
+
+ /**
+ * Setup the performance-optimized function for de/encrypt()
+ *
+ * @see Common\SymmetricKey::_setupInlineCrypt()
+ */
+ protected function setupInlineCrypt()
+ {
+ $p = $this->bctx['p'];
+ $init_crypt = '
+ static $sb;
+ if (!$sb) {
+ $sb = $this->bctx["sb"];
+ }
+ ';
+
+ $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[$l >> 24 & 0xff] + $sb[0x100 + ($l >> 16 & 0xff)]') . ' ^
+ $sb[0x200 + ($l >> 8 & 0xff)]) +
+ $sb[0x300 + ($l & 0xff)]') . ';
+
+ $r^= ' . $p[$i + 1] . ';
+ $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb[$r >> 24 & 0xff] + $sb[0x100 + ($r >> 16 & 0xff)]') . ' ^
+ $sb[0x200 + ($r >> 8 & 0xff)]) +
+ $sb[0x300 + ($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[$l >> 24 & 0xff] + $sb[0x100 + ($l >> 16 & 0xff)]') . ' ^
+ $sb[0x200 + ($l >> 8 & 0xff)]) +
+ $sb[0x300 + ($l & 0xff)]') . ';
+
+ $r^= ' . $p[$i - 1] . ';
+ $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb[$r >> 24 & 0xff] + $sb[0x100 + ($r >> 16 & 0xff)]') . ' ^
+ $sb[0x200 + ($r >> 8 & 0xff)]) +
+ $sb[0x300 + ($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
+ ]
+ );
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/ChaCha20.php b/Sources/Phpseclib/Crypt/ChaCha20.php
new file mode 100644
index 0000000000..b2691b5ddc
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/ChaCha20.php
@@ -0,0 +1,799 @@
+
+ * @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
+ */
+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);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/AsymmetricKey.php b/Sources/Phpseclib/Crypt/Common/AsymmetricKey.php
new file mode 100644
index 0000000000..a380e43d78
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/AsymmetricKey.php
@@ -0,0 +1,581 @@
+
+ * @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
+ */
+abstract class AsymmetricKey
+{
+ /**
+ * Precomputed Zero
+ *
+ * @var BigInteger
+ */
+ protected static $zero;
+
+ /**
+ * Precomputed One
+ *
+ * @var BigInteger
+ */
+ protected static $one;
+
+ /**
+ * Format of the loaded key
+ *
+ * @var string
+ */
+ protected $format;
+
+ /**
+ * Hash function
+ *
+ * @var Hash
+ */
+ protected $hash;
+
+ /**
+ * HMAC function
+ *
+ * @var 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 array|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 PublicKey|PrivateKey
+ */
+ public static function load($key, $password = false)
+ {
+ self::initialize_static_variables();
+
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('load() should not be called from final classes (' . static::class . ')');
+ }
+
+ $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($shortname);
+ }
+ }
+ }
+
+ /**
+ * 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 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 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);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/BlockCipher.php b/Sources/Phpseclib/Crypt/Common/BlockCipher.php
new file mode 100644
index 0000000000..b2642be11f
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/BlockCipher.php
@@ -0,0 +1,24 @@
+
+ * @author Hans-Juergen Petrich
+ * @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
+ */
+abstract class BlockCipher extends SymmetricKey
+{
+}
diff --git a/Sources/Phpseclib/Crypt/Common/Formats/Keys/JWK.php b/Sources/Phpseclib/Crypt/Common/Formats/Keys/JWK.php
new file mode 100644
index 0000000000..98b8dacc96
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/Formats/Keys/JWK.php
@@ -0,0 +1,77 @@
+
+ * @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
+ */
+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 (!is_object($key)) {
+ throw new \RuntimeException('invalid JWK: not an object');
+ }
+
+ if (!isset($key->keys)) {
+ throw new \RuntimeException('invalid JWK: object has no property "keys"');
+ }
+
+ 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]]);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php b/Sources/Phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php
new file mode 100644
index 0000000000..ab7f050451
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php
@@ -0,0 +1,224 @@
+
+ * @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;
+use phpseclib3\Exception\BadDecryptionException;
+
+/**
+ * OpenSSH Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+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 ciphers 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) {
+ if (isset($crypto)) {
+ throw new BadDecryptionException('Unable to decrypt key - please verify the password you are using');
+ }
+ throw new \RuntimeException("The two checkints do not match ($checkint1 vs. $checkint2)");
+ }
+ 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";
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/Formats/Keys/PKCS.php b/Sources/Phpseclib/Crypt/Common/Formats/Keys/PKCS.php
new file mode 100644
index 0000000000..0219400bc3
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/Formats/Keys/PKCS.php
@@ -0,0 +1,72 @@
+
+ * @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
+ */
+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;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/Formats/Keys/PKCS1.php b/Sources/Phpseclib/Crypt/Common/Formats/Keys/PKCS1.php
new file mode 100644
index 0000000000..4c639c05e9
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/Formats/Keys/PKCS1.php
@@ -0,0 +1,209 @@
+
+ * @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
+ */
+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-----";
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/Formats/Keys/PKCS8.php b/Sources/Phpseclib/Crypt/Common/Formats/Keys/PKCS8.php
new file mode 100644
index 0000000000..2211a8747a
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/Formats/Keys/PKCS8.php
@@ -0,0 +1,766 @@
+
+ * @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
+ */
+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;
+
+ /**
+ * Binary key flag
+ *
+ * @var bool
+ */
+ private static $binary = 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');
+ $cipher->setKeyLength(64);
+ 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 DES('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 = '')
+ {
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ $isPublic = strpos($key, 'PUBLIC') !== false;
+ $isPrivate = strpos($key, 'PRIVATE') !== false;
+
+ $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');
+ }
+ $map = ASN1::asn1map($temp[0], Maps\PBEParameter::MAP);
+ $salt = $map['salt'];
+ $iterationCount = $map['iterationCount'];
+ $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);
+ $keyDerivationFunc = $temp['keyDerivationFunc'];
+ $encryptionScheme = $temp['encryptionScheme'];
+
+ $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);
+ $keyDerivationFunc = $temp['keyDerivationFunc'];
+ $encryptionScheme = $temp['encryptionScheme'];
+
+ if (!$cipher instanceof RC2) {
+ $cipher->setIV($encryptionScheme['parameters']['octetString']);
+ } else {
+ $temp = ASN1::decodeBER($encryptionScheme['parameters']);
+ if (!$temp) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $map = ASN1::asn1map($temp[0], Maps\RC2CBCParameter::MAP);
+ $rc2ParametersVersion = $map['rc2ParametersVersion'];
+ $iv = $map['iv'];
+ $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');
+ }
+ $params = ASN1::asn1map($temp[0], Maps\PBKDF2params::MAP);
+ if (empty($params['prf'])) {
+ $params['prf'] = ['algorithm' => 'id-hmacWithSHA1'];
+ }
+ $salt = $params['salt'];
+ $iterationCount = $params['iterationCount'];
+ $prf = $params['prf'];
+ $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 ($isPublic) {
+ throw new \UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key');
+ }
+
+ 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 ($isPrivate) {
+ throw new \UnexpectedValueException('Human readable string claims private key but DER encoded string claims public key');
+ }
+
+ 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');
+ }
+
+ /**
+ * Toggle between binary (DER) and printable (PEM) keys
+ *
+ * Printable keys are what are generated by default.
+ *
+ * @param bool $enabled
+ */
+ public static function setBinaryOutput($enabled)
+ {
+ self::$binary = $enabled;
+ }
+
+ /**
+ * 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);
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $key;
+ }
+
+ return "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" .
+ chunk_split(Strings::base64_encode($key), 64) .
+ "-----END ENCRYPTED PRIVATE KEY-----";
+ }
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $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, array $options = [])
+ {
+ 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);
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $key;
+ }
+
+ 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 (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)
+ {
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ $decoded = self::preParse($key);
+
+ $r = ASN1::asn1map($decoded[0], 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], 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'];
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/Formats/Keys/PuTTY.php b/Sources/Phpseclib/Crypt/Common/Formats/Keys/PuTTY.php
new file mode 100644
index 0000000000..ff4a95a82d
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/Formats/Keys/PuTTY.php
@@ -0,0 +1,380 @@
+
+ * @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
+ */
+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);
+
+ $length = unpack('Nlength', Strings::shift($public, 4))['length'];
+ $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++])));
+
+ $v3key = self::generateV3Key($password, $flavour, $memory, $passes, $salt);
+ $symkey = $v3key['symkey'];
+ $symiv = $v3key['symiv'];
+ $hashkey = $v3key['hashkey'];
+
+ 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";
+ $v3key = self::generateV3Key($password, 'Argon2id', 8192, 13, $salt);
+ $symkey = $v3key['symkey'];
+ $symiv = $v3key['symiv'];
+ $hashkey = $v3key['hashkey'];
+
+ $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;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/Formats/Keys/index.php b/Sources/Phpseclib/Crypt/Common/Formats/Keys/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/Formats/Keys/index.php
@@ -0,0 +1,8 @@
+
+ * @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
+ */
+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 BigInteger $r
+ * @param BigInteger $s
+ * @return string
+ */
+ public static function save(BigInteger $r, BigInteger $s)
+ {
+ return compact('r', 's');
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/Formats/Signature/index.php b/Sources/Phpseclib/Crypt/Common/Formats/Signature/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/Formats/Signature/index.php
@@ -0,0 +1,8 @@
+
+ * @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
+ */
+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);
+}
diff --git a/Sources/Phpseclib/Crypt/Common/PublicKey.php b/Sources/Phpseclib/Crypt/Common/PublicKey.php
new file mode 100644
index 0000000000..48a5875b1d
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/PublicKey.php
@@ -0,0 +1,25 @@
+
+ * @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
+ */
+interface PublicKey
+{
+ public function verify($message, $signature);
+ //public function encrypt($plaintext);
+ public function toString($type, array $options = []);
+ public function getFingerprint($algorithm);
+}
diff --git a/Sources/Phpseclib/Crypt/Common/StreamCipher.php b/Sources/Phpseclib/Crypt/Common/StreamCipher.php
new file mode 100644
index 0000000000..c7c080f4e2
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/StreamCipher.php
@@ -0,0 +1,54 @@
+
+ * @author Hans-Juergen Petrich
+ * @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
+ */
+abstract class StreamCipher extends SymmetricKey
+{
+ /**
+ * Block Length of the cipher
+ *
+ * Stream ciphers do not have a block size
+ *
+ * @see SymmetricKey::block_size
+ * @var int
+ */
+ protected $block_size = 0;
+
+ /**
+ * Default Constructor.
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
+ * @return StreamCipher
+ */
+ public function __construct()
+ {
+ parent::__construct('stream');
+ }
+
+ /**
+ * Stream ciphers not use an IV
+ *
+ * @return bool
+ */
+ public function usesIV()
+ {
+ return false;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/SymmetricKey.php b/Sources/Phpseclib/Crypt/Common/SymmetricKey.php
new file mode 100644
index 0000000000..35d7a7d7a0
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/SymmetricKey.php
@@ -0,0 +1,3398 @@
+
+ * @author Hans-Juergen Petrich
+ * @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
+ * @author Hans-Juergen Petrich
+ */
+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 !function_exists('php_uname'):
+ case !is_string(php_uname('m')):
+ 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 int|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 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:
+ *
+ *
+ * echo $rijndael->encrypt(substr($plaintext, 0, 16));
+ * echo $rijndael->encrypt(substr($plaintext, 16, 16));
+ *
+ *
+ * echo $rijndael->encrypt($plaintext);
+ *
+ *
+ * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates
+ * another, as demonstrated with the following:
+ *
+ *
+ * $rijndael->encrypt(substr($plaintext, 0, 16));
+ * echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16)));
+ *
+ *
+ * echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16)));
+ *
+ *
+ * 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:
+ *
+ *
+ * +----------------------------------------------------------------------------------------------+
+ * | 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; |
+ * | } |
+ * | } |
+ * +----------------------------------------------------------------------------------------------+
+ *
+ *
+ * See also the \phpseclib3\Crypt\*::_setupInlineCrypt()'s for
+ * productive inline $cipher_code's how they works.
+ *
+ * Structure of:
+ *
+ * $cipher_code = [
+ * 'init_crypt' => (string) '', // optional
+ * 'init_encrypt' => (string) '', // optional
+ * 'init_decrypt' => (string) '', // optional
+ * 'encrypt_block' => (string) '', // required
+ * 'decrypt_block' => (string) '' // required
+ * ];
+ *
+ *
+ * @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;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/Traits/Fingerprint.php b/Sources/Phpseclib/Crypt/Common/Traits/Fingerprint.php
new file mode 100644
index 0000000000..9ca8926d3a
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/Traits/Fingerprint.php
@@ -0,0 +1,57 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common\Traits;
+
+use phpseclib3\Crypt\Hash;
+
+/**
+ * Fingerprint Trait for Private Keys
+ *
+ * @author Jim Wigginton
+ */
+trait Fingerprint
+{
+ /**
+ * Returns the public key's fingerprint
+ *
+ * The public key's fingerprint is returned, which is equivalent to running `ssh-keygen -lf rsa.pub`. If there is
+ * no public key currently loaded, false is returned.
+ * Example output (md5): "c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87" (as specified by RFC 4716)
+ *
+ * @param string $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned
+ * for invalid values.
+ * @return mixed
+ */
+ public function getFingerprint($algorithm = 'md5')
+ {
+ $type = self::validatePlugin('Keys', 'OpenSSH', 'savePublicKey');
+ if ($type === false) {
+ return false;
+ }
+ $key = $this->toString('OpenSSH', ['binary' => true]);
+ if ($key === false) {
+ return false;
+ }
+ switch ($algorithm) {
+ case 'sha256':
+ $hash = new Hash('sha256');
+ $base = base64_encode($hash->hash($key));
+ return substr($base, 0, strlen($base) - 1);
+ case 'md5':
+ return substr(chunk_split(md5($key), 2, ':'), 0, -1);
+ default:
+ return false;
+ }
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/Traits/PasswordProtected.php b/Sources/Phpseclib/Crypt/Common/Traits/PasswordProtected.php
new file mode 100644
index 0000000000..0ac274e8d0
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/Traits/PasswordProtected.php
@@ -0,0 +1,46 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common\Traits;
+
+/**
+ * Password Protected Trait for Private Keys
+ *
+ * @author Jim Wigginton
+ */
+trait PasswordProtected
+{
+ /**
+ * Password
+ *
+ * @var string|bool
+ */
+ private $password = false;
+
+ /**
+ * Sets the password
+ *
+ * Private keys can be encrypted with a password. To unset the password, pass in the empty string or false.
+ * Or rather, pass in $password such that empty($password) && !is_string($password) is true.
+ *
+ * @see self::createKey()
+ * @see self::load()
+ * @param string|bool $password
+ */
+ public function withPassword($password = false)
+ {
+ $new = clone $this;
+ $new->password = $password;
+ return $new;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Common/Traits/index.php b/Sources/Phpseclib/Crypt/Common/Traits/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Common/Traits/index.php
@@ -0,0 +1,8 @@
+
+ * setKey('abcdefgh');
+ *
+ * $size = 10 * 1024;
+ * $plaintext = '';
+ * for ($i = 0; $i < $size; $i++) {
+ * $plaintext.= 'a';
+ * }
+ *
+ * echo $des->decrypt($des->encrypt($plaintext));
+ * ?>
+ *
+ *
+ * @author Jim Wigginton
+ * @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;
+use phpseclib3\Exception\BadModeException;
+
+/**
+ * Pure-PHP implementation of DES.
+ *
+ * @author Jim Wigginton
+ */
+class DES extends BlockCipher
+{
+ /**
+ * Contains $keys[self::ENCRYPT]
+ *
+ * @see \phpseclib3\Crypt\DES::setupKey()
+ * @see \phpseclib3\Crypt\DES::processBlock()
+ */
+ const ENCRYPT = 0;
+ /**
+ * Contains $keys[self::DECRYPT]
+ *
+ * @see \phpseclib3\Crypt\DES::setupKey()
+ * @see \phpseclib3\Crypt\DES::processBlock()
+ */
+ const DECRYPT = 1;
+
+ /**
+ * Block Length of the cipher
+ *
+ * @see Common\SymmetricKey::block_size
+ * @var int
+ */
+ protected $block_size = 8;
+
+ /**
+ * Key Length (in bytes)
+ *
+ * @see Common\SymmetricKey::setKeyLength()
+ * @var int
+ */
+ protected $key_length = 8;
+
+ /**
+ * The mcrypt specific name of the cipher
+ *
+ * @see Common\SymmetricKey::cipher_name_mcrypt
+ * @var string
+ */
+ protected $cipher_name_mcrypt = 'des';
+
+ /**
+ * The OpenSSL names of the cipher / modes
+ *
+ * @see Common\SymmetricKey::openssl_mode_names
+ * @var array
+ */
+ protected $openssl_mode_names = [
+ self::MODE_ECB => 'des-ecb',
+ self::MODE_CBC => 'des-cbc',
+ self::MODE_CFB => 'des-cfb',
+ self::MODE_OFB => 'des-ofb'
+ // self::MODE_CTR is undefined for DES
+ ];
+
+ /**
+ * Optimizing value while CFB-encrypting
+ *
+ * @see Common\SymmetricKey::cfb_init_len
+ * @var int
+ */
+ protected $cfb_init_len = 500;
+
+ /**
+ * Switch for DES/3DES encryption
+ *
+ * Used only if $engine == self::ENGINE_INTERNAL
+ *
+ * @see self::setupKey()
+ * @see self::processBlock()
+ * @var int
+ */
+ protected $des_rounds = 1;
+
+ /**
+ * max possible size of $key
+ *
+ * @see self::setKey()
+ * @var string
+ */
+ protected $key_length_max = 8;
+
+ /**
+ * The Key Schedule
+ *
+ * @see self::setupKey()
+ * @var array
+ */
+ private $keys;
+
+ /**
+ * Key Cache "key"
+ *
+ * @see self::setupKey()
+ * @var array
+ */
+ private $kl;
+
+ /**
+ * Shuffle table.
+ *
+ * For each byte value index, the entry holds an 8-byte string
+ * with each byte containing all bits in the same state as the
+ * corresponding bit in the index value.
+ *
+ * @see self::processBlock()
+ * @see self::setupKey()
+ * @var array
+ */
+ protected static $shuffle = [
+ "\x00\x00\x00\x00\x00\x00\x00\x00", "\x00\x00\x00\x00\x00\x00\x00\xFF",
+ "\x00\x00\x00\x00\x00\x00\xFF\x00", "\x00\x00\x00\x00\x00\x00\xFF\xFF",
+ "\x00\x00\x00\x00\x00\xFF\x00\x00", "\x00\x00\x00\x00\x00\xFF\x00\xFF",
+ "\x00\x00\x00\x00\x00\xFF\xFF\x00", "\x00\x00\x00\x00\x00\xFF\xFF\xFF",
+ "\x00\x00\x00\x00\xFF\x00\x00\x00", "\x00\x00\x00\x00\xFF\x00\x00\xFF",
+ "\x00\x00\x00\x00\xFF\x00\xFF\x00", "\x00\x00\x00\x00\xFF\x00\xFF\xFF",
+ "\x00\x00\x00\x00\xFF\xFF\x00\x00", "\x00\x00\x00\x00\xFF\xFF\x00\xFF",
+ "\x00\x00\x00\x00\xFF\xFF\xFF\x00", "\x00\x00\x00\x00\xFF\xFF\xFF\xFF",
+ "\x00\x00\x00\xFF\x00\x00\x00\x00", "\x00\x00\x00\xFF\x00\x00\x00\xFF",
+ "\x00\x00\x00\xFF\x00\x00\xFF\x00", "\x00\x00\x00\xFF\x00\x00\xFF\xFF",
+ "\x00\x00\x00\xFF\x00\xFF\x00\x00", "\x00\x00\x00\xFF\x00\xFF\x00\xFF",
+ "\x00\x00\x00\xFF\x00\xFF\xFF\x00", "\x00\x00\x00\xFF\x00\xFF\xFF\xFF",
+ "\x00\x00\x00\xFF\xFF\x00\x00\x00", "\x00\x00\x00\xFF\xFF\x00\x00\xFF",
+ "\x00\x00\x00\xFF\xFF\x00\xFF\x00", "\x00\x00\x00\xFF\xFF\x00\xFF\xFF",
+ "\x00\x00\x00\xFF\xFF\xFF\x00\x00", "\x00\x00\x00\xFF\xFF\xFF\x00\xFF",
+ "\x00\x00\x00\xFF\xFF\xFF\xFF\x00", "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF",
+ "\x00\x00\xFF\x00\x00\x00\x00\x00", "\x00\x00\xFF\x00\x00\x00\x00\xFF",
+ "\x00\x00\xFF\x00\x00\x00\xFF\x00", "\x00\x00\xFF\x00\x00\x00\xFF\xFF",
+ "\x00\x00\xFF\x00\x00\xFF\x00\x00", "\x00\x00\xFF\x00\x00\xFF\x00\xFF",
+ "\x00\x00\xFF\x00\x00\xFF\xFF\x00", "\x00\x00\xFF\x00\x00\xFF\xFF\xFF",
+ "\x00\x00\xFF\x00\xFF\x00\x00\x00", "\x00\x00\xFF\x00\xFF\x00\x00\xFF",
+ "\x00\x00\xFF\x00\xFF\x00\xFF\x00", "\x00\x00\xFF\x00\xFF\x00\xFF\xFF",
+ "\x00\x00\xFF\x00\xFF\xFF\x00\x00", "\x00\x00\xFF\x00\xFF\xFF\x00\xFF",
+ "\x00\x00\xFF\x00\xFF\xFF\xFF\x00", "\x00\x00\xFF\x00\xFF\xFF\xFF\xFF",
+ "\x00\x00\xFF\xFF\x00\x00\x00\x00", "\x00\x00\xFF\xFF\x00\x00\x00\xFF",
+ "\x00\x00\xFF\xFF\x00\x00\xFF\x00", "\x00\x00\xFF\xFF\x00\x00\xFF\xFF",
+ "\x00\x00\xFF\xFF\x00\xFF\x00\x00", "\x00\x00\xFF\xFF\x00\xFF\x00\xFF",
+ "\x00\x00\xFF\xFF\x00\xFF\xFF\x00", "\x00\x00\xFF\xFF\x00\xFF\xFF\xFF",
+ "\x00\x00\xFF\xFF\xFF\x00\x00\x00", "\x00\x00\xFF\xFF\xFF\x00\x00\xFF",
+ "\x00\x00\xFF\xFF\xFF\x00\xFF\x00", "\x00\x00\xFF\xFF\xFF\x00\xFF\xFF",
+ "\x00\x00\xFF\xFF\xFF\xFF\x00\x00", "\x00\x00\xFF\xFF\xFF\xFF\x00\xFF",
+ "\x00\x00\xFF\xFF\xFF\xFF\xFF\x00", "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF",
+ "\x00\xFF\x00\x00\x00\x00\x00\x00", "\x00\xFF\x00\x00\x00\x00\x00\xFF",
+ "\x00\xFF\x00\x00\x00\x00\xFF\x00", "\x00\xFF\x00\x00\x00\x00\xFF\xFF",
+ "\x00\xFF\x00\x00\x00\xFF\x00\x00", "\x00\xFF\x00\x00\x00\xFF\x00\xFF",
+ "\x00\xFF\x00\x00\x00\xFF\xFF\x00", "\x00\xFF\x00\x00\x00\xFF\xFF\xFF",
+ "\x00\xFF\x00\x00\xFF\x00\x00\x00", "\x00\xFF\x00\x00\xFF\x00\x00\xFF",
+ "\x00\xFF\x00\x00\xFF\x00\xFF\x00", "\x00\xFF\x00\x00\xFF\x00\xFF\xFF",
+ "\x00\xFF\x00\x00\xFF\xFF\x00\x00", "\x00\xFF\x00\x00\xFF\xFF\x00\xFF",
+ "\x00\xFF\x00\x00\xFF\xFF\xFF\x00", "\x00\xFF\x00\x00\xFF\xFF\xFF\xFF",
+ "\x00\xFF\x00\xFF\x00\x00\x00\x00", "\x00\xFF\x00\xFF\x00\x00\x00\xFF",
+ "\x00\xFF\x00\xFF\x00\x00\xFF\x00", "\x00\xFF\x00\xFF\x00\x00\xFF\xFF",
+ "\x00\xFF\x00\xFF\x00\xFF\x00\x00", "\x00\xFF\x00\xFF\x00\xFF\x00\xFF",
+ "\x00\xFF\x00\xFF\x00\xFF\xFF\x00", "\x00\xFF\x00\xFF\x00\xFF\xFF\xFF",
+ "\x00\xFF\x00\xFF\xFF\x00\x00\x00", "\x00\xFF\x00\xFF\xFF\x00\x00\xFF",
+ "\x00\xFF\x00\xFF\xFF\x00\xFF\x00", "\x00\xFF\x00\xFF\xFF\x00\xFF\xFF",
+ "\x00\xFF\x00\xFF\xFF\xFF\x00\x00", "\x00\xFF\x00\xFF\xFF\xFF\x00\xFF",
+ "\x00\xFF\x00\xFF\xFF\xFF\xFF\x00", "\x00\xFF\x00\xFF\xFF\xFF\xFF\xFF",
+ "\x00\xFF\xFF\x00\x00\x00\x00\x00", "\x00\xFF\xFF\x00\x00\x00\x00\xFF",
+ "\x00\xFF\xFF\x00\x00\x00\xFF\x00", "\x00\xFF\xFF\x00\x00\x00\xFF\xFF",
+ "\x00\xFF\xFF\x00\x00\xFF\x00\x00", "\x00\xFF\xFF\x00\x00\xFF\x00\xFF",
+ "\x00\xFF\xFF\x00\x00\xFF\xFF\x00", "\x00\xFF\xFF\x00\x00\xFF\xFF\xFF",
+ "\x00\xFF\xFF\x00\xFF\x00\x00\x00", "\x00\xFF\xFF\x00\xFF\x00\x00\xFF",
+ "\x00\xFF\xFF\x00\xFF\x00\xFF\x00", "\x00\xFF\xFF\x00\xFF\x00\xFF\xFF",
+ "\x00\xFF\xFF\x00\xFF\xFF\x00\x00", "\x00\xFF\xFF\x00\xFF\xFF\x00\xFF",
+ "\x00\xFF\xFF\x00\xFF\xFF\xFF\x00", "\x00\xFF\xFF\x00\xFF\xFF\xFF\xFF",
+ "\x00\xFF\xFF\xFF\x00\x00\x00\x00", "\x00\xFF\xFF\xFF\x00\x00\x00\xFF",
+ "\x00\xFF\xFF\xFF\x00\x00\xFF\x00", "\x00\xFF\xFF\xFF\x00\x00\xFF\xFF",
+ "\x00\xFF\xFF\xFF\x00\xFF\x00\x00", "\x00\xFF\xFF\xFF\x00\xFF\x00\xFF",
+ "\x00\xFF\xFF\xFF\x00\xFF\xFF\x00", "\x00\xFF\xFF\xFF\x00\xFF\xFF\xFF",
+ "\x00\xFF\xFF\xFF\xFF\x00\x00\x00", "\x00\xFF\xFF\xFF\xFF\x00\x00\xFF",
+ "\x00\xFF\xFF\xFF\xFF\x00\xFF\x00", "\x00\xFF\xFF\xFF\xFF\x00\xFF\xFF",
+ "\x00\xFF\xFF\xFF\xFF\xFF\x00\x00", "\x00\xFF\xFF\xFF\xFF\xFF\x00\xFF",
+ "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
+ "\xFF\x00\x00\x00\x00\x00\x00\x00", "\xFF\x00\x00\x00\x00\x00\x00\xFF",
+ "\xFF\x00\x00\x00\x00\x00\xFF\x00", "\xFF\x00\x00\x00\x00\x00\xFF\xFF",
+ "\xFF\x00\x00\x00\x00\xFF\x00\x00", "\xFF\x00\x00\x00\x00\xFF\x00\xFF",
+ "\xFF\x00\x00\x00\x00\xFF\xFF\x00", "\xFF\x00\x00\x00\x00\xFF\xFF\xFF",
+ "\xFF\x00\x00\x00\xFF\x00\x00\x00", "\xFF\x00\x00\x00\xFF\x00\x00\xFF",
+ "\xFF\x00\x00\x00\xFF\x00\xFF\x00", "\xFF\x00\x00\x00\xFF\x00\xFF\xFF",
+ "\xFF\x00\x00\x00\xFF\xFF\x00\x00", "\xFF\x00\x00\x00\xFF\xFF\x00\xFF",
+ "\xFF\x00\x00\x00\xFF\xFF\xFF\x00", "\xFF\x00\x00\x00\xFF\xFF\xFF\xFF",
+ "\xFF\x00\x00\xFF\x00\x00\x00\x00", "\xFF\x00\x00\xFF\x00\x00\x00\xFF",
+ "\xFF\x00\x00\xFF\x00\x00\xFF\x00", "\xFF\x00\x00\xFF\x00\x00\xFF\xFF",
+ "\xFF\x00\x00\xFF\x00\xFF\x00\x00", "\xFF\x00\x00\xFF\x00\xFF\x00\xFF",
+ "\xFF\x00\x00\xFF\x00\xFF\xFF\x00", "\xFF\x00\x00\xFF\x00\xFF\xFF\xFF",
+ "\xFF\x00\x00\xFF\xFF\x00\x00\x00", "\xFF\x00\x00\xFF\xFF\x00\x00\xFF",
+ "\xFF\x00\x00\xFF\xFF\x00\xFF\x00", "\xFF\x00\x00\xFF\xFF\x00\xFF\xFF",
+ "\xFF\x00\x00\xFF\xFF\xFF\x00\x00", "\xFF\x00\x00\xFF\xFF\xFF\x00\xFF",
+ "\xFF\x00\x00\xFF\xFF\xFF\xFF\x00", "\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF",
+ "\xFF\x00\xFF\x00\x00\x00\x00\x00", "\xFF\x00\xFF\x00\x00\x00\x00\xFF",
+ "\xFF\x00\xFF\x00\x00\x00\xFF\x00", "\xFF\x00\xFF\x00\x00\x00\xFF\xFF",
+ "\xFF\x00\xFF\x00\x00\xFF\x00\x00", "\xFF\x00\xFF\x00\x00\xFF\x00\xFF",
+ "\xFF\x00\xFF\x00\x00\xFF\xFF\x00", "\xFF\x00\xFF\x00\x00\xFF\xFF\xFF",
+ "\xFF\x00\xFF\x00\xFF\x00\x00\x00", "\xFF\x00\xFF\x00\xFF\x00\x00\xFF",
+ "\xFF\x00\xFF\x00\xFF\x00\xFF\x00", "\xFF\x00\xFF\x00\xFF\x00\xFF\xFF",
+ "\xFF\x00\xFF\x00\xFF\xFF\x00\x00", "\xFF\x00\xFF\x00\xFF\xFF\x00\xFF",
+ "\xFF\x00\xFF\x00\xFF\xFF\xFF\x00", "\xFF\x00\xFF\x00\xFF\xFF\xFF\xFF",
+ "\xFF\x00\xFF\xFF\x00\x00\x00\x00", "\xFF\x00\xFF\xFF\x00\x00\x00\xFF",
+ "\xFF\x00\xFF\xFF\x00\x00\xFF\x00", "\xFF\x00\xFF\xFF\x00\x00\xFF\xFF",
+ "\xFF\x00\xFF\xFF\x00\xFF\x00\x00", "\xFF\x00\xFF\xFF\x00\xFF\x00\xFF",
+ "\xFF\x00\xFF\xFF\x00\xFF\xFF\x00", "\xFF\x00\xFF\xFF\x00\xFF\xFF\xFF",
+ "\xFF\x00\xFF\xFF\xFF\x00\x00\x00", "\xFF\x00\xFF\xFF\xFF\x00\x00\xFF",
+ "\xFF\x00\xFF\xFF\xFF\x00\xFF\x00", "\xFF\x00\xFF\xFF\xFF\x00\xFF\xFF",
+ "\xFF\x00\xFF\xFF\xFF\xFF\x00\x00", "\xFF\x00\xFF\xFF\xFF\xFF\x00\xFF",
+ "\xFF\x00\xFF\xFF\xFF\xFF\xFF\x00", "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF",
+ "\xFF\xFF\x00\x00\x00\x00\x00\x00", "\xFF\xFF\x00\x00\x00\x00\x00\xFF",
+ "\xFF\xFF\x00\x00\x00\x00\xFF\x00", "\xFF\xFF\x00\x00\x00\x00\xFF\xFF",
+ "\xFF\xFF\x00\x00\x00\xFF\x00\x00", "\xFF\xFF\x00\x00\x00\xFF\x00\xFF",
+ "\xFF\xFF\x00\x00\x00\xFF\xFF\x00", "\xFF\xFF\x00\x00\x00\xFF\xFF\xFF",
+ "\xFF\xFF\x00\x00\xFF\x00\x00\x00", "\xFF\xFF\x00\x00\xFF\x00\x00\xFF",
+ "\xFF\xFF\x00\x00\xFF\x00\xFF\x00", "\xFF\xFF\x00\x00\xFF\x00\xFF\xFF",
+ "\xFF\xFF\x00\x00\xFF\xFF\x00\x00", "\xFF\xFF\x00\x00\xFF\xFF\x00\xFF",
+ "\xFF\xFF\x00\x00\xFF\xFF\xFF\x00", "\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF",
+ "\xFF\xFF\x00\xFF\x00\x00\x00\x00", "\xFF\xFF\x00\xFF\x00\x00\x00\xFF",
+ "\xFF\xFF\x00\xFF\x00\x00\xFF\x00", "\xFF\xFF\x00\xFF\x00\x00\xFF\xFF",
+ "\xFF\xFF\x00\xFF\x00\xFF\x00\x00", "\xFF\xFF\x00\xFF\x00\xFF\x00\xFF",
+ "\xFF\xFF\x00\xFF\x00\xFF\xFF\x00", "\xFF\xFF\x00\xFF\x00\xFF\xFF\xFF",
+ "\xFF\xFF\x00\xFF\xFF\x00\x00\x00", "\xFF\xFF\x00\xFF\xFF\x00\x00\xFF",
+ "\xFF\xFF\x00\xFF\xFF\x00\xFF\x00", "\xFF\xFF\x00\xFF\xFF\x00\xFF\xFF",
+ "\xFF\xFF\x00\xFF\xFF\xFF\x00\x00", "\xFF\xFF\x00\xFF\xFF\xFF\x00\xFF",
+ "\xFF\xFF\x00\xFF\xFF\xFF\xFF\x00", "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF",
+ "\xFF\xFF\xFF\x00\x00\x00\x00\x00", "\xFF\xFF\xFF\x00\x00\x00\x00\xFF",
+ "\xFF\xFF\xFF\x00\x00\x00\xFF\x00", "\xFF\xFF\xFF\x00\x00\x00\xFF\xFF",
+ "\xFF\xFF\xFF\x00\x00\xFF\x00\x00", "\xFF\xFF\xFF\x00\x00\xFF\x00\xFF",
+ "\xFF\xFF\xFF\x00\x00\xFF\xFF\x00", "\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF",
+ "\xFF\xFF\xFF\x00\xFF\x00\x00\x00", "\xFF\xFF\xFF\x00\xFF\x00\x00\xFF",
+ "\xFF\xFF\xFF\x00\xFF\x00\xFF\x00", "\xFF\xFF\xFF\x00\xFF\x00\xFF\xFF",
+ "\xFF\xFF\xFF\x00\xFF\xFF\x00\x00", "\xFF\xFF\xFF\x00\xFF\xFF\x00\xFF",
+ "\xFF\xFF\xFF\x00\xFF\xFF\xFF\x00", "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF",
+ "\xFF\xFF\xFF\xFF\x00\x00\x00\x00", "\xFF\xFF\xFF\xFF\x00\x00\x00\xFF",
+ "\xFF\xFF\xFF\xFF\x00\x00\xFF\x00", "\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF",
+ "\xFF\xFF\xFF\xFF\x00\xFF\x00\x00", "\xFF\xFF\xFF\xFF\x00\xFF\x00\xFF",
+ "\xFF\xFF\xFF\xFF\x00\xFF\xFF\x00", "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF",
+ "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00", "\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF",
+ "\xFF\xFF\xFF\xFF\xFF\x00\xFF\x00", "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF",
+ "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00", "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF",
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ ];
+
+ /**
+ * IP mapping helper table.
+ *
+ * Indexing this table with each source byte performs the initial bit permutation.
+ *
+ * @var array
+ */
+ protected static $ipmap = [
+ 0x00, 0x10, 0x01, 0x11, 0x20, 0x30, 0x21, 0x31,
+ 0x02, 0x12, 0x03, 0x13, 0x22, 0x32, 0x23, 0x33,
+ 0x40, 0x50, 0x41, 0x51, 0x60, 0x70, 0x61, 0x71,
+ 0x42, 0x52, 0x43, 0x53, 0x62, 0x72, 0x63, 0x73,
+ 0x04, 0x14, 0x05, 0x15, 0x24, 0x34, 0x25, 0x35,
+ 0x06, 0x16, 0x07, 0x17, 0x26, 0x36, 0x27, 0x37,
+ 0x44, 0x54, 0x45, 0x55, 0x64, 0x74, 0x65, 0x75,
+ 0x46, 0x56, 0x47, 0x57, 0x66, 0x76, 0x67, 0x77,
+ 0x80, 0x90, 0x81, 0x91, 0xA0, 0xB0, 0xA1, 0xB1,
+ 0x82, 0x92, 0x83, 0x93, 0xA2, 0xB2, 0xA3, 0xB3,
+ 0xC0, 0xD0, 0xC1, 0xD1, 0xE0, 0xF0, 0xE1, 0xF1,
+ 0xC2, 0xD2, 0xC3, 0xD3, 0xE2, 0xF2, 0xE3, 0xF3,
+ 0x84, 0x94, 0x85, 0x95, 0xA4, 0xB4, 0xA5, 0xB5,
+ 0x86, 0x96, 0x87, 0x97, 0xA6, 0xB6, 0xA7, 0xB7,
+ 0xC4, 0xD4, 0xC5, 0xD5, 0xE4, 0xF4, 0xE5, 0xF5,
+ 0xC6, 0xD6, 0xC7, 0xD7, 0xE6, 0xF6, 0xE7, 0xF7,
+ 0x08, 0x18, 0x09, 0x19, 0x28, 0x38, 0x29, 0x39,
+ 0x0A, 0x1A, 0x0B, 0x1B, 0x2A, 0x3A, 0x2B, 0x3B,
+ 0x48, 0x58, 0x49, 0x59, 0x68, 0x78, 0x69, 0x79,
+ 0x4A, 0x5A, 0x4B, 0x5B, 0x6A, 0x7A, 0x6B, 0x7B,
+ 0x0C, 0x1C, 0x0D, 0x1D, 0x2C, 0x3C, 0x2D, 0x3D,
+ 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F,
+ 0x4C, 0x5C, 0x4D, 0x5D, 0x6C, 0x7C, 0x6D, 0x7D,
+ 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F,
+ 0x88, 0x98, 0x89, 0x99, 0xA8, 0xB8, 0xA9, 0xB9,
+ 0x8A, 0x9A, 0x8B, 0x9B, 0xAA, 0xBA, 0xAB, 0xBB,
+ 0xC8, 0xD8, 0xC9, 0xD9, 0xE8, 0xF8, 0xE9, 0xF9,
+ 0xCA, 0xDA, 0xCB, 0xDB, 0xEA, 0xFA, 0xEB, 0xFB,
+ 0x8C, 0x9C, 0x8D, 0x9D, 0xAC, 0xBC, 0xAD, 0xBD,
+ 0x8E, 0x9E, 0x8F, 0x9F, 0xAE, 0xBE, 0xAF, 0xBF,
+ 0xCC, 0xDC, 0xCD, 0xDD, 0xEC, 0xFC, 0xED, 0xFD,
+ 0xCE, 0xDE, 0xCF, 0xDF, 0xEE, 0xFE, 0xEF, 0xFF
+ ];
+
+ /**
+ * Inverse IP mapping helper table.
+ * Indexing this table with a byte value reverses the bit order.
+ *
+ * @var array
+ */
+ protected static $invipmap = [
+ 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
+ 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
+ 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
+ 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
+ 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
+ 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
+ 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC,
+ 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
+ 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
+ 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
+ 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA,
+ 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
+ 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6,
+ 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
+ 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
+ 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
+ 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1,
+ 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
+ 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9,
+ 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
+ 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
+ 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
+ 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED,
+ 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
+ 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
+ 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
+ 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
+ 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
+ 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7,
+ 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
+ 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
+ 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
+ ];
+
+ /**
+ * Pre-permuted S-box1
+ *
+ * Each box ($sbox1-$sbox8) has been vectorized, then each value pre-permuted using the
+ * P table: concatenation can then be replaced by exclusive ORs.
+ *
+ * @var array
+ */
+ protected static $sbox1 = [
+ 0x00808200, 0x00000000, 0x00008000, 0x00808202,
+ 0x00808002, 0x00008202, 0x00000002, 0x00008000,
+ 0x00000200, 0x00808200, 0x00808202, 0x00000200,
+ 0x00800202, 0x00808002, 0x00800000, 0x00000002,
+ 0x00000202, 0x00800200, 0x00800200, 0x00008200,
+ 0x00008200, 0x00808000, 0x00808000, 0x00800202,
+ 0x00008002, 0x00800002, 0x00800002, 0x00008002,
+ 0x00000000, 0x00000202, 0x00008202, 0x00800000,
+ 0x00008000, 0x00808202, 0x00000002, 0x00808000,
+ 0x00808200, 0x00800000, 0x00800000, 0x00000200,
+ 0x00808002, 0x00008000, 0x00008200, 0x00800002,
+ 0x00000200, 0x00000002, 0x00800202, 0x00008202,
+ 0x00808202, 0x00008002, 0x00808000, 0x00800202,
+ 0x00800002, 0x00000202, 0x00008202, 0x00808200,
+ 0x00000202, 0x00800200, 0x00800200, 0x00000000,
+ 0x00008002, 0x00008200, 0x00000000, 0x00808002
+ ];
+
+ /**
+ * Pre-permuted S-box2
+ *
+ * @var array
+ */
+ protected static $sbox2 = [
+ 0x40084010, 0x40004000, 0x00004000, 0x00084010,
+ 0x00080000, 0x00000010, 0x40080010, 0x40004010,
+ 0x40000010, 0x40084010, 0x40084000, 0x40000000,
+ 0x40004000, 0x00080000, 0x00000010, 0x40080010,
+ 0x00084000, 0x00080010, 0x40004010, 0x00000000,
+ 0x40000000, 0x00004000, 0x00084010, 0x40080000,
+ 0x00080010, 0x40000010, 0x00000000, 0x00084000,
+ 0x00004010, 0x40084000, 0x40080000, 0x00004010,
+ 0x00000000, 0x00084010, 0x40080010, 0x00080000,
+ 0x40004010, 0x40080000, 0x40084000, 0x00004000,
+ 0x40080000, 0x40004000, 0x00000010, 0x40084010,
+ 0x00084010, 0x00000010, 0x00004000, 0x40000000,
+ 0x00004010, 0x40084000, 0x00080000, 0x40000010,
+ 0x00080010, 0x40004010, 0x40000010, 0x00080010,
+ 0x00084000, 0x00000000, 0x40004000, 0x00004010,
+ 0x40000000, 0x40080010, 0x40084010, 0x00084000
+ ];
+
+ /**
+ * Pre-permuted S-box3
+ *
+ * @var array
+ */
+ protected static $sbox3 = [
+ 0x00000104, 0x04010100, 0x00000000, 0x04010004,
+ 0x04000100, 0x00000000, 0x00010104, 0x04000100,
+ 0x00010004, 0x04000004, 0x04000004, 0x00010000,
+ 0x04010104, 0x00010004, 0x04010000, 0x00000104,
+ 0x04000000, 0x00000004, 0x04010100, 0x00000100,
+ 0x00010100, 0x04010000, 0x04010004, 0x00010104,
+ 0x04000104, 0x00010100, 0x00010000, 0x04000104,
+ 0x00000004, 0x04010104, 0x00000100, 0x04000000,
+ 0x04010100, 0x04000000, 0x00010004, 0x00000104,
+ 0x00010000, 0x04010100, 0x04000100, 0x00000000,
+ 0x00000100, 0x00010004, 0x04010104, 0x04000100,
+ 0x04000004, 0x00000100, 0x00000000, 0x04010004,
+ 0x04000104, 0x00010000, 0x04000000, 0x04010104,
+ 0x00000004, 0x00010104, 0x00010100, 0x04000004,
+ 0x04010000, 0x04000104, 0x00000104, 0x04010000,
+ 0x00010104, 0x00000004, 0x04010004, 0x00010100
+ ];
+
+ /**
+ * Pre-permuted S-box4
+ *
+ * @var array
+ */
+ protected static $sbox4 = [
+ 0x80401000, 0x80001040, 0x80001040, 0x00000040,
+ 0x00401040, 0x80400040, 0x80400000, 0x80001000,
+ 0x00000000, 0x00401000, 0x00401000, 0x80401040,
+ 0x80000040, 0x00000000, 0x00400040, 0x80400000,
+ 0x80000000, 0x00001000, 0x00400000, 0x80401000,
+ 0x00000040, 0x00400000, 0x80001000, 0x00001040,
+ 0x80400040, 0x80000000, 0x00001040, 0x00400040,
+ 0x00001000, 0x00401040, 0x80401040, 0x80000040,
+ 0x00400040, 0x80400000, 0x00401000, 0x80401040,
+ 0x80000040, 0x00000000, 0x00000000, 0x00401000,
+ 0x00001040, 0x00400040, 0x80400040, 0x80000000,
+ 0x80401000, 0x80001040, 0x80001040, 0x00000040,
+ 0x80401040, 0x80000040, 0x80000000, 0x00001000,
+ 0x80400000, 0x80001000, 0x00401040, 0x80400040,
+ 0x80001000, 0x00001040, 0x00400000, 0x80401000,
+ 0x00000040, 0x00400000, 0x00001000, 0x00401040
+ ];
+
+ /**
+ * Pre-permuted S-box5
+ *
+ * @var array
+ */
+ protected static $sbox5 = [
+ 0x00000080, 0x01040080, 0x01040000, 0x21000080,
+ 0x00040000, 0x00000080, 0x20000000, 0x01040000,
+ 0x20040080, 0x00040000, 0x01000080, 0x20040080,
+ 0x21000080, 0x21040000, 0x00040080, 0x20000000,
+ 0x01000000, 0x20040000, 0x20040000, 0x00000000,
+ 0x20000080, 0x21040080, 0x21040080, 0x01000080,
+ 0x21040000, 0x20000080, 0x00000000, 0x21000000,
+ 0x01040080, 0x01000000, 0x21000000, 0x00040080,
+ 0x00040000, 0x21000080, 0x00000080, 0x01000000,
+ 0x20000000, 0x01040000, 0x21000080, 0x20040080,
+ 0x01000080, 0x20000000, 0x21040000, 0x01040080,
+ 0x20040080, 0x00000080, 0x01000000, 0x21040000,
+ 0x21040080, 0x00040080, 0x21000000, 0x21040080,
+ 0x01040000, 0x00000000, 0x20040000, 0x21000000,
+ 0x00040080, 0x01000080, 0x20000080, 0x00040000,
+ 0x00000000, 0x20040000, 0x01040080, 0x20000080
+ ];
+
+ /**
+ * Pre-permuted S-box6
+ *
+ * @var array
+ */
+ protected static $sbox6 = [
+ 0x10000008, 0x10200000, 0x00002000, 0x10202008,
+ 0x10200000, 0x00000008, 0x10202008, 0x00200000,
+ 0x10002000, 0x00202008, 0x00200000, 0x10000008,
+ 0x00200008, 0x10002000, 0x10000000, 0x00002008,
+ 0x00000000, 0x00200008, 0x10002008, 0x00002000,
+ 0x00202000, 0x10002008, 0x00000008, 0x10200008,
+ 0x10200008, 0x00000000, 0x00202008, 0x10202000,
+ 0x00002008, 0x00202000, 0x10202000, 0x10000000,
+ 0x10002000, 0x00000008, 0x10200008, 0x00202000,
+ 0x10202008, 0x00200000, 0x00002008, 0x10000008,
+ 0x00200000, 0x10002000, 0x10000000, 0x00002008,
+ 0x10000008, 0x10202008, 0x00202000, 0x10200000,
+ 0x00202008, 0x10202000, 0x00000000, 0x10200008,
+ 0x00000008, 0x00002000, 0x10200000, 0x00202008,
+ 0x00002000, 0x00200008, 0x10002008, 0x00000000,
+ 0x10202000, 0x10000000, 0x00200008, 0x10002008
+ ];
+
+ /**
+ * Pre-permuted S-box7
+ *
+ * @var array
+ */
+ protected static $sbox7 = [
+ 0x00100000, 0x02100001, 0x02000401, 0x00000000,
+ 0x00000400, 0x02000401, 0x00100401, 0x02100400,
+ 0x02100401, 0x00100000, 0x00000000, 0x02000001,
+ 0x00000001, 0x02000000, 0x02100001, 0x00000401,
+ 0x02000400, 0x00100401, 0x00100001, 0x02000400,
+ 0x02000001, 0x02100000, 0x02100400, 0x00100001,
+ 0x02100000, 0x00000400, 0x00000401, 0x02100401,
+ 0x00100400, 0x00000001, 0x02000000, 0x00100400,
+ 0x02000000, 0x00100400, 0x00100000, 0x02000401,
+ 0x02000401, 0x02100001, 0x02100001, 0x00000001,
+ 0x00100001, 0x02000000, 0x02000400, 0x00100000,
+ 0x02100400, 0x00000401, 0x00100401, 0x02100400,
+ 0x00000401, 0x02000001, 0x02100401, 0x02100000,
+ 0x00100400, 0x00000000, 0x00000001, 0x02100401,
+ 0x00000000, 0x00100401, 0x02100000, 0x00000400,
+ 0x02000001, 0x02000400, 0x00000400, 0x00100001
+ ];
+
+ /**
+ * Pre-permuted S-box8
+ *
+ * @var array
+ */
+ protected static $sbox8 = [
+ 0x08000820, 0x00000800, 0x00020000, 0x08020820,
+ 0x08000000, 0x08000820, 0x00000020, 0x08000000,
+ 0x00020020, 0x08020000, 0x08020820, 0x00020800,
+ 0x08020800, 0x00020820, 0x00000800, 0x00000020,
+ 0x08020000, 0x08000020, 0x08000800, 0x00000820,
+ 0x00020800, 0x00020020, 0x08020020, 0x08020800,
+ 0x00000820, 0x00000000, 0x00000000, 0x08020020,
+ 0x08000020, 0x08000800, 0x00020820, 0x00020000,
+ 0x00020820, 0x00020000, 0x08020800, 0x00000800,
+ 0x00000020, 0x08020020, 0x00000800, 0x00020820,
+ 0x08000800, 0x00000020, 0x08000020, 0x08020000,
+ 0x08020020, 0x08000000, 0x00020000, 0x08000820,
+ 0x00000000, 0x08020820, 0x00020020, 0x08000020,
+ 0x08020000, 0x08000800, 0x08000820, 0x00000000,
+ 0x08020820, 0x00020800, 0x00020800, 0x00000820,
+ 0x00000820, 0x00020020, 0x08000000, 0x08020800
+ ];
+
+ /**
+ * Default Constructor.
+ *
+ * @param string $mode
+ * @throws BadModeException if an invalid / unsupported mode is provided
+ */
+ public function __construct($mode)
+ {
+ parent::__construct($mode);
+
+ if ($this->mode == self::MODE_STREAM) {
+ throw new BadModeException('Block ciphers cannot be ran in stream mode');
+ }
+ }
+
+ /**
+ * Test for engine validity
+ *
+ * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
+ *
+ * @see Common\SymmetricKey::isValidEngine()
+ * @param int $engine
+ * @return bool
+ */
+ protected function isValidEngineHelper($engine)
+ {
+ if ($this->key_length_max == 8) {
+ if ($engine == self::ENGINE_OPENSSL) {
+ // 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 (defined('OPENSSL_VERSION_TEXT') && version_compare(preg_replace('#OpenSSL (\d+\.\d+\.\d+) .*#', '$1', OPENSSL_VERSION_TEXT), '3.0.1', '>=')) {
+ return false;
+ }
+ $this->cipher_name_openssl_ecb = 'des-ecb';
+ $this->cipher_name_openssl = 'des-' . $this->openssl_translate_mode();
+ }
+ }
+
+ return parent::isValidEngineHelper($engine);
+ }
+
+ /**
+ * Sets the key.
+ *
+ * Keys must be 64-bits long or 8 bytes long.
+ *
+ * DES also requires that every eighth bit be a parity bit, however, we'll ignore that.
+ *
+ * @see Common\SymmetricKey::setKey()
+ * @param string $key
+ */
+ public function setKey($key)
+ {
+ if (!($this instanceof TripleDES) && strlen($key) != 8) {
+ throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of size 8 are supported');
+ }
+
+ // Sets the key
+ parent::setKey($key);
+ }
+
+ /**
+ * Encrypts a block
+ *
+ * @see Common\SymmetricKey::encryptBlock()
+ * @see Common\SymmetricKey::encrypt()
+ * @see self::encrypt()
+ * @param string $in
+ * @return string
+ */
+ protected function encryptBlock($in)
+ {
+ return $this->processBlock($in, self::ENCRYPT);
+ }
+
+ /**
+ * Decrypts a block
+ *
+ * @see Common\SymmetricKey::decryptBlock()
+ * @see Common\SymmetricKey::decrypt()
+ * @see self::decrypt()
+ * @param string $in
+ * @return string
+ */
+ protected function decryptBlock($in)
+ {
+ return $this->processBlock($in, self::DECRYPT);
+ }
+
+ /**
+ * Encrypts or decrypts a 64-bit block
+ *
+ * $mode should be either self::ENCRYPT or self::DECRYPT. See
+ * {@link http://en.wikipedia.org/wiki/Image:Feistel.png Feistel.png} to get a general
+ * idea of what this function does.
+ *
+ * @see self::encryptBlock()
+ * @see self::decryptBlock()
+ * @param string $block
+ * @param int $mode
+ * @return string
+ */
+ private function processBlock($block, $mode)
+ {
+ static $sbox1, $sbox2, $sbox3, $sbox4, $sbox5, $sbox6, $sbox7, $sbox8, $shuffleip, $shuffleinvip;
+ if (!$sbox1) {
+ $sbox1 = array_map('intval', self::$sbox1);
+ $sbox2 = array_map('intval', self::$sbox2);
+ $sbox3 = array_map('intval', self::$sbox3);
+ $sbox4 = array_map('intval', self::$sbox4);
+ $sbox5 = array_map('intval', self::$sbox5);
+ $sbox6 = array_map('intval', self::$sbox6);
+ $sbox7 = array_map('intval', self::$sbox7);
+ $sbox8 = array_map('intval', self::$sbox8);
+ /* Merge $shuffle with $[inv]ipmap */
+ for ($i = 0; $i < 256; ++$i) {
+ $shuffleip[] = self::$shuffle[self::$ipmap[$i]];
+ $shuffleinvip[] = self::$shuffle[self::$invipmap[$i]];
+ }
+ }
+
+ $keys = $this->keys[$mode];
+ $ki = -1;
+
+ // Do the initial IP permutation.
+ $t = unpack('Nl/Nr', $block);
+ list($l, $r) = [$t['l'], $t['r']];
+ $block = ($shuffleip[ $r & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
+ ($shuffleip[($r >> 8) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
+ ($shuffleip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
+ ($shuffleip[($r >> 24) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") |
+ ($shuffleip[ $l & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") |
+ ($shuffleip[($l >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") |
+ ($shuffleip[($l >> 16) & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") |
+ ($shuffleip[($l >> 24) & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01");
+
+ // Extract L0 and R0.
+ $t = unpack('Nl/Nr', $block);
+ list($l, $r) = [$t['l'], $t['r']];
+
+ for ($des_round = 0; $des_round < $this->des_rounds; ++$des_round) {
+ // Perform the 16 steps.
+ for ($i = 0; $i < 16; $i++) {
+ // start of "the Feistel (F) function" - see the following URL:
+ // http://en.wikipedia.org/wiki/Image:Data_Encryption_Standard_InfoBox_Diagram.png
+ // Merge key schedule.
+ $b1 = (($r >> 3) & 0x1FFFFFFF) ^ ($r << 29) ^ $keys[++$ki];
+ $b2 = (($r >> 31) & 0x00000001) ^ ($r << 1) ^ $keys[++$ki];
+
+ // S-box indexing.
+ $t = $sbox1[($b1 >> 24) & 0x3F] ^ $sbox2[($b2 >> 24) & 0x3F] ^
+ $sbox3[($b1 >> 16) & 0x3F] ^ $sbox4[($b2 >> 16) & 0x3F] ^
+ $sbox5[($b1 >> 8) & 0x3F] ^ $sbox6[($b2 >> 8) & 0x3F] ^
+ $sbox7[ $b1 & 0x3F] ^ $sbox8[ $b2 & 0x3F] ^ $l;
+ // end of "the Feistel (F) function"
+
+ $l = $r;
+ $r = $t;
+ }
+
+ // Last step should not permute L & R.
+ $t = $l;
+ $l = $r;
+ $r = $t;
+ }
+
+ // Perform the inverse IP permutation.
+ return ($shuffleinvip[($r >> 24) & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
+ ($shuffleinvip[($l >> 24) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
+ ($shuffleinvip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
+ ($shuffleinvip[($l >> 16) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") |
+ ($shuffleinvip[($r >> 8) & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") |
+ ($shuffleinvip[($l >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") |
+ ($shuffleinvip[ $r & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") |
+ ($shuffleinvip[ $l & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01");
+ }
+
+ /**
+ * Creates the key schedule
+ *
+ * @see Common\SymmetricKey::setupKey()
+ */
+ protected function setupKey()
+ {
+ if (isset($this->kl['key']) && $this->key === $this->kl['key'] && $this->des_rounds === $this->kl['des_rounds']) {
+ // already expanded
+ return;
+ }
+ $this->kl = ['key' => $this->key, 'des_rounds' => $this->des_rounds];
+
+ static $shifts = [ // number of key bits shifted per round
+ 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
+ ];
+
+ static $pc1map = [
+ 0x00, 0x00, 0x08, 0x08, 0x04, 0x04, 0x0C, 0x0C,
+ 0x02, 0x02, 0x0A, 0x0A, 0x06, 0x06, 0x0E, 0x0E,
+ 0x10, 0x10, 0x18, 0x18, 0x14, 0x14, 0x1C, 0x1C,
+ 0x12, 0x12, 0x1A, 0x1A, 0x16, 0x16, 0x1E, 0x1E,
+ 0x20, 0x20, 0x28, 0x28, 0x24, 0x24, 0x2C, 0x2C,
+ 0x22, 0x22, 0x2A, 0x2A, 0x26, 0x26, 0x2E, 0x2E,
+ 0x30, 0x30, 0x38, 0x38, 0x34, 0x34, 0x3C, 0x3C,
+ 0x32, 0x32, 0x3A, 0x3A, 0x36, 0x36, 0x3E, 0x3E,
+ 0x40, 0x40, 0x48, 0x48, 0x44, 0x44, 0x4C, 0x4C,
+ 0x42, 0x42, 0x4A, 0x4A, 0x46, 0x46, 0x4E, 0x4E,
+ 0x50, 0x50, 0x58, 0x58, 0x54, 0x54, 0x5C, 0x5C,
+ 0x52, 0x52, 0x5A, 0x5A, 0x56, 0x56, 0x5E, 0x5E,
+ 0x60, 0x60, 0x68, 0x68, 0x64, 0x64, 0x6C, 0x6C,
+ 0x62, 0x62, 0x6A, 0x6A, 0x66, 0x66, 0x6E, 0x6E,
+ 0x70, 0x70, 0x78, 0x78, 0x74, 0x74, 0x7C, 0x7C,
+ 0x72, 0x72, 0x7A, 0x7A, 0x76, 0x76, 0x7E, 0x7E,
+ 0x80, 0x80, 0x88, 0x88, 0x84, 0x84, 0x8C, 0x8C,
+ 0x82, 0x82, 0x8A, 0x8A, 0x86, 0x86, 0x8E, 0x8E,
+ 0x90, 0x90, 0x98, 0x98, 0x94, 0x94, 0x9C, 0x9C,
+ 0x92, 0x92, 0x9A, 0x9A, 0x96, 0x96, 0x9E, 0x9E,
+ 0xA0, 0xA0, 0xA8, 0xA8, 0xA4, 0xA4, 0xAC, 0xAC,
+ 0xA2, 0xA2, 0xAA, 0xAA, 0xA6, 0xA6, 0xAE, 0xAE,
+ 0xB0, 0xB0, 0xB8, 0xB8, 0xB4, 0xB4, 0xBC, 0xBC,
+ 0xB2, 0xB2, 0xBA, 0xBA, 0xB6, 0xB6, 0xBE, 0xBE,
+ 0xC0, 0xC0, 0xC8, 0xC8, 0xC4, 0xC4, 0xCC, 0xCC,
+ 0xC2, 0xC2, 0xCA, 0xCA, 0xC6, 0xC6, 0xCE, 0xCE,
+ 0xD0, 0xD0, 0xD8, 0xD8, 0xD4, 0xD4, 0xDC, 0xDC,
+ 0xD2, 0xD2, 0xDA, 0xDA, 0xD6, 0xD6, 0xDE, 0xDE,
+ 0xE0, 0xE0, 0xE8, 0xE8, 0xE4, 0xE4, 0xEC, 0xEC,
+ 0xE2, 0xE2, 0xEA, 0xEA, 0xE6, 0xE6, 0xEE, 0xEE,
+ 0xF0, 0xF0, 0xF8, 0xF8, 0xF4, 0xF4, 0xFC, 0xFC,
+ 0xF2, 0xF2, 0xFA, 0xFA, 0xF6, 0xF6, 0xFE, 0xFE
+ ];
+
+ // Mapping tables for the PC-2 transformation.
+ static $pc2mapc1 = [
+ 0x00000000, 0x00000400, 0x00200000, 0x00200400,
+ 0x00000001, 0x00000401, 0x00200001, 0x00200401,
+ 0x02000000, 0x02000400, 0x02200000, 0x02200400,
+ 0x02000001, 0x02000401, 0x02200001, 0x02200401
+ ];
+ static $pc2mapc2 = [
+ 0x00000000, 0x00000800, 0x08000000, 0x08000800,
+ 0x00010000, 0x00010800, 0x08010000, 0x08010800,
+ 0x00000000, 0x00000800, 0x08000000, 0x08000800,
+ 0x00010000, 0x00010800, 0x08010000, 0x08010800,
+ 0x00000100, 0x00000900, 0x08000100, 0x08000900,
+ 0x00010100, 0x00010900, 0x08010100, 0x08010900,
+ 0x00000100, 0x00000900, 0x08000100, 0x08000900,
+ 0x00010100, 0x00010900, 0x08010100, 0x08010900,
+ 0x00000010, 0x00000810, 0x08000010, 0x08000810,
+ 0x00010010, 0x00010810, 0x08010010, 0x08010810,
+ 0x00000010, 0x00000810, 0x08000010, 0x08000810,
+ 0x00010010, 0x00010810, 0x08010010, 0x08010810,
+ 0x00000110, 0x00000910, 0x08000110, 0x08000910,
+ 0x00010110, 0x00010910, 0x08010110, 0x08010910,
+ 0x00000110, 0x00000910, 0x08000110, 0x08000910,
+ 0x00010110, 0x00010910, 0x08010110, 0x08010910,
+ 0x00040000, 0x00040800, 0x08040000, 0x08040800,
+ 0x00050000, 0x00050800, 0x08050000, 0x08050800,
+ 0x00040000, 0x00040800, 0x08040000, 0x08040800,
+ 0x00050000, 0x00050800, 0x08050000, 0x08050800,
+ 0x00040100, 0x00040900, 0x08040100, 0x08040900,
+ 0x00050100, 0x00050900, 0x08050100, 0x08050900,
+ 0x00040100, 0x00040900, 0x08040100, 0x08040900,
+ 0x00050100, 0x00050900, 0x08050100, 0x08050900,
+ 0x00040010, 0x00040810, 0x08040010, 0x08040810,
+ 0x00050010, 0x00050810, 0x08050010, 0x08050810,
+ 0x00040010, 0x00040810, 0x08040010, 0x08040810,
+ 0x00050010, 0x00050810, 0x08050010, 0x08050810,
+ 0x00040110, 0x00040910, 0x08040110, 0x08040910,
+ 0x00050110, 0x00050910, 0x08050110, 0x08050910,
+ 0x00040110, 0x00040910, 0x08040110, 0x08040910,
+ 0x00050110, 0x00050910, 0x08050110, 0x08050910,
+ 0x01000000, 0x01000800, 0x09000000, 0x09000800,
+ 0x01010000, 0x01010800, 0x09010000, 0x09010800,
+ 0x01000000, 0x01000800, 0x09000000, 0x09000800,
+ 0x01010000, 0x01010800, 0x09010000, 0x09010800,
+ 0x01000100, 0x01000900, 0x09000100, 0x09000900,
+ 0x01010100, 0x01010900, 0x09010100, 0x09010900,
+ 0x01000100, 0x01000900, 0x09000100, 0x09000900,
+ 0x01010100, 0x01010900, 0x09010100, 0x09010900,
+ 0x01000010, 0x01000810, 0x09000010, 0x09000810,
+ 0x01010010, 0x01010810, 0x09010010, 0x09010810,
+ 0x01000010, 0x01000810, 0x09000010, 0x09000810,
+ 0x01010010, 0x01010810, 0x09010010, 0x09010810,
+ 0x01000110, 0x01000910, 0x09000110, 0x09000910,
+ 0x01010110, 0x01010910, 0x09010110, 0x09010910,
+ 0x01000110, 0x01000910, 0x09000110, 0x09000910,
+ 0x01010110, 0x01010910, 0x09010110, 0x09010910,
+ 0x01040000, 0x01040800, 0x09040000, 0x09040800,
+ 0x01050000, 0x01050800, 0x09050000, 0x09050800,
+ 0x01040000, 0x01040800, 0x09040000, 0x09040800,
+ 0x01050000, 0x01050800, 0x09050000, 0x09050800,
+ 0x01040100, 0x01040900, 0x09040100, 0x09040900,
+ 0x01050100, 0x01050900, 0x09050100, 0x09050900,
+ 0x01040100, 0x01040900, 0x09040100, 0x09040900,
+ 0x01050100, 0x01050900, 0x09050100, 0x09050900,
+ 0x01040010, 0x01040810, 0x09040010, 0x09040810,
+ 0x01050010, 0x01050810, 0x09050010, 0x09050810,
+ 0x01040010, 0x01040810, 0x09040010, 0x09040810,
+ 0x01050010, 0x01050810, 0x09050010, 0x09050810,
+ 0x01040110, 0x01040910, 0x09040110, 0x09040910,
+ 0x01050110, 0x01050910, 0x09050110, 0x09050910,
+ 0x01040110, 0x01040910, 0x09040110, 0x09040910,
+ 0x01050110, 0x01050910, 0x09050110, 0x09050910
+ ];
+ static $pc2mapc3 = [
+ 0x00000000, 0x00000004, 0x00001000, 0x00001004,
+ 0x00000000, 0x00000004, 0x00001000, 0x00001004,
+ 0x10000000, 0x10000004, 0x10001000, 0x10001004,
+ 0x10000000, 0x10000004, 0x10001000, 0x10001004,
+ 0x00000020, 0x00000024, 0x00001020, 0x00001024,
+ 0x00000020, 0x00000024, 0x00001020, 0x00001024,
+ 0x10000020, 0x10000024, 0x10001020, 0x10001024,
+ 0x10000020, 0x10000024, 0x10001020, 0x10001024,
+ 0x00080000, 0x00080004, 0x00081000, 0x00081004,
+ 0x00080000, 0x00080004, 0x00081000, 0x00081004,
+ 0x10080000, 0x10080004, 0x10081000, 0x10081004,
+ 0x10080000, 0x10080004, 0x10081000, 0x10081004,
+ 0x00080020, 0x00080024, 0x00081020, 0x00081024,
+ 0x00080020, 0x00080024, 0x00081020, 0x00081024,
+ 0x10080020, 0x10080024, 0x10081020, 0x10081024,
+ 0x10080020, 0x10080024, 0x10081020, 0x10081024,
+ 0x20000000, 0x20000004, 0x20001000, 0x20001004,
+ 0x20000000, 0x20000004, 0x20001000, 0x20001004,
+ 0x30000000, 0x30000004, 0x30001000, 0x30001004,
+ 0x30000000, 0x30000004, 0x30001000, 0x30001004,
+ 0x20000020, 0x20000024, 0x20001020, 0x20001024,
+ 0x20000020, 0x20000024, 0x20001020, 0x20001024,
+ 0x30000020, 0x30000024, 0x30001020, 0x30001024,
+ 0x30000020, 0x30000024, 0x30001020, 0x30001024,
+ 0x20080000, 0x20080004, 0x20081000, 0x20081004,
+ 0x20080000, 0x20080004, 0x20081000, 0x20081004,
+ 0x30080000, 0x30080004, 0x30081000, 0x30081004,
+ 0x30080000, 0x30080004, 0x30081000, 0x30081004,
+ 0x20080020, 0x20080024, 0x20081020, 0x20081024,
+ 0x20080020, 0x20080024, 0x20081020, 0x20081024,
+ 0x30080020, 0x30080024, 0x30081020, 0x30081024,
+ 0x30080020, 0x30080024, 0x30081020, 0x30081024,
+ 0x00000002, 0x00000006, 0x00001002, 0x00001006,
+ 0x00000002, 0x00000006, 0x00001002, 0x00001006,
+ 0x10000002, 0x10000006, 0x10001002, 0x10001006,
+ 0x10000002, 0x10000006, 0x10001002, 0x10001006,
+ 0x00000022, 0x00000026, 0x00001022, 0x00001026,
+ 0x00000022, 0x00000026, 0x00001022, 0x00001026,
+ 0x10000022, 0x10000026, 0x10001022, 0x10001026,
+ 0x10000022, 0x10000026, 0x10001022, 0x10001026,
+ 0x00080002, 0x00080006, 0x00081002, 0x00081006,
+ 0x00080002, 0x00080006, 0x00081002, 0x00081006,
+ 0x10080002, 0x10080006, 0x10081002, 0x10081006,
+ 0x10080002, 0x10080006, 0x10081002, 0x10081006,
+ 0x00080022, 0x00080026, 0x00081022, 0x00081026,
+ 0x00080022, 0x00080026, 0x00081022, 0x00081026,
+ 0x10080022, 0x10080026, 0x10081022, 0x10081026,
+ 0x10080022, 0x10080026, 0x10081022, 0x10081026,
+ 0x20000002, 0x20000006, 0x20001002, 0x20001006,
+ 0x20000002, 0x20000006, 0x20001002, 0x20001006,
+ 0x30000002, 0x30000006, 0x30001002, 0x30001006,
+ 0x30000002, 0x30000006, 0x30001002, 0x30001006,
+ 0x20000022, 0x20000026, 0x20001022, 0x20001026,
+ 0x20000022, 0x20000026, 0x20001022, 0x20001026,
+ 0x30000022, 0x30000026, 0x30001022, 0x30001026,
+ 0x30000022, 0x30000026, 0x30001022, 0x30001026,
+ 0x20080002, 0x20080006, 0x20081002, 0x20081006,
+ 0x20080002, 0x20080006, 0x20081002, 0x20081006,
+ 0x30080002, 0x30080006, 0x30081002, 0x30081006,
+ 0x30080002, 0x30080006, 0x30081002, 0x30081006,
+ 0x20080022, 0x20080026, 0x20081022, 0x20081026,
+ 0x20080022, 0x20080026, 0x20081022, 0x20081026,
+ 0x30080022, 0x30080026, 0x30081022, 0x30081026,
+ 0x30080022, 0x30080026, 0x30081022, 0x30081026
+ ];
+ static $pc2mapc4 = [
+ 0x00000000, 0x00100000, 0x00000008, 0x00100008,
+ 0x00000200, 0x00100200, 0x00000208, 0x00100208,
+ 0x00000000, 0x00100000, 0x00000008, 0x00100008,
+ 0x00000200, 0x00100200, 0x00000208, 0x00100208,
+ 0x04000000, 0x04100000, 0x04000008, 0x04100008,
+ 0x04000200, 0x04100200, 0x04000208, 0x04100208,
+ 0x04000000, 0x04100000, 0x04000008, 0x04100008,
+ 0x04000200, 0x04100200, 0x04000208, 0x04100208,
+ 0x00002000, 0x00102000, 0x00002008, 0x00102008,
+ 0x00002200, 0x00102200, 0x00002208, 0x00102208,
+ 0x00002000, 0x00102000, 0x00002008, 0x00102008,
+ 0x00002200, 0x00102200, 0x00002208, 0x00102208,
+ 0x04002000, 0x04102000, 0x04002008, 0x04102008,
+ 0x04002200, 0x04102200, 0x04002208, 0x04102208,
+ 0x04002000, 0x04102000, 0x04002008, 0x04102008,
+ 0x04002200, 0x04102200, 0x04002208, 0x04102208,
+ 0x00000000, 0x00100000, 0x00000008, 0x00100008,
+ 0x00000200, 0x00100200, 0x00000208, 0x00100208,
+ 0x00000000, 0x00100000, 0x00000008, 0x00100008,
+ 0x00000200, 0x00100200, 0x00000208, 0x00100208,
+ 0x04000000, 0x04100000, 0x04000008, 0x04100008,
+ 0x04000200, 0x04100200, 0x04000208, 0x04100208,
+ 0x04000000, 0x04100000, 0x04000008, 0x04100008,
+ 0x04000200, 0x04100200, 0x04000208, 0x04100208,
+ 0x00002000, 0x00102000, 0x00002008, 0x00102008,
+ 0x00002200, 0x00102200, 0x00002208, 0x00102208,
+ 0x00002000, 0x00102000, 0x00002008, 0x00102008,
+ 0x00002200, 0x00102200, 0x00002208, 0x00102208,
+ 0x04002000, 0x04102000, 0x04002008, 0x04102008,
+ 0x04002200, 0x04102200, 0x04002208, 0x04102208,
+ 0x04002000, 0x04102000, 0x04002008, 0x04102008,
+ 0x04002200, 0x04102200, 0x04002208, 0x04102208,
+ 0x00020000, 0x00120000, 0x00020008, 0x00120008,
+ 0x00020200, 0x00120200, 0x00020208, 0x00120208,
+ 0x00020000, 0x00120000, 0x00020008, 0x00120008,
+ 0x00020200, 0x00120200, 0x00020208, 0x00120208,
+ 0x04020000, 0x04120000, 0x04020008, 0x04120008,
+ 0x04020200, 0x04120200, 0x04020208, 0x04120208,
+ 0x04020000, 0x04120000, 0x04020008, 0x04120008,
+ 0x04020200, 0x04120200, 0x04020208, 0x04120208,
+ 0x00022000, 0x00122000, 0x00022008, 0x00122008,
+ 0x00022200, 0x00122200, 0x00022208, 0x00122208,
+ 0x00022000, 0x00122000, 0x00022008, 0x00122008,
+ 0x00022200, 0x00122200, 0x00022208, 0x00122208,
+ 0x04022000, 0x04122000, 0x04022008, 0x04122008,
+ 0x04022200, 0x04122200, 0x04022208, 0x04122208,
+ 0x04022000, 0x04122000, 0x04022008, 0x04122008,
+ 0x04022200, 0x04122200, 0x04022208, 0x04122208,
+ 0x00020000, 0x00120000, 0x00020008, 0x00120008,
+ 0x00020200, 0x00120200, 0x00020208, 0x00120208,
+ 0x00020000, 0x00120000, 0x00020008, 0x00120008,
+ 0x00020200, 0x00120200, 0x00020208, 0x00120208,
+ 0x04020000, 0x04120000, 0x04020008, 0x04120008,
+ 0x04020200, 0x04120200, 0x04020208, 0x04120208,
+ 0x04020000, 0x04120000, 0x04020008, 0x04120008,
+ 0x04020200, 0x04120200, 0x04020208, 0x04120208,
+ 0x00022000, 0x00122000, 0x00022008, 0x00122008,
+ 0x00022200, 0x00122200, 0x00022208, 0x00122208,
+ 0x00022000, 0x00122000, 0x00022008, 0x00122008,
+ 0x00022200, 0x00122200, 0x00022208, 0x00122208,
+ 0x04022000, 0x04122000, 0x04022008, 0x04122008,
+ 0x04022200, 0x04122200, 0x04022208, 0x04122208,
+ 0x04022000, 0x04122000, 0x04022008, 0x04122008,
+ 0x04022200, 0x04122200, 0x04022208, 0x04122208
+ ];
+ static $pc2mapd1 = [
+ 0x00000000, 0x00000001, 0x08000000, 0x08000001,
+ 0x00200000, 0x00200001, 0x08200000, 0x08200001,
+ 0x00000002, 0x00000003, 0x08000002, 0x08000003,
+ 0x00200002, 0x00200003, 0x08200002, 0x08200003
+ ];
+ static $pc2mapd2 = [
+ 0x00000000, 0x00100000, 0x00000800, 0x00100800,
+ 0x00000000, 0x00100000, 0x00000800, 0x00100800,
+ 0x04000000, 0x04100000, 0x04000800, 0x04100800,
+ 0x04000000, 0x04100000, 0x04000800, 0x04100800,
+ 0x00000004, 0x00100004, 0x00000804, 0x00100804,
+ 0x00000004, 0x00100004, 0x00000804, 0x00100804,
+ 0x04000004, 0x04100004, 0x04000804, 0x04100804,
+ 0x04000004, 0x04100004, 0x04000804, 0x04100804,
+ 0x00000000, 0x00100000, 0x00000800, 0x00100800,
+ 0x00000000, 0x00100000, 0x00000800, 0x00100800,
+ 0x04000000, 0x04100000, 0x04000800, 0x04100800,
+ 0x04000000, 0x04100000, 0x04000800, 0x04100800,
+ 0x00000004, 0x00100004, 0x00000804, 0x00100804,
+ 0x00000004, 0x00100004, 0x00000804, 0x00100804,
+ 0x04000004, 0x04100004, 0x04000804, 0x04100804,
+ 0x04000004, 0x04100004, 0x04000804, 0x04100804,
+ 0x00000200, 0x00100200, 0x00000A00, 0x00100A00,
+ 0x00000200, 0x00100200, 0x00000A00, 0x00100A00,
+ 0x04000200, 0x04100200, 0x04000A00, 0x04100A00,
+ 0x04000200, 0x04100200, 0x04000A00, 0x04100A00,
+ 0x00000204, 0x00100204, 0x00000A04, 0x00100A04,
+ 0x00000204, 0x00100204, 0x00000A04, 0x00100A04,
+ 0x04000204, 0x04100204, 0x04000A04, 0x04100A04,
+ 0x04000204, 0x04100204, 0x04000A04, 0x04100A04,
+ 0x00000200, 0x00100200, 0x00000A00, 0x00100A00,
+ 0x00000200, 0x00100200, 0x00000A00, 0x00100A00,
+ 0x04000200, 0x04100200, 0x04000A00, 0x04100A00,
+ 0x04000200, 0x04100200, 0x04000A00, 0x04100A00,
+ 0x00000204, 0x00100204, 0x00000A04, 0x00100A04,
+ 0x00000204, 0x00100204, 0x00000A04, 0x00100A04,
+ 0x04000204, 0x04100204, 0x04000A04, 0x04100A04,
+ 0x04000204, 0x04100204, 0x04000A04, 0x04100A04,
+ 0x00020000, 0x00120000, 0x00020800, 0x00120800,
+ 0x00020000, 0x00120000, 0x00020800, 0x00120800,
+ 0x04020000, 0x04120000, 0x04020800, 0x04120800,
+ 0x04020000, 0x04120000, 0x04020800, 0x04120800,
+ 0x00020004, 0x00120004, 0x00020804, 0x00120804,
+ 0x00020004, 0x00120004, 0x00020804, 0x00120804,
+ 0x04020004, 0x04120004, 0x04020804, 0x04120804,
+ 0x04020004, 0x04120004, 0x04020804, 0x04120804,
+ 0x00020000, 0x00120000, 0x00020800, 0x00120800,
+ 0x00020000, 0x00120000, 0x00020800, 0x00120800,
+ 0x04020000, 0x04120000, 0x04020800, 0x04120800,
+ 0x04020000, 0x04120000, 0x04020800, 0x04120800,
+ 0x00020004, 0x00120004, 0x00020804, 0x00120804,
+ 0x00020004, 0x00120004, 0x00020804, 0x00120804,
+ 0x04020004, 0x04120004, 0x04020804, 0x04120804,
+ 0x04020004, 0x04120004, 0x04020804, 0x04120804,
+ 0x00020200, 0x00120200, 0x00020A00, 0x00120A00,
+ 0x00020200, 0x00120200, 0x00020A00, 0x00120A00,
+ 0x04020200, 0x04120200, 0x04020A00, 0x04120A00,
+ 0x04020200, 0x04120200, 0x04020A00, 0x04120A00,
+ 0x00020204, 0x00120204, 0x00020A04, 0x00120A04,
+ 0x00020204, 0x00120204, 0x00020A04, 0x00120A04,
+ 0x04020204, 0x04120204, 0x04020A04, 0x04120A04,
+ 0x04020204, 0x04120204, 0x04020A04, 0x04120A04,
+ 0x00020200, 0x00120200, 0x00020A00, 0x00120A00,
+ 0x00020200, 0x00120200, 0x00020A00, 0x00120A00,
+ 0x04020200, 0x04120200, 0x04020A00, 0x04120A00,
+ 0x04020200, 0x04120200, 0x04020A00, 0x04120A00,
+ 0x00020204, 0x00120204, 0x00020A04, 0x00120A04,
+ 0x00020204, 0x00120204, 0x00020A04, 0x00120A04,
+ 0x04020204, 0x04120204, 0x04020A04, 0x04120A04,
+ 0x04020204, 0x04120204, 0x04020A04, 0x04120A04
+ ];
+ static $pc2mapd3 = [
+ 0x00000000, 0x00010000, 0x02000000, 0x02010000,
+ 0x00000020, 0x00010020, 0x02000020, 0x02010020,
+ 0x00040000, 0x00050000, 0x02040000, 0x02050000,
+ 0x00040020, 0x00050020, 0x02040020, 0x02050020,
+ 0x00002000, 0x00012000, 0x02002000, 0x02012000,
+ 0x00002020, 0x00012020, 0x02002020, 0x02012020,
+ 0x00042000, 0x00052000, 0x02042000, 0x02052000,
+ 0x00042020, 0x00052020, 0x02042020, 0x02052020,
+ 0x00000000, 0x00010000, 0x02000000, 0x02010000,
+ 0x00000020, 0x00010020, 0x02000020, 0x02010020,
+ 0x00040000, 0x00050000, 0x02040000, 0x02050000,
+ 0x00040020, 0x00050020, 0x02040020, 0x02050020,
+ 0x00002000, 0x00012000, 0x02002000, 0x02012000,
+ 0x00002020, 0x00012020, 0x02002020, 0x02012020,
+ 0x00042000, 0x00052000, 0x02042000, 0x02052000,
+ 0x00042020, 0x00052020, 0x02042020, 0x02052020,
+ 0x00000010, 0x00010010, 0x02000010, 0x02010010,
+ 0x00000030, 0x00010030, 0x02000030, 0x02010030,
+ 0x00040010, 0x00050010, 0x02040010, 0x02050010,
+ 0x00040030, 0x00050030, 0x02040030, 0x02050030,
+ 0x00002010, 0x00012010, 0x02002010, 0x02012010,
+ 0x00002030, 0x00012030, 0x02002030, 0x02012030,
+ 0x00042010, 0x00052010, 0x02042010, 0x02052010,
+ 0x00042030, 0x00052030, 0x02042030, 0x02052030,
+ 0x00000010, 0x00010010, 0x02000010, 0x02010010,
+ 0x00000030, 0x00010030, 0x02000030, 0x02010030,
+ 0x00040010, 0x00050010, 0x02040010, 0x02050010,
+ 0x00040030, 0x00050030, 0x02040030, 0x02050030,
+ 0x00002010, 0x00012010, 0x02002010, 0x02012010,
+ 0x00002030, 0x00012030, 0x02002030, 0x02012030,
+ 0x00042010, 0x00052010, 0x02042010, 0x02052010,
+ 0x00042030, 0x00052030, 0x02042030, 0x02052030,
+ 0x20000000, 0x20010000, 0x22000000, 0x22010000,
+ 0x20000020, 0x20010020, 0x22000020, 0x22010020,
+ 0x20040000, 0x20050000, 0x22040000, 0x22050000,
+ 0x20040020, 0x20050020, 0x22040020, 0x22050020,
+ 0x20002000, 0x20012000, 0x22002000, 0x22012000,
+ 0x20002020, 0x20012020, 0x22002020, 0x22012020,
+ 0x20042000, 0x20052000, 0x22042000, 0x22052000,
+ 0x20042020, 0x20052020, 0x22042020, 0x22052020,
+ 0x20000000, 0x20010000, 0x22000000, 0x22010000,
+ 0x20000020, 0x20010020, 0x22000020, 0x22010020,
+ 0x20040000, 0x20050000, 0x22040000, 0x22050000,
+ 0x20040020, 0x20050020, 0x22040020, 0x22050020,
+ 0x20002000, 0x20012000, 0x22002000, 0x22012000,
+ 0x20002020, 0x20012020, 0x22002020, 0x22012020,
+ 0x20042000, 0x20052000, 0x22042000, 0x22052000,
+ 0x20042020, 0x20052020, 0x22042020, 0x22052020,
+ 0x20000010, 0x20010010, 0x22000010, 0x22010010,
+ 0x20000030, 0x20010030, 0x22000030, 0x22010030,
+ 0x20040010, 0x20050010, 0x22040010, 0x22050010,
+ 0x20040030, 0x20050030, 0x22040030, 0x22050030,
+ 0x20002010, 0x20012010, 0x22002010, 0x22012010,
+ 0x20002030, 0x20012030, 0x22002030, 0x22012030,
+ 0x20042010, 0x20052010, 0x22042010, 0x22052010,
+ 0x20042030, 0x20052030, 0x22042030, 0x22052030,
+ 0x20000010, 0x20010010, 0x22000010, 0x22010010,
+ 0x20000030, 0x20010030, 0x22000030, 0x22010030,
+ 0x20040010, 0x20050010, 0x22040010, 0x22050010,
+ 0x20040030, 0x20050030, 0x22040030, 0x22050030,
+ 0x20002010, 0x20012010, 0x22002010, 0x22012010,
+ 0x20002030, 0x20012030, 0x22002030, 0x22012030,
+ 0x20042010, 0x20052010, 0x22042010, 0x22052010,
+ 0x20042030, 0x20052030, 0x22042030, 0x22052030
+ ];
+ static $pc2mapd4 = [
+ 0x00000000, 0x00000400, 0x01000000, 0x01000400,
+ 0x00000000, 0x00000400, 0x01000000, 0x01000400,
+ 0x00000100, 0x00000500, 0x01000100, 0x01000500,
+ 0x00000100, 0x00000500, 0x01000100, 0x01000500,
+ 0x10000000, 0x10000400, 0x11000000, 0x11000400,
+ 0x10000000, 0x10000400, 0x11000000, 0x11000400,
+ 0x10000100, 0x10000500, 0x11000100, 0x11000500,
+ 0x10000100, 0x10000500, 0x11000100, 0x11000500,
+ 0x00080000, 0x00080400, 0x01080000, 0x01080400,
+ 0x00080000, 0x00080400, 0x01080000, 0x01080400,
+ 0x00080100, 0x00080500, 0x01080100, 0x01080500,
+ 0x00080100, 0x00080500, 0x01080100, 0x01080500,
+ 0x10080000, 0x10080400, 0x11080000, 0x11080400,
+ 0x10080000, 0x10080400, 0x11080000, 0x11080400,
+ 0x10080100, 0x10080500, 0x11080100, 0x11080500,
+ 0x10080100, 0x10080500, 0x11080100, 0x11080500,
+ 0x00000008, 0x00000408, 0x01000008, 0x01000408,
+ 0x00000008, 0x00000408, 0x01000008, 0x01000408,
+ 0x00000108, 0x00000508, 0x01000108, 0x01000508,
+ 0x00000108, 0x00000508, 0x01000108, 0x01000508,
+ 0x10000008, 0x10000408, 0x11000008, 0x11000408,
+ 0x10000008, 0x10000408, 0x11000008, 0x11000408,
+ 0x10000108, 0x10000508, 0x11000108, 0x11000508,
+ 0x10000108, 0x10000508, 0x11000108, 0x11000508,
+ 0x00080008, 0x00080408, 0x01080008, 0x01080408,
+ 0x00080008, 0x00080408, 0x01080008, 0x01080408,
+ 0x00080108, 0x00080508, 0x01080108, 0x01080508,
+ 0x00080108, 0x00080508, 0x01080108, 0x01080508,
+ 0x10080008, 0x10080408, 0x11080008, 0x11080408,
+ 0x10080008, 0x10080408, 0x11080008, 0x11080408,
+ 0x10080108, 0x10080508, 0x11080108, 0x11080508,
+ 0x10080108, 0x10080508, 0x11080108, 0x11080508,
+ 0x00001000, 0x00001400, 0x01001000, 0x01001400,
+ 0x00001000, 0x00001400, 0x01001000, 0x01001400,
+ 0x00001100, 0x00001500, 0x01001100, 0x01001500,
+ 0x00001100, 0x00001500, 0x01001100, 0x01001500,
+ 0x10001000, 0x10001400, 0x11001000, 0x11001400,
+ 0x10001000, 0x10001400, 0x11001000, 0x11001400,
+ 0x10001100, 0x10001500, 0x11001100, 0x11001500,
+ 0x10001100, 0x10001500, 0x11001100, 0x11001500,
+ 0x00081000, 0x00081400, 0x01081000, 0x01081400,
+ 0x00081000, 0x00081400, 0x01081000, 0x01081400,
+ 0x00081100, 0x00081500, 0x01081100, 0x01081500,
+ 0x00081100, 0x00081500, 0x01081100, 0x01081500,
+ 0x10081000, 0x10081400, 0x11081000, 0x11081400,
+ 0x10081000, 0x10081400, 0x11081000, 0x11081400,
+ 0x10081100, 0x10081500, 0x11081100, 0x11081500,
+ 0x10081100, 0x10081500, 0x11081100, 0x11081500,
+ 0x00001008, 0x00001408, 0x01001008, 0x01001408,
+ 0x00001008, 0x00001408, 0x01001008, 0x01001408,
+ 0x00001108, 0x00001508, 0x01001108, 0x01001508,
+ 0x00001108, 0x00001508, 0x01001108, 0x01001508,
+ 0x10001008, 0x10001408, 0x11001008, 0x11001408,
+ 0x10001008, 0x10001408, 0x11001008, 0x11001408,
+ 0x10001108, 0x10001508, 0x11001108, 0x11001508,
+ 0x10001108, 0x10001508, 0x11001108, 0x11001508,
+ 0x00081008, 0x00081408, 0x01081008, 0x01081408,
+ 0x00081008, 0x00081408, 0x01081008, 0x01081408,
+ 0x00081108, 0x00081508, 0x01081108, 0x01081508,
+ 0x00081108, 0x00081508, 0x01081108, 0x01081508,
+ 0x10081008, 0x10081408, 0x11081008, 0x11081408,
+ 0x10081008, 0x10081408, 0x11081008, 0x11081408,
+ 0x10081108, 0x10081508, 0x11081108, 0x11081508,
+ 0x10081108, 0x10081508, 0x11081108, 0x11081508
+ ];
+
+ $keys = [];
+ for ($des_round = 0; $des_round < $this->des_rounds; ++$des_round) {
+ // pad the key and remove extra characters as appropriate.
+ $key = str_pad(substr($this->key, $des_round * 8, 8), 8, "\0");
+
+ // Perform the PC/1 transformation and compute C and D.
+ $t = unpack('Nl/Nr', $key);
+ list($l, $r) = [$t['l'], $t['r']];
+ $key = (self::$shuffle[$pc1map[ $r & 0xFF]] & "\x80\x80\x80\x80\x80\x80\x80\x00") |
+ (self::$shuffle[$pc1map[($r >> 8) & 0xFF]] & "\x40\x40\x40\x40\x40\x40\x40\x00") |
+ (self::$shuffle[$pc1map[($r >> 16) & 0xFF]] & "\x20\x20\x20\x20\x20\x20\x20\x00") |
+ (self::$shuffle[$pc1map[($r >> 24) & 0xFF]] & "\x10\x10\x10\x10\x10\x10\x10\x00") |
+ (self::$shuffle[$pc1map[ $l & 0xFF]] & "\x08\x08\x08\x08\x08\x08\x08\x00") |
+ (self::$shuffle[$pc1map[($l >> 8) & 0xFF]] & "\x04\x04\x04\x04\x04\x04\x04\x00") |
+ (self::$shuffle[$pc1map[($l >> 16) & 0xFF]] & "\x02\x02\x02\x02\x02\x02\x02\x00") |
+ (self::$shuffle[$pc1map[($l >> 24) & 0xFF]] & "\x01\x01\x01\x01\x01\x01\x01\x00");
+ $key = unpack('Nc/Nd', $key);
+ $c = ( $key['c'] >> 4) & 0x0FFFFFFF;
+ $d = (($key['d'] >> 4) & 0x0FFFFFF0) | ($key['c'] & 0x0F);
+
+ $keys[$des_round] = [
+ self::ENCRYPT => [],
+ self::DECRYPT => array_fill(0, 32, 0)
+ ];
+ for ($i = 0, $ki = 31; $i < 16; ++$i, $ki -= 2) {
+ $c <<= $shifts[$i];
+ $c = ($c | ($c >> 28)) & 0x0FFFFFFF;
+ $d <<= $shifts[$i];
+ $d = ($d | ($d >> 28)) & 0x0FFFFFFF;
+
+ // Perform the PC-2 transformation.
+ $cp = $pc2mapc1[ $c >> 24 ] | $pc2mapc2[($c >> 16) & 0xFF] |
+ $pc2mapc3[($c >> 8) & 0xFF] | $pc2mapc4[ $c & 0xFF];
+ $dp = $pc2mapd1[ $d >> 24 ] | $pc2mapd2[($d >> 16) & 0xFF] |
+ $pc2mapd3[($d >> 8) & 0xFF] | $pc2mapd4[ $d & 0xFF];
+
+ // Reorder: odd bytes/even bytes. Push the result in key schedule.
+ $val1 = ( $cp & intval(0xFF000000)) | (($cp << 8) & 0x00FF0000) |
+ (($dp >> 16) & 0x0000FF00) | (($dp >> 8) & 0x000000FF);
+ $val2 = (($cp << 8) & intval(0xFF000000)) | (($cp << 16) & 0x00FF0000) |
+ (($dp >> 8) & 0x0000FF00) | ( $dp & 0x000000FF);
+ $keys[$des_round][self::ENCRYPT][ ] = $val1;
+ $keys[$des_round][self::DECRYPT][$ki - 1] = $val1;
+ $keys[$des_round][self::ENCRYPT][ ] = $val2;
+ $keys[$des_round][self::DECRYPT][$ki ] = $val2;
+ }
+ }
+
+ switch ($this->des_rounds) {
+ case 3: // 3DES keys
+ $this->keys = [
+ self::ENCRYPT => array_merge(
+ $keys[0][self::ENCRYPT],
+ $keys[1][self::DECRYPT],
+ $keys[2][self::ENCRYPT]
+ ),
+ self::DECRYPT => array_merge(
+ $keys[2][self::DECRYPT],
+ $keys[1][self::ENCRYPT],
+ $keys[0][self::DECRYPT]
+ )
+ ];
+ break;
+ // case 1: // DES keys
+ default:
+ $this->keys = [
+ self::ENCRYPT => $keys[0][self::ENCRYPT],
+ self::DECRYPT => $keys[0][self::DECRYPT]
+ ];
+ }
+ }
+
+ /**
+ * Setup the performance-optimized function for de/encrypt()
+ *
+ * @see Common\SymmetricKey::setupInlineCrypt()
+ */
+ protected function setupInlineCrypt()
+ {
+ // Engine configuration for:
+ // - DES ($des_rounds == 1) or
+ // - 3DES ($des_rounds == 3)
+ $des_rounds = $this->des_rounds;
+
+ $init_crypt = 'static $sbox1, $sbox2, $sbox3, $sbox4, $sbox5, $sbox6, $sbox7, $sbox8, $shuffleip, $shuffleinvip;
+ if (!$sbox1) {
+ $sbox1 = array_map("intval", self::$sbox1);
+ $sbox2 = array_map("intval", self::$sbox2);
+ $sbox3 = array_map("intval", self::$sbox3);
+ $sbox4 = array_map("intval", self::$sbox4);
+ $sbox5 = array_map("intval", self::$sbox5);
+ $sbox6 = array_map("intval", self::$sbox6);
+ $sbox7 = array_map("intval", self::$sbox7);
+ $sbox8 = array_map("intval", self::$sbox8);'
+ /* Merge $shuffle with $[inv]ipmap */ . '
+ for ($i = 0; $i < 256; ++$i) {
+ $shuffleip[] = self::$shuffle[self::$ipmap[$i]];
+ $shuffleinvip[] = self::$shuffle[self::$invipmap[$i]];
+ }
+ }
+ ';
+
+ $k = [
+ self::ENCRYPT => $this->keys[self::ENCRYPT],
+ self::DECRYPT => $this->keys[self::DECRYPT]
+ ];
+ $init_encrypt = '';
+ $init_decrypt = '';
+
+ // Creating code for en- and decryption.
+ $crypt_block = [];
+ foreach ([self::ENCRYPT, self::DECRYPT] as $c) {
+ /* Do the initial IP permutation. */
+ $crypt_block[$c] = '
+ $in = unpack("N*", $in);
+ $l = $in[1];
+ $r = $in[2];
+ $in = unpack("N*",
+ ($shuffleip[ $r & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
+ ($shuffleip[($r >> 8) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
+ ($shuffleip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
+ ($shuffleip[($r >> 24) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") |
+ ($shuffleip[ $l & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") |
+ ($shuffleip[($l >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") |
+ ($shuffleip[($l >> 16) & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") |
+ ($shuffleip[($l >> 24) & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01")
+ );
+ ' . /* Extract L0 and R0 */ '
+ $l = $in[1];
+ $r = $in[2];
+ ';
+
+ $l = '$l';
+ $r = '$r';
+
+ // Perform DES or 3DES.
+ for ($ki = -1, $des_round = 0; $des_round < $des_rounds; ++$des_round) {
+ // Perform the 16 steps.
+ for ($i = 0; $i < 16; ++$i) {
+ // start of "the Feistel (F) function" - see the following URL:
+ // http://en.wikipedia.org/wiki/Image:Data_Encryption_Standard_InfoBox_Diagram.png
+ // Merge key schedule.
+ $crypt_block[$c] .= '
+ $b1 = ((' . $r . ' >> 3) & 0x1FFFFFFF) ^ (' . $r . ' << 29) ^ ' . $k[$c][++$ki] . ';
+ $b2 = ((' . $r . ' >> 31) & 0x00000001) ^ (' . $r . ' << 1) ^ ' . $k[$c][++$ki] . ';' .
+ /* S-box indexing. */
+ $l . ' = $sbox1[($b1 >> 24) & 0x3F] ^ $sbox2[($b2 >> 24) & 0x3F] ^
+ $sbox3[($b1 >> 16) & 0x3F] ^ $sbox4[($b2 >> 16) & 0x3F] ^
+ $sbox5[($b1 >> 8) & 0x3F] ^ $sbox6[($b2 >> 8) & 0x3F] ^
+ $sbox7[ $b1 & 0x3F] ^ $sbox8[ $b2 & 0x3F] ^ ' . $l . ';
+ ';
+ // end of "the Feistel (F) function"
+
+ // swap L & R
+ list($l, $r) = [$r, $l];
+ }
+ list($l, $r) = [$r, $l];
+ }
+
+ // Perform the inverse IP permutation.
+ $crypt_block[$c] .= '$in =
+ ($shuffleinvip[($l >> 24) & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
+ ($shuffleinvip[($r >> 24) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
+ ($shuffleinvip[($l >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
+ ($shuffleinvip[($r >> 16) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") |
+ ($shuffleinvip[($l >> 8) & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") |
+ ($shuffleinvip[($r >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") |
+ ($shuffleinvip[ $l & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") |
+ ($shuffleinvip[ $r & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01");
+ ';
+ }
+
+ // Creates the inline-crypt function
+ $this->inline_crypt = $this->createInlineCryptFunction(
+ [
+ 'init_crypt' => $init_crypt,
+ 'init_encrypt' => $init_encrypt,
+ 'init_decrypt' => $init_decrypt,
+ 'encrypt_block' => $crypt_block[self::ENCRYPT],
+ 'decrypt_block' => $crypt_block[self::DECRYPT]
+ ]
+ );
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DH.php b/Sources/Phpseclib/Crypt/DH.php
new file mode 100644
index 0000000000..b2301986f6
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DH.php
@@ -0,0 +1,405 @@
+
+ *
+ *
+ *
+ * @author Jim Wigginton
+ * @copyright 2016 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\AsymmetricKey;
+use phpseclib3\Crypt\DH\Parameters;
+use phpseclib3\Crypt\DH\PrivateKey;
+use phpseclib3\Crypt\DH\PublicKey;
+use phpseclib3\Exception\NoKeyLoadedException;
+use phpseclib3\Exception\UnsupportedOperationException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Pure-PHP (EC)DH implementation
+ *
+ * @author Jim Wigginton
+ */
+abstract class DH extends AsymmetricKey
+{
+ /**
+ * Algorithm Name
+ *
+ * @var string
+ */
+ const ALGORITHM = 'DH';
+
+ /**
+ * DH prime
+ *
+ * @var BigInteger
+ */
+ protected $prime;
+
+ /**
+ * DH Base
+ *
+ * Prime divisor of p-1
+ *
+ * @var BigInteger
+ */
+ protected $base;
+
+ /**
+ * Public Key
+ *
+ * @var BigInteger
+ */
+ protected $publicKey;
+
+ /**
+ * Create DH parameters
+ *
+ * This method is a bit polymorphic. It can take any of the following:
+ * - two BigInteger's (prime and base)
+ * - an integer representing the size of the prime in bits (the base is assumed to be 2)
+ * - a string (eg. diffie-hellman-group14-sha1)
+ *
+ * @return Parameters
+ */
+ public static function createParameters(...$args)
+ {
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('createParameters() should not be called from final classes (' . static::class . ')');
+ }
+
+ $params = new Parameters();
+ if (count($args) == 2 && $args[0] instanceof BigInteger && $args[1] instanceof BigInteger) {
+ //if (!$args[0]->isPrime()) {
+ // throw new \InvalidArgumentException('The first parameter should be a prime number');
+ //}
+ $params->prime = $args[0];
+ $params->base = $args[1];
+ return $params;
+ } elseif (count($args) == 1 && is_numeric($args[0])) {
+ $params->prime = BigInteger::randomPrime($args[0]);
+ $params->base = new BigInteger(2);
+ return $params;
+ } elseif (count($args) != 1 || !is_string($args[0])) {
+ throw new \InvalidArgumentException('Valid parameters are either: two BigInteger\'s (prime and base), a single integer (the length of the prime; base is assumed to be 2) or a string');
+ }
+ switch ($args[0]) {
+ // see http://tools.ietf.org/html/rfc2409#section-6.2 and
+ // http://tools.ietf.org/html/rfc2412, appendex E
+ case 'diffie-hellman-group1-sha1':
+ $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
+ break;
+ // see http://tools.ietf.org/html/rfc3526#section-3
+ case 'diffie-hellman-group14-sha1': // 2048-bit MODP Group
+ case 'diffie-hellman-group14-sha256':
+ $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
+ '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
+ '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
+ 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
+ '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
+ break;
+ // see https://tools.ietf.org/html/rfc3526#section-4
+ case 'diffie-hellman-group15-sha512': // 3072-bit MODP Group
+ $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
+ '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
+ '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
+ 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
+ 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
+ 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
+ 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
+ '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF';
+ break;
+ // see https://tools.ietf.org/html/rfc3526#section-5
+ case 'diffie-hellman-group16-sha512': // 4096-bit MODP Group
+ $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
+ '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
+ '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
+ 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
+ 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
+ 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
+ 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
+ '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
+ '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
+ 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
+ '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
+ '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF';
+ break;
+ // see https://tools.ietf.org/html/rfc3526#section-6
+ case 'diffie-hellman-group17-sha512': // 6144-bit MODP Group
+ $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
+ '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
+ '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
+ 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
+ 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
+ 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
+ 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
+ '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
+ '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
+ 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
+ '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
+ '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' .
+ 'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' .
+ 'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' .
+ 'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' .
+ 'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' .
+ '59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' .
+ 'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' .
+ 'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' .
+ '043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF';
+ break;
+ // see https://tools.ietf.org/html/rfc3526#section-7
+ case 'diffie-hellman-group18-sha512': // 8192-bit MODP Group
+ $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
+ '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
+ '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
+ 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
+ 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
+ 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
+ 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
+ '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
+ '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
+ 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
+ '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
+ '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' .
+ 'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' .
+ 'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' .
+ 'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' .
+ 'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' .
+ '59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' .
+ 'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' .
+ 'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' .
+ '043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4' .
+ '38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED' .
+ '2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652D' .
+ 'E3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B' .
+ '4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6' .
+ '6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851D' .
+ 'F9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92' .
+ '4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA' .
+ '9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF';
+ break;
+ default:
+ throw new \InvalidArgumentException('Invalid named prime provided');
+ }
+
+ $params->prime = new BigInteger($prime, 16);
+ $params->base = new BigInteger(2);
+
+ return $params;
+ }
+
+ /**
+ * Create public / private key pair.
+ *
+ * The rationale for the second parameter is described in http://tools.ietf.org/html/rfc4419#section-6.2 :
+ *
+ * "To increase the speed of the key exchange, both client and server may
+ * reduce the size of their private exponents. It should be at least
+ * twice as long as the key material that is generated from the shared
+ * secret. For more details, see the paper by van Oorschot and Wiener
+ * [VAN-OORSCHOT]."
+ *
+ * $length is in bits
+ *
+ * @param Parameters $params
+ * @param int $length optional
+ * @return PrivateKey
+ */
+ public static function createKey(Parameters $params, $length = 0)
+ {
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
+ }
+
+ $one = new BigInteger(1);
+ if ($length) {
+ $max = $one->bitwise_leftShift($length);
+ $max = $max->subtract($one);
+ } else {
+ $max = $params->prime->subtract($one);
+ }
+
+ $key = new PrivateKey();
+ $key->prime = $params->prime;
+ $key->base = $params->base;
+ $key->privateKey = BigInteger::randomRange($one, $max);
+ $key->publicKey = $key->base->powMod($key->privateKey, $key->prime);
+ return $key;
+ }
+
+ /**
+ * Compute Shared Secret
+ *
+ * @param PrivateKey|EC $private
+ * @param PublicKey|BigInteger|string $public
+ * @return mixed
+ */
+ public static function computeSecret($private, $public)
+ {
+ if ($private instanceof PrivateKey) { // DH\PrivateKey
+ switch (true) {
+ case $public instanceof PublicKey:
+ if (!$private->prime->equals($public->prime) || !$private->base->equals($public->base)) {
+ throw new \InvalidArgumentException('The public and private key do not share the same prime and / or base numbers');
+ }
+ return $public->publicKey->powMod($private->privateKey, $private->prime)->toBytes(true);
+ case is_string($public):
+ $public = new BigInteger($public, -256);
+ // fall-through
+ case $public instanceof BigInteger:
+ return $public->powMod($private->privateKey, $private->prime)->toBytes(true);
+ default:
+ throw new \InvalidArgumentException('$public needs to be an instance of DH\PublicKey, a BigInteger or a string');
+ }
+ }
+
+ if ($private instanceof EC\PrivateKey) {
+ switch (true) {
+ case $public instanceof EC\PublicKey:
+ $public = $public->getEncodedCoordinates();
+ // fall-through
+ case is_string($public):
+ $point = $private->multiply($public);
+ switch ($private->getCurve()) {
+ case 'Curve25519':
+ case 'Curve448':
+ $secret = $point;
+ break;
+ default:
+ // according to https://www.secg.org/sec1-v2.pdf#page=33 only X is returned
+ $secret = substr($point, 1, (strlen($point) - 1) >> 1);
+ }
+ /*
+ if (($secret[0] & "\x80") === "\x80") {
+ $secret = "\0$secret";
+ }
+ */
+ return $secret;
+ default:
+ throw new \InvalidArgumentException('$public needs to be an instance of EC\PublicKey or a string (an encoded coordinate)');
+ }
+ }
+ }
+
+ /**
+ * Load the key
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return AsymmetricKey
+ */
+ public static function load($key, $password = false)
+ {
+ try {
+ return EC::load($key, $password);
+ } catch (NoKeyLoadedException $e) {
+ }
+
+ return parent::load($key, $password);
+ }
+
+ /**
+ * OnLoad Handler
+ *
+ * @return bool
+ */
+ protected static function onLoad(array $components)
+ {
+ if (!isset($components['privateKey']) && !isset($components['publicKey'])) {
+ $new = new Parameters();
+ } else {
+ $new = isset($components['privateKey']) ?
+ new PrivateKey() :
+ new PublicKey();
+ }
+
+ $new->prime = $components['prime'];
+ $new->base = $components['base'];
+
+ if (isset($components['privateKey'])) {
+ $new->privateKey = $components['privateKey'];
+ }
+ if (isset($components['publicKey'])) {
+ $new->publicKey = $components['publicKey'];
+ }
+
+ return $new;
+ }
+
+ /**
+ * Determines which hashing function should be used
+ *
+ * @param string $hash
+ */
+ public function withHash($hash)
+ {
+ throw new UnsupportedOperationException('DH does not use a hash algorithm');
+ }
+
+ /**
+ * Returns the hash algorithm currently being used
+ *
+ */
+ public function getHash()
+ {
+ throw new UnsupportedOperationException('DH does not use a hash algorithm');
+ }
+
+ /**
+ * Returns the parameters
+ *
+ * A public / private key is only returned if the currently loaded "key" contains an x or y
+ * value.
+ *
+ * @see self::getPublicKey()
+ * @return mixed
+ */
+ public function getParameters()
+ {
+ $type = DH::validatePlugin('Keys', 'PKCS1', 'saveParameters');
+
+ $key = $type::saveParameters($this->prime, $this->base);
+ return DH::load($key, 'PKCS1');
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DH/Formats/Keys/PKCS1.php b/Sources/Phpseclib/Crypt/DH/Formats/Keys/PKCS1.php
new file mode 100644
index 0000000000..65a0a5dbc6
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DH/Formats/Keys/PKCS1.php
@@ -0,0 +1,77 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DH\Formats\Keys;
+
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * "PKCS1" Formatted DH Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class PKCS1 extends Progenitor
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ $decoded = ASN1::decodeBER($key);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ $components = ASN1::asn1map($decoded[0], Maps\DHParameter::MAP);
+ if (!is_array($components)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping on parameters');
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert EC parameters to the appropriate format
+ *
+ * @return string
+ */
+ public static function saveParameters(BigInteger $prime, BigInteger $base, array $options = [])
+ {
+ $params = [
+ 'prime' => $prime,
+ 'base' => $base
+ ];
+ $params = ASN1::encodeDER($params, Maps\DHParameter::MAP);
+
+ return "-----BEGIN DH PARAMETERS-----\r\n" .
+ chunk_split(base64_encode($params), 64) .
+ "-----END DH PARAMETERS-----\r\n";
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DH/Formats/Keys/PKCS8.php b/Sources/Phpseclib/Crypt/DH/Formats/Keys/PKCS8.php
new file mode 100644
index 0000000000..3b83a42900
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DH/Formats/Keys/PKCS8.php
@@ -0,0 +1,132 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DH\Formats\Keys;
+
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#8 Formatted DH Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class PKCS8 extends Progenitor
+{
+ /**
+ * OID Name
+ *
+ * @var string
+ */
+ const OID_NAME = 'dhKeyAgreement';
+
+ /**
+ * OID Value
+ *
+ * @var string
+ */
+ const OID_VALUE = '1.2.840.113549.1.3.1';
+
+ /**
+ * Child OIDs loaded
+ *
+ * @var bool
+ */
+ protected static $childOIDsLoaded = false;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ $type = isset($key['privateKey']) ? 'privateKey' : 'publicKey';
+
+ $decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element);
+ if (empty($decoded)) {
+ throw new \RuntimeException('Unable to decode BER of parameters');
+ }
+ $components = ASN1::asn1map($decoded[0], Maps\DHParameter::MAP);
+ if (!is_array($components)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping on parameters');
+ }
+
+ $decoded = ASN1::decodeBER($key[$type]);
+ switch (true) {
+ case !isset($decoded):
+ case !isset($decoded[0]['content']):
+ case !$decoded[0]['content'] instanceof BigInteger:
+ throw new \RuntimeException('Unable to decode BER of parameters');
+ }
+ $components[$type] = $decoded[0]['content'];
+
+ return $components;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $prime
+ * @param BigInteger $base
+ * @param BigInteger $privateKey
+ * @param BigInteger $publicKey
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $prime, BigInteger $base, BigInteger $privateKey, BigInteger $publicKey, $password = '', array $options = [])
+ {
+ $params = [
+ 'prime' => $prime,
+ 'base' => $base
+ ];
+ $params = ASN1::encodeDER($params, Maps\DHParameter::MAP);
+ $params = new ASN1\Element($params);
+ $key = ASN1::encodeDER($privateKey, ['type' => ASN1::TYPE_INTEGER]);
+ return self::wrapPrivateKey($key, [], $params, $password, null, '', $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $prime
+ * @param BigInteger $base
+ * @param BigInteger $publicKey
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $prime, BigInteger $base, BigInteger $publicKey, array $options = [])
+ {
+ $params = [
+ 'prime' => $prime,
+ 'base' => $base
+ ];
+ $params = ASN1::encodeDER($params, Maps\DHParameter::MAP);
+ $params = new ASN1\Element($params);
+ $key = ASN1::encodeDER($publicKey, ['type' => ASN1::TYPE_INTEGER]);
+ return self::wrapPublicKey($key, $params, null, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DH/Formats/Keys/index.php b/Sources/Phpseclib/Crypt/DH/Formats/Keys/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DH/Formats/Keys/index.php
@@ -0,0 +1,8 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DH;
+
+use phpseclib3\Crypt\DH;
+
+/**
+ * DH Parameters
+ *
+ * @author Jim Wigginton
+ */
+final class Parameters extends DH
+{
+ /**
+ * Returns the parameters
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type = 'PKCS1', array $options = [])
+ {
+ $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
+
+ return $type::saveParameters($this->prime, $this->base, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DH/PrivateKey.php b/Sources/Phpseclib/Crypt/DH/PrivateKey.php
new file mode 100644
index 0000000000..e2407e35e3
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DH/PrivateKey.php
@@ -0,0 +1,75 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DH;
+
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\DH;
+
+/**
+ * DH Private Key
+ *
+ * @author Jim Wigginton
+ */
+final class PrivateKey extends DH
+{
+ use Common\Traits\PasswordProtected;
+
+ /**
+ * Private Key
+ *
+ * @var \phpseclib3\Math\BigInteger
+ */
+ protected $privateKey;
+
+ /**
+ * Public Key
+ *
+ * @var \phpseclib3\Math\BigInteger
+ */
+ protected $publicKey;
+
+ /**
+ * Returns the public key
+ *
+ * @return PublicKey
+ */
+ public function getPublicKey()
+ {
+ $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey');
+
+ if (!isset($this->publicKey)) {
+ $this->publicKey = $this->base->powMod($this->privateKey, $this->prime);
+ }
+
+ $key = $type::savePublicKey($this->prime, $this->base, $this->publicKey);
+
+ return DH::loadFormat('PKCS8', $key);
+ }
+
+ /**
+ * Returns the private key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePrivateKey');
+
+ if (!isset($this->publicKey)) {
+ $this->publicKey = $this->base->powMod($this->privateKey, $this->prime);
+ }
+
+ return $type::savePrivateKey($this->prime, $this->base, $this->privateKey, $this->publicKey, $this->password, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DH/PublicKey.php b/Sources/Phpseclib/Crypt/DH/PublicKey.php
new file mode 100644
index 0000000000..87726a5a3e
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DH/PublicKey.php
@@ -0,0 +1,49 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DH;
+
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\DH;
+
+/**
+ * DH Public Key
+ *
+ * @author Jim Wigginton
+ */
+final class PublicKey extends DH
+{
+ use Common\Traits\Fingerprint;
+
+ /**
+ * Returns the public key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePublicKey');
+
+ return $type::savePublicKey($this->prime, $this->base, $this->publicKey, $options);
+ }
+
+ /**
+ * Returns the public key as a BigInteger
+ *
+ * @return \phpseclib3\Math\BigInteger
+ */
+ public function toBigInteger()
+ {
+ return $this->publicKey;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DH/index.php b/Sources/Phpseclib/Crypt/DH/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DH/index.php
@@ -0,0 +1,8 @@
+
+ * getPublicKey();
+ *
+ * $plaintext = 'terrafrost';
+ *
+ * $signature = $private->sign($plaintext);
+ *
+ * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
+ * ?>
+ *
+ *
+ * @author Jim Wigginton
+ * @copyright 2016 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\AsymmetricKey;
+use phpseclib3\Crypt\DSA\Parameters;
+use phpseclib3\Crypt\DSA\PrivateKey;
+use phpseclib3\Crypt\DSA\PublicKey;
+use phpseclib3\Exception\InsufficientSetupException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Pure-PHP FIPS 186-4 compliant implementation of DSA.
+ *
+ * @author Jim Wigginton
+ */
+abstract class DSA extends AsymmetricKey
+{
+ /**
+ * Algorithm Name
+ *
+ * @var string
+ */
+ const ALGORITHM = 'DSA';
+
+ /**
+ * DSA Prime P
+ *
+ * @var BigInteger
+ */
+ protected $p;
+
+ /**
+ * DSA Group Order q
+ *
+ * Prime divisor of p-1
+ *
+ * @var BigInteger
+ */
+ protected $q;
+
+ /**
+ * DSA Group Generator G
+ *
+ * @var BigInteger
+ */
+ protected $g;
+
+ /**
+ * DSA public key value y
+ *
+ * @var BigInteger
+ */
+ protected $y;
+
+ /**
+ * Signature Format
+ *
+ * @var string
+ */
+ protected $sigFormat;
+
+ /**
+ * Signature Format (Short)
+ *
+ * @var string
+ */
+ protected $shortFormat;
+
+ /**
+ * Create DSA parameters
+ *
+ * @param int $L
+ * @param int $N
+ * @return DSA|bool
+ */
+ public static function createParameters($L = 2048, $N = 224)
+ {
+ self::initialize_static_variables();
+
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('createParameters() should not be called from final classes (' . static::class . ')');
+ }
+
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+
+ switch (true) {
+ case $N == 160:
+ /*
+ in FIPS 186-1 and 186-2 N was fixed at 160 whereas K had an upper bound of 1024.
+ RFC 4253 (SSH Transport Layer Protocol) references FIPS 186-2 and as such most
+ SSH DSA implementations only support keys with an N of 160.
+ puttygen let's you set the size of L (but not the size of N) and uses 2048 as the
+ default L value. that's not really compliant with any of the FIPS standards, however,
+ for the purposes of maintaining compatibility with puttygen, we'll support it
+ */
+ //case ($L >= 512 || $L <= 1024) && (($L & 0x3F) == 0) && $N == 160:
+ // FIPS 186-3 changed this as follows:
+ //case $L == 1024 && $N == 160:
+ case $L == 2048 && $N == 224:
+ case $L == 2048 && $N == 256:
+ case $L == 3072 && $N == 256:
+ break;
+ default:
+ throw new \InvalidArgumentException('Invalid values for N and L');
+ }
+
+ $two = new BigInteger(2);
+
+ $q = BigInteger::randomPrime($N);
+ $divisor = $q->multiply($two);
+
+ do {
+ $x = BigInteger::random($L);
+ list(, $c) = $x->divide($divisor);
+ $p = $x->subtract($c->subtract(self::$one));
+ } while ($p->getLength() != $L || !$p->isPrime());
+
+ $p_1 = $p->subtract(self::$one);
+ list($e) = $p_1->divide($q);
+
+ // quoting http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf#page=50 ,
+ // "h could be obtained from a random number generator or from a counter that
+ // changes after each use". PuTTY (sshdssg.c) starts h off at 1 and increments
+ // it on each loop. wikipedia says "commonly h = 2 is used" so we'll just do that
+ $h = clone $two;
+ while (true) {
+ $g = $h->powMod($e, $p);
+ if (!$g->equals(self::$one)) {
+ break;
+ }
+ $h = $h->add(self::$one);
+ }
+
+ $dsa = new Parameters();
+ $dsa->p = $p;
+ $dsa->q = $q;
+ $dsa->g = $g;
+
+ return $dsa;
+ }
+
+ /**
+ * Create public / private key pair.
+ *
+ * This method is a bit polymorphic. It can take a DSA/Parameters object, L / N as two distinct parameters or
+ * no parameters (at which point L and N will be generated with this method)
+ *
+ * Returns the private key, from which the publickey can be extracted
+ *
+ * @param int[] ...$args
+ * @return PrivateKey
+ */
+ public static function createKey(...$args)
+ {
+ self::initialize_static_variables();
+
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
+ }
+
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+
+ if (count($args) == 2 && is_int($args[0]) && is_int($args[1])) {
+ $params = self::createParameters($args[0], $args[1]);
+ } elseif (count($args) == 1 && $args[0] instanceof Parameters) {
+ $params = $args[0];
+ } elseif (!count($args)) {
+ $params = self::createParameters();
+ } else {
+ throw new InsufficientSetupException('Valid parameters are either two integers (L and N), a single DSA object or no parameters at all.');
+ }
+
+ $private = new PrivateKey();
+ $private->p = $params->p;
+ $private->q = $params->q;
+ $private->g = $params->g;
+
+ $private->x = BigInteger::randomRange(self::$one, $private->q->subtract(self::$one));
+ $private->y = $private->g->powMod($private->x, $private->p);
+
+ //$public = clone $private;
+ //unset($public->x);
+
+ return $private
+ ->withHash($params->hash->getHash())
+ ->withSignatureFormat($params->shortFormat);
+ }
+
+ /**
+ * OnLoad Handler
+ *
+ * @return bool
+ */
+ protected static function onLoad(array $components)
+ {
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+
+ if (!isset($components['x']) && !isset($components['y'])) {
+ $new = new Parameters();
+ } elseif (isset($components['x'])) {
+ $new = new PrivateKey();
+ $new->x = $components['x'];
+ } else {
+ $new = new PublicKey();
+ }
+
+ $new->p = $components['p'];
+ $new->q = $components['q'];
+ $new->g = $components['g'];
+
+ if (isset($components['y'])) {
+ $new->y = $components['y'];
+ }
+
+ return $new;
+ }
+
+ /**
+ * Constructor
+ *
+ * PublicKey and PrivateKey objects can only be created from abstract RSA class
+ */
+ protected function __construct()
+ {
+ $this->sigFormat = self::validatePlugin('Signature', 'ASN1');
+ $this->shortFormat = 'ASN1';
+
+ parent::__construct();
+ }
+
+ /**
+ * Returns the key size
+ *
+ * More specifically, this L (the length of DSA Prime P) and N (the length of DSA Group Order q)
+ *
+ * @return array
+ */
+ public function getLength()
+ {
+ return ['L' => $this->p->getLength(), 'N' => $this->q->getLength()];
+ }
+
+ /**
+ * Returns the current engine being used
+ *
+ * @see self::useInternalEngine()
+ * @see self::useBestEngine()
+ * @return string
+ */
+ public function getEngine()
+ {
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+ return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ?
+ 'OpenSSL' : 'PHP';
+ }
+
+ /**
+ * Returns the parameters
+ *
+ * A public / private key is only returned if the currently loaded "key" contains an x or y
+ * value.
+ *
+ * @see self::getPublicKey()
+ * @return mixed
+ */
+ public function getParameters()
+ {
+ $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
+
+ $key = $type::saveParameters($this->p, $this->q, $this->g);
+ return DSA::load($key, 'PKCS1')
+ ->withHash($this->hash->getHash())
+ ->withSignatureFormat($this->shortFormat);
+ }
+
+ /**
+ * Determines the signature padding mode
+ *
+ * Valid values are: ASN1, SSH2, Raw
+ *
+ * @param string $format
+ */
+ public function withSignatureFormat($format)
+ {
+ $new = clone $this;
+ $new->shortFormat = $format;
+ $new->sigFormat = self::validatePlugin('Signature', $format);
+ return $new;
+ }
+
+ /**
+ * Returns the signature format currently being used
+ *
+ */
+ public function getSignatureFormat()
+ {
+ return $this->shortFormat;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php
new file mode 100644
index 0000000000..bc41fcf5ea
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php
@@ -0,0 +1,118 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * OpenSSH Formatted DSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class OpenSSH extends Progenitor
+{
+ /**
+ * Supported Key Types
+ *
+ * @var array
+ */
+ protected static $types = ['ssh-dss'];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $parsed = parent::load($key, $password);
+
+ if (isset($parsed['paddedKey'])) {
+ list($type) = Strings::unpackSSH2('s', $parsed['paddedKey']);
+ if ($type != $parsed['type']) {
+ throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
+ }
+
+ list($p, $q, $g, $y, $x, $comment) = Strings::unpackSSH2('i5s', $parsed['paddedKey']);
+
+ return compact('p', 'q', 'g', 'y', 'x', 'comment');
+ }
+
+ list($p, $q, $g, $y) = Strings::unpackSSH2('iiii', $parsed['publicKey']);
+
+ $comment = $parsed['comment'];
+
+ return compact('p', 'q', 'g', 'y', 'comment');
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, array $options = [])
+ {
+ if ($q->getLength() != 160) {
+ throw new \InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160');
+ }
+
+ // from :
+ // string "ssh-dss"
+ // mpint p
+ // mpint q
+ // mpint g
+ // mpint y
+ $DSAPublicKey = Strings::packSSH2('siiii', 'ssh-dss', $p, $q, $g, $y);
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $DSAPublicKey;
+ }
+
+ $comment = isset($options['comment']) ? $options['comment'] : self::$comment;
+ $DSAPublicKey = 'ssh-dss ' . base64_encode($DSAPublicKey) . ' ' . $comment;
+
+ return $DSAPublicKey;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param BigInteger $x
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = [])
+ {
+ $publicKey = self::savePublicKey($p, $q, $g, $y, ['binary' => true]);
+ $privateKey = Strings::packSSH2('si5', 'ssh-dss', $p, $q, $g, $y, $x);
+
+ return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php
new file mode 100644
index 0000000000..800cfb38c4
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php
@@ -0,0 +1,143 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#1 Formatted DSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class PKCS1 extends Progenitor
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ $decoded = ASN1::decodeBER($key);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\DSAParams::MAP);
+ if (is_array($key)) {
+ return $key;
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\DSAPrivateKey::MAP);
+ if (is_array($key)) {
+ return $key;
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\DSAPublicKey::MAP);
+ if (is_array($key)) {
+ return $key;
+ }
+
+ throw new \RuntimeException('Unable to perform ASN1 mapping');
+ }
+
+ /**
+ * Convert DSA parameters to the appropriate format
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @return string
+ */
+ public static function saveParameters(BigInteger $p, BigInteger $q, BigInteger $g)
+ {
+ $key = [
+ 'p' => $p,
+ 'q' => $q,
+ 'g' => $g
+ ];
+
+ $key = ASN1::encodeDER($key, Maps\DSAParams::MAP);
+
+ return "-----BEGIN DSA PARAMETERS-----\r\n" .
+ chunk_split(Strings::base64_encode($key), 64) .
+ "-----END DSA PARAMETERS-----\r\n";
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param BigInteger $x
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = [])
+ {
+ $key = [
+ 'version' => 0,
+ 'p' => $p,
+ 'q' => $q,
+ 'g' => $g,
+ 'y' => $y,
+ 'x' => $x
+ ];
+
+ $key = ASN1::encodeDER($key, Maps\DSAPrivateKey::MAP);
+
+ return self::wrapPrivateKey($key, 'DSA', $password, $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y)
+ {
+ $key = ASN1::encodeDER($y, Maps\DSAPublicKey::MAP);
+
+ return self::wrapPublicKey($key, 'DSA');
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php
new file mode 100644
index 0000000000..359ed09ea2
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php
@@ -0,0 +1,146 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Keys;
+
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#8 Formatted DSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class PKCS8 extends Progenitor
+{
+ /**
+ * OID Name
+ *
+ * @var string
+ */
+ const OID_NAME = 'id-dsa';
+
+ /**
+ * OID Value
+ *
+ * @var string
+ */
+ const OID_VALUE = '1.2.840.10040.4.1';
+
+ /**
+ * Child OIDs loaded
+ *
+ * @var bool
+ */
+ protected static $childOIDsLoaded = false;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ $type = isset($key['privateKey']) ? 'privateKey' : 'publicKey';
+
+ $decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER of parameters');
+ }
+ $components = ASN1::asn1map($decoded[0], Maps\DSAParams::MAP);
+ if (!is_array($components)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping on parameters');
+ }
+
+ $decoded = ASN1::decodeBER($key[$type]);
+ if (empty($decoded)) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ $var = $type == 'privateKey' ? 'x' : 'y';
+ $components[$var] = ASN1::asn1map($decoded[0], Maps\DSAPublicKey::MAP);
+ if (!$components[$var] instanceof BigInteger) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping');
+ }
+
+ if (isset($key['meta'])) {
+ $components['meta'] = $key['meta'];
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param BigInteger $x
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = [])
+ {
+ $params = [
+ 'p' => $p,
+ 'q' => $q,
+ 'g' => $g
+ ];
+ $params = ASN1::encodeDER($params, Maps\DSAParams::MAP);
+ $params = new ASN1\Element($params);
+ $key = ASN1::encodeDER($x, Maps\DSAPublicKey::MAP);
+ return self::wrapPrivateKey($key, [], $params, $password, null, '', $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, array $options = [])
+ {
+ $params = [
+ 'p' => $p,
+ 'q' => $q,
+ 'g' => $g
+ ];
+ $params = ASN1::encodeDER($params, Maps\DSAParams::MAP);
+ $params = new ASN1\Element($params);
+ $key = ASN1::encodeDER($y, Maps\DSAPublicKey::MAP);
+ return self::wrapPublicKey($key, $params, null, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php
new file mode 100644
index 0000000000..8549a2ec7b
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php
@@ -0,0 +1,112 @@
+ 160 kinda useless, hence this handlers not supporting such keys.
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PuTTY as Progenitor;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PuTTY Formatted DSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class PuTTY extends Progenitor
+{
+ /**
+ * Public Handler
+ *
+ * @var string
+ */
+ const PUBLIC_HANDLER = 'phpseclib3\Crypt\DSA\Formats\Keys\OpenSSH';
+
+ /**
+ * Algorithm Identifier
+ *
+ * @var array
+ */
+ protected static $types = ['ssh-dss'];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $components = parent::load($key, $password);
+ if (!isset($components['private'])) {
+ return $components;
+ }
+ $type = $components['type'];
+ $comment = $components['comment'];
+ $public = $components['public'];
+ $private = $components['private'];
+ unset($components['public'], $components['private']);
+
+ list($p, $q, $g, $y) = Strings::unpackSSH2('iiii', $public);
+ list($x) = Strings::unpackSSH2('i', $private);
+
+ return compact('p', 'q', 'g', 'y', 'x', 'comment');
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param BigInteger $x
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = false, array $options = [])
+ {
+ if ($q->getLength() != 160) {
+ throw new \InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160');
+ }
+
+ $public = Strings::packSSH2('iiii', $p, $q, $g, $y);
+ $private = Strings::packSSH2('i', $x);
+
+ return self::wrapPrivateKey($public, $private, 'ssh-dss', $password, $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y)
+ {
+ if ($q->getLength() != 160) {
+ throw new \InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160');
+ }
+
+ return self::wrapPublicKey(Strings::packSSH2('iiii', $p, $q, $g, $y), 'ssh-dss');
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/Formats/Keys/Raw.php b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/Raw.php
new file mode 100644
index 0000000000..8e2ef01f1f
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/Raw.php
@@ -0,0 +1,85 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Keys;
+
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Raw DSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class Raw
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param array $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ if (!is_array($key)) {
+ throw new \UnexpectedValueException('Key should be a array - not a ' . gettype($key));
+ }
+
+ switch (true) {
+ case !isset($key['p']) || !isset($key['q']) || !isset($key['g']):
+ case !$key['p'] instanceof BigInteger:
+ case !$key['q'] instanceof BigInteger:
+ case !$key['g'] instanceof BigInteger:
+ case !isset($key['x']) && !isset($key['y']):
+ case isset($key['x']) && !$key['x'] instanceof BigInteger:
+ case isset($key['y']) && !$key['y'] instanceof BigInteger:
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ $options = ['p' => 1, 'q' => 1, 'g' => 1, 'x' => 1, 'y' => 1];
+
+ return array_intersect_key($key, $options);
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param BigInteger $x
+ * @param string $password optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '')
+ {
+ return compact('p', 'q', 'g', 'y', 'x');
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y)
+ {
+ return compact('p', 'q', 'g', 'y');
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/Formats/Keys/XML.php b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/XML.php
new file mode 100644
index 0000000000..f77cbf20d8
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/XML.php
@@ -0,0 +1,132 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Exception\BadConfigurationException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * XML Formatted DSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class XML
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @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 (!class_exists('DOMDocument')) {
+ throw new BadConfigurationException('The dom extension is not setup correctly on this system');
+ }
+
+ $use_errors = libxml_use_internal_errors(true);
+
+ $dom = new \DOMDocument();
+ if (substr($key, 0, 5) != '' . $key . '';
+ }
+ if (!$dom->loadXML($key)) {
+ libxml_use_internal_errors($use_errors);
+ throw new \UnexpectedValueException('Key does not appear to contain XML');
+ }
+ $xpath = new \DOMXPath($dom);
+ $keys = ['p', 'q', 'g', 'y', 'j', 'seed', 'pgencounter'];
+ foreach ($keys as $key) {
+ // $dom->getElementsByTagName($key) is case-sensitive
+ $temp = $xpath->query("//*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$key']");
+ if (!$temp->length) {
+ continue;
+ }
+ $value = new BigInteger(Strings::base64_decode($temp->item(0)->nodeValue), 256);
+ switch ($key) {
+ case 'p': // a prime modulus meeting the [DSS] requirements
+ // Parameters P, Q, and G can be public and common to a group of users. They might be known
+ // from application context. As such, they are optional but P and Q must either both appear
+ // or both be absent
+ $components['p'] = $value;
+ break;
+ case 'q': // an integer in the range 2**159 < Q < 2**160 which is a prime divisor of P-1
+ $components['q'] = $value;
+ break;
+ case 'g': // an integer with certain properties with respect to P and Q
+ $components['g'] = $value;
+ break;
+ case 'y': // G**X mod P (where X is part of the private key and not made public)
+ $components['y'] = $value;
+ // the remaining options do not do anything
+ case 'j': // (P - 1) / Q
+ // Parameter J is available for inclusion solely for efficiency as it is calculatable from
+ // P and Q
+ case 'seed': // a DSA prime generation seed
+ // Parameters seed and pgenCounter are used in the DSA prime number generation algorithm
+ // specified in [DSS]. As such, they are optional but must either both be present or both
+ // be absent
+ case 'pgencounter': // a DSA prime generation counter
+ }
+ }
+
+ libxml_use_internal_errors($use_errors);
+
+ if (!isset($components['y'])) {
+ throw new \UnexpectedValueException('Key is missing y component');
+ }
+
+ switch (true) {
+ case !isset($components['p']):
+ case !isset($components['q']):
+ case !isset($components['g']):
+ return ['y' => $components['y']];
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * See https://www.w3.org/TR/xmldsig-core/#sec-DSAKeyValue
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y)
+ {
+ return "\r\n" .
+ ' ' . Strings::base64_encode($p->toBytes()) . "
\r\n" .
+ ' ' . Strings::base64_encode($q->toBytes()) . "
\r\n" .
+ ' ' . Strings::base64_encode($g->toBytes()) . "\r\n" .
+ ' ' . Strings::base64_encode($y->toBytes()) . "\r\n" .
+ '';
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/Formats/Keys/index.php b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/Formats/Keys/index.php
@@ -0,0 +1,8 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Signature;
+
+use phpseclib3\File\ASN1 as Encoder;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * ASN1 Signature Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class ASN1
+{
+ /**
+ * Loads a signature
+ *
+ * @param string $sig
+ * @return array|bool
+ */
+ public static function load($sig)
+ {
+ if (!is_string($sig)) {
+ return false;
+ }
+
+ $decoded = Encoder::decodeBER($sig);
+ if (empty($decoded)) {
+ return false;
+ }
+ $components = Encoder::asn1map($decoded[0], Maps\DssSigValue::MAP);
+
+ return $components;
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @param BigInteger $r
+ * @param BigInteger $s
+ * @return string
+ */
+ public static function save(BigInteger $r, BigInteger $s)
+ {
+ return Encoder::encodeDER(compact('r', 's'), Maps\DssSigValue::MAP);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/Formats/Signature/Raw.php b/Sources/Phpseclib/Crypt/DSA/Formats/Signature/Raw.php
new file mode 100644
index 0000000000..2657a2a87c
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/Formats/Signature/Raw.php
@@ -0,0 +1,25 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Signature;
+
+use phpseclib3\Crypt\Common\Formats\Signature\Raw as Progenitor;
+
+/**
+ * Raw DSA Signature Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class Raw extends Progenitor
+{
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/Formats/Signature/SSH2.php b/Sources/Phpseclib/Crypt/DSA/Formats/Signature/SSH2.php
new file mode 100644
index 0000000000..88807b5b82
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/Formats/Signature/SSH2.php
@@ -0,0 +1,74 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Signature;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * SSH2 Signature Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class SSH2
+{
+ /**
+ * Loads a signature
+ *
+ * @param string $sig
+ * @return mixed
+ */
+ public static function load($sig)
+ {
+ if (!is_string($sig)) {
+ return false;
+ }
+
+ $result = Strings::unpackSSH2('ss', $sig);
+ if ($result === false) {
+ return false;
+ }
+ list($type, $blob) = $result;
+ if ($type != 'ssh-dss' || strlen($blob) != 40) {
+ return false;
+ }
+
+ return [
+ 'r' => new BigInteger(substr($blob, 0, 20), 256),
+ 's' => new BigInteger(substr($blob, 20), 256)
+ ];
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @param BigInteger $r
+ * @param BigInteger $s
+ * @return string
+ */
+ public static function save(BigInteger $r, BigInteger $s)
+ {
+ if ($r->getLength() > 160 || $s->getLength() > 160) {
+ return false;
+ }
+ return Strings::packSSH2(
+ 'ss',
+ 'ssh-dss',
+ str_pad($r->toBytes(), 20, "\0", STR_PAD_LEFT) .
+ str_pad($s->toBytes(), 20, "\0", STR_PAD_LEFT)
+ );
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/Formats/Signature/index.php b/Sources/Phpseclib/Crypt/DSA/Formats/Signature/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/Formats/Signature/index.php
@@ -0,0 +1,8 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA;
+
+use phpseclib3\Crypt\DSA;
+
+/**
+ * DSA Parameters
+ *
+ * @author Jim Wigginton
+ */
+final class Parameters extends DSA
+{
+ /**
+ * Returns the parameters
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type = 'PKCS1', array $options = [])
+ {
+ $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
+
+ return $type::saveParameters($this->p, $this->q, $this->g, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/PrivateKey.php b/Sources/Phpseclib/Crypt/DSA/PrivateKey.php
new file mode 100644
index 0000000000..90252139dc
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/PrivateKey.php
@@ -0,0 +1,154 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA;
+
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\DSA;
+use phpseclib3\Crypt\DSA\Formats\Signature\ASN1 as ASN1Signature;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * DSA Private Key
+ *
+ * @author Jim Wigginton
+ */
+final class PrivateKey extends DSA implements Common\PrivateKey
+{
+ use Common\Traits\PasswordProtected;
+
+ /**
+ * DSA secret exponent x
+ *
+ * @var BigInteger
+ */
+ protected $x;
+
+ /**
+ * Returns the public key
+ *
+ * If you do "openssl rsa -in private.rsa -pubout -outform PEM" you get a PKCS8 formatted key
+ * that contains a publicKeyAlgorithm AlgorithmIdentifier and a publicKey BIT STRING.
+ * An AlgorithmIdentifier contains an OID and a parameters field. With RSA public keys this
+ * parameters field is NULL. With DSA PKCS8 public keys it is not - it contains the p, q and g
+ * variables. The publicKey BIT STRING contains, simply, the y variable. This can be verified
+ * by getting a DSA PKCS8 public key:
+ *
+ * "openssl dsa -in private.dsa -pubout -outform PEM"
+ *
+ * ie. just swap out rsa with dsa in the rsa command above.
+ *
+ * A PKCS1 public key corresponds to the publicKey portion of the PKCS8 key. In the case of RSA
+ * the publicKey portion /is/ the key. In the case of DSA it is not. You cannot verify a signature
+ * without the parameters and the PKCS1 DSA public key format does not include the parameters.
+ *
+ * @see self::getPrivateKey()
+ * @return mixed
+ */
+ public function getPublicKey()
+ {
+ $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey');
+
+ if (!isset($this->y)) {
+ $this->y = $this->g->powMod($this->x, $this->p);
+ }
+
+ $key = $type::savePublicKey($this->p, $this->q, $this->g, $this->y);
+
+ return DSA::loadFormat('PKCS8', $key)
+ ->withHash($this->hash->getHash())
+ ->withSignatureFormat($this->shortFormat);
+ }
+
+ /**
+ * Create a signature
+ *
+ * @see self::verify()
+ * @param string $message
+ * @return mixed
+ */
+ public function sign($message)
+ {
+ $format = $this->sigFormat;
+
+ if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
+ $signature = '';
+ $result = openssl_sign($message, $signature, $this->toString('PKCS8'), $this->hash->getHash());
+
+ if ($result) {
+ if ($this->shortFormat == 'ASN1') {
+ return $signature;
+ }
+
+ $loaded = ASN1Signature::load($signature);
+ $r = $loaded['r'];
+ $s = $loaded['s'];
+
+ return $format::save($r, $s);
+ }
+ }
+
+ $h = $this->hash->hash($message);
+ $h = $this->bits2int($h);
+
+ while (true) {
+ $k = BigInteger::randomRange(self::$one, $this->q->subtract(self::$one));
+ $r = $this->g->powMod($k, $this->p);
+ list(, $r) = $r->divide($this->q);
+ if ($r->equals(self::$zero)) {
+ continue;
+ }
+ $kinv = $k->modInverse($this->q);
+ $temp = $h->add($this->x->multiply($r));
+ $temp = $kinv->multiply($temp);
+ list(, $s) = $temp->divide($this->q);
+ if (!$s->equals(self::$zero)) {
+ break;
+ }
+ }
+
+ // the following is an RFC6979 compliant implementation of deterministic DSA
+ // it's unused because it's mainly intended for use when a good CSPRNG isn't
+ // available. if phpseclib's CSPRNG isn't good then even key generation is
+ // suspect
+ /*
+ $h1 = $this->hash->hash($message);
+ $k = $this->computek($h1);
+ $r = $this->g->powMod($k, $this->p);
+ list(, $r) = $r->divide($this->q);
+ $kinv = $k->modInverse($this->q);
+ $h1 = $this->bits2int($h1);
+ $temp = $h1->add($this->x->multiply($r));
+ $temp = $kinv->multiply($temp);
+ list(, $s) = $temp->divide($this->q);
+ */
+
+ return $format::save($r, $s);
+ }
+
+ /**
+ * Returns the private key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePrivateKey');
+
+ if (!isset($this->y)) {
+ $this->y = $this->g->powMod($this->x, $this->p);
+ }
+
+ return $type::savePrivateKey($this->p, $this->q, $this->g, $this->y, $this->x, $this->password, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/PublicKey.php b/Sources/Phpseclib/Crypt/DSA/PublicKey.php
new file mode 100644
index 0000000000..3e16762b8c
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/PublicKey.php
@@ -0,0 +1,87 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA;
+
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\DSA;
+use phpseclib3\Crypt\DSA\Formats\Signature\ASN1 as ASN1Signature;
+
+/**
+ * DSA Public Key
+ *
+ * @author Jim Wigginton
+ */
+final class PublicKey extends DSA implements Common\PublicKey
+{
+ use Common\Traits\Fingerprint;
+
+ /**
+ * Verify a signature
+ *
+ * @see self::verify()
+ * @param string $message
+ * @param string $signature
+ * @return mixed
+ */
+ public function verify($message, $signature)
+ {
+ $format = $this->sigFormat;
+
+ $params = $format::load($signature);
+ if ($params === false || count($params) != 2) {
+ return false;
+ }
+ $r = $params['r'];
+ $s = $params['s'];
+
+ if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
+ $sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature;
+
+ $result = openssl_verify($message, $sig, $this->toString('PKCS8'), $this->hash->getHash());
+
+ if ($result != -1) {
+ return (bool) $result;
+ }
+ }
+
+ $q_1 = $this->q->subtract(self::$one);
+ if (!$r->between(self::$one, $q_1) || !$s->between(self::$one, $q_1)) {
+ return false;
+ }
+
+ $w = $s->modInverse($this->q);
+ $h = $this->hash->hash($message);
+ $h = $this->bits2int($h);
+ list(, $u1) = $h->multiply($w)->divide($this->q);
+ list(, $u2) = $r->multiply($w)->divide($this->q);
+ $v1 = $this->g->powMod($u1, $this->p);
+ $v2 = $this->y->powMod($u2, $this->p);
+ list(, $v) = $v1->multiply($v2)->divide($this->p);
+ list(, $v) = $v->divide($this->q);
+
+ return $v->equals($r);
+ }
+
+ /**
+ * Returns the public key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePublicKey');
+
+ return $type::savePublicKey($this->p, $this->q, $this->g, $this->y, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/DSA/index.php b/Sources/Phpseclib/Crypt/DSA/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/DSA/index.php
@@ -0,0 +1,8 @@
+
+ * getPublicKey();
+ *
+ * $plaintext = 'terrafrost';
+ *
+ * $signature = $private->sign($plaintext);
+ *
+ * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
+ * ?>
+ *
+ *
+ * @author Jim Wigginton
+ * @copyright 2016 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\AsymmetricKey;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Crypt\EC\Curves\Curve25519;
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Crypt\EC\Curves\Ed448;
+use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
+use phpseclib3\Crypt\EC\Parameters;
+use phpseclib3\Crypt\EC\PrivateKey;
+use phpseclib3\Crypt\EC\PublicKey;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\Exception\UnsupportedOperationException;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps\ECParameters;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Pure-PHP implementation of EC.
+ *
+ * @author Jim Wigginton
+ */
+abstract class EC extends AsymmetricKey
+{
+ /**
+ * Algorithm Name
+ *
+ * @var string
+ */
+ const ALGORITHM = 'EC';
+
+ /**
+ * Public Key QA
+ *
+ * @var object[]
+ */
+ protected $QA;
+
+ /**
+ * Curve
+ *
+ * @var EC\BaseCurves\Base
+ */
+ protected $curve;
+
+ /**
+ * Signature Format
+ *
+ * @var string
+ */
+ protected $format;
+
+ /**
+ * Signature Format (Short)
+ *
+ * @var string
+ */
+ protected $shortFormat;
+
+ /**
+ * Curve Name
+ *
+ * @var string
+ */
+ private $curveName;
+
+ /**
+ * Curve Order
+ *
+ * Used for deterministic ECDSA
+ *
+ * @var BigInteger
+ */
+ protected $q;
+
+ /**
+ * Alias for the private key
+ *
+ * Used for deterministic ECDSA. AsymmetricKey expects $x. I don't like x because
+ * with x you have x * the base point yielding an (x, y)-coordinate that is the
+ * public key. But the x is different depending on which side of the equal sign
+ * you're on. It's less ambiguous if you do dA * base point = (x, y)-coordinate.
+ *
+ * @var BigInteger
+ */
+ protected $x;
+
+ /**
+ * Context
+ *
+ * @var string
+ */
+ protected $context;
+
+ /**
+ * Signature Format
+ *
+ * @var string
+ */
+ protected $sigFormat;
+
+ /**
+ * Create public / private key pair.
+ *
+ * @param string $curve
+ * @return PrivateKey
+ */
+ public static function createKey($curve)
+ {
+ self::initialize_static_variables();
+
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
+ }
+
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+
+ $curve = strtolower($curve);
+ if (self::$engines['libsodium'] && $curve == 'ed25519' && function_exists('sodium_crypto_sign_keypair')) {
+ $kp = sodium_crypto_sign_keypair();
+
+ $privatekey = EC::loadFormat('libsodium', sodium_crypto_sign_secretkey($kp));
+ //$publickey = EC::loadFormat('libsodium', sodium_crypto_sign_publickey($kp));
+
+ $privatekey->curveName = 'Ed25519';
+ //$publickey->curveName = $curve;
+
+ return $privatekey;
+ }
+
+ $privatekey = new PrivateKey();
+
+ $curveName = $curve;
+ if (preg_match('#(?:^curve|^ed)\d+$#', $curveName)) {
+ $curveName = ucfirst($curveName);
+ } elseif (substr($curveName, 0, 10) == 'brainpoolp') {
+ $curveName = 'brainpoolP' . substr($curveName, 10);
+ }
+ $curve = '\phpseclib3\Crypt\EC\Curves\\' . $curveName;
+
+ if (!class_exists($curve)) {
+ throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported');
+ }
+
+ $reflect = new \ReflectionClass($curve);
+ $curveName = $reflect->isFinal() ?
+ $reflect->getParentClass()->getShortName() :
+ $reflect->getShortName();
+
+ $curve = new $curve();
+ if ($curve instanceof TwistedEdwardsCurve) {
+ $arr = $curve->extractSecret(Random::string($curve instanceof Ed448 ? 57 : 32));
+ $privatekey->dA = $dA = $arr['dA'];
+ $privatekey->secret = $arr['secret'];
+ } else {
+ $privatekey->dA = $dA = $curve->createRandomMultiplier();
+ }
+ if ($curve instanceof Curve25519 && self::$engines['libsodium']) {
+ //$r = pack('H*', '0900000000000000000000000000000000000000000000000000000000000000');
+ //$QA = sodium_crypto_scalarmult($dA->toBytes(), $r);
+ $QA = sodium_crypto_box_publickey_from_secretkey($dA->toBytes());
+ $privatekey->QA = [$curve->convertInteger(new BigInteger(strrev($QA), 256))];
+ } else {
+ $privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA);
+ }
+ $privatekey->curve = $curve;
+
+ //$publickey = clone $privatekey;
+ //unset($publickey->dA);
+ //unset($publickey->x);
+
+ $privatekey->curveName = $curveName;
+ //$publickey->curveName = $curveName;
+
+ if ($privatekey->curve instanceof TwistedEdwardsCurve) {
+ return $privatekey->withHash($curve::HASH);
+ }
+
+ return $privatekey;
+ }
+
+ /**
+ * OnLoad Handler
+ *
+ * @return bool
+ */
+ protected static function onLoad(array $components)
+ {
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+
+ if (!isset($components['dA']) && !isset($components['QA'])) {
+ $new = new Parameters();
+ $new->curve = $components['curve'];
+ return $new;
+ }
+
+ $new = isset($components['dA']) ?
+ new PrivateKey() :
+ new PublicKey();
+ $new->curve = $components['curve'];
+ $new->QA = $components['QA'];
+
+ if (isset($components['dA'])) {
+ $new->dA = $components['dA'];
+ $new->secret = $components['secret'];
+ }
+
+ if ($new->curve instanceof TwistedEdwardsCurve) {
+ return $new->withHash($components['curve']::HASH);
+ }
+
+ return $new;
+ }
+
+ /**
+ * Constructor
+ *
+ * PublicKey and PrivateKey objects can only be created from abstract RSA class
+ */
+ protected function __construct()
+ {
+ $this->sigFormat = self::validatePlugin('Signature', 'ASN1');
+ $this->shortFormat = 'ASN1';
+
+ parent::__construct();
+ }
+
+ /**
+ * Returns the curve
+ *
+ * Returns a string if it's a named curve, an array if not
+ *
+ * @return string|array
+ */
+ public function getCurve()
+ {
+ if ($this->curveName) {
+ return $this->curveName;
+ }
+
+ if ($this->curve instanceof MontgomeryCurve) {
+ $this->curveName = $this->curve instanceof Curve25519 ? 'Curve25519' : 'Curve448';
+ return $this->curveName;
+ }
+
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ $this->curveName = $this->curve instanceof Ed25519 ? 'Ed25519' : 'Ed448';
+ return $this->curveName;
+ }
+
+ $params = $this->getParameters()->toString('PKCS8', ['namedCurve' => true]);
+ $decoded = ASN1::extractBER($params);
+ $decoded = ASN1::decodeBER($decoded);
+ $decoded = ASN1::asn1map($decoded[0], ECParameters::MAP);
+ if (isset($decoded['namedCurve'])) {
+ $this->curveName = $decoded['namedCurve'];
+ return $decoded['namedCurve'];
+ }
+
+ if (!$namedCurves) {
+ PKCS1::useSpecifiedCurve();
+ }
+
+ return $decoded;
+ }
+
+ /**
+ * Returns the key size
+ *
+ * Quoting https://tools.ietf.org/html/rfc5656#section-2,
+ *
+ * "The size of a set of elliptic curve domain parameters on a prime
+ * curve is defined as the number of bits in the binary representation
+ * of the field order, commonly denoted by p. Size on a
+ * characteristic-2 curve is defined as the number of bits in the binary
+ * representation of the field, commonly denoted by m. A set of
+ * elliptic curve domain parameters defines a group of order n generated
+ * by a base point P"
+ *
+ * @return int
+ */
+ public function getLength()
+ {
+ return $this->curve->getLength();
+ }
+
+ /**
+ * Returns the current engine being used
+ *
+ * @see self::useInternalEngine()
+ * @see self::useBestEngine()
+ * @return string
+ */
+ public function getEngine()
+ {
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ return $this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context) ?
+ 'libsodium' : 'PHP';
+ }
+
+ return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ?
+ 'OpenSSL' : 'PHP';
+ }
+
+ /**
+ * Returns the public key coordinates as a string
+ *
+ * Used by ECDH
+ *
+ * @return string
+ */
+ public function getEncodedCoordinates()
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ return strrev($this->QA[0]->toBytes(true));
+ }
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ return $this->curve->encodePoint($this->QA);
+ }
+ return "\4" . $this->QA[0]->toBytes(true) . $this->QA[1]->toBytes(true);
+ }
+
+ /**
+ * Returns the parameters
+ *
+ * @see self::getPublicKey()
+ * @param string $type optional
+ * @return mixed
+ */
+ public function getParameters($type = 'PKCS1')
+ {
+ $type = self::validatePlugin('Keys', $type, 'saveParameters');
+
+ $key = $type::saveParameters($this->curve);
+
+ return EC::load($key, 'PKCS1')
+ ->withHash($this->hash->getHash())
+ ->withSignatureFormat($this->shortFormat);
+ }
+
+ /**
+ * Determines the signature padding mode
+ *
+ * Valid values are: ASN1, SSH2, Raw
+ *
+ * @param string $format
+ */
+ public function withSignatureFormat($format)
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
+ }
+
+ $new = clone $this;
+ $new->shortFormat = $format;
+ $new->sigFormat = self::validatePlugin('Signature', $format);
+ return $new;
+ }
+
+ /**
+ * Returns the signature format currently being used
+ *
+ */
+ public function getSignatureFormat()
+ {
+ return $this->shortFormat;
+ }
+
+ /**
+ * Sets the context
+ *
+ * Used by Ed25519 / Ed448.
+ *
+ * @see self::sign()
+ * @see self::verify()
+ * @param string $context optional
+ */
+ public function withContext($context = null)
+ {
+ if (!$this->curve instanceof TwistedEdwardsCurve) {
+ throw new UnsupportedCurveException('Only Ed25519 and Ed448 support contexts');
+ }
+
+ $new = clone $this;
+ if (!isset($context)) {
+ $new->context = null;
+ return $new;
+ }
+ if (!is_string($context)) {
+ throw new \InvalidArgumentException('setContext expects a string');
+ }
+ if (strlen($context) > 255) {
+ throw new \LengthException('The context is supposed to be, at most, 255 bytes long');
+ }
+ $new->context = $context;
+ return $new;
+ }
+
+ /**
+ * Returns the signature format currently being used
+ *
+ */
+ public function getContext()
+ {
+ return $this->context;
+ }
+
+ /**
+ * Determines which hashing function should be used
+ *
+ * @param string $hash
+ */
+ public function withHash($hash)
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
+ }
+ if ($this->curve instanceof Ed25519 && $hash != 'sha512') {
+ throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash');
+ }
+ if ($this->curve instanceof Ed448 && $hash != 'shake256-912') {
+ throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes');
+ }
+
+ return parent::withHash($hash);
+ }
+
+ /**
+ * __toString() magic method
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ return '';
+ }
+
+ return parent::__toString();
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/BaseCurves/Base.php b/Sources/Phpseclib/Crypt/EC/BaseCurves/Base.php
new file mode 100644
index 0000000000..d76562d0d0
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/BaseCurves/Base.php
@@ -0,0 +1,218 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\BaseCurves;
+
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Base
+ *
+ * @author Jim Wigginton
+ */
+abstract class Base
+{
+ /**
+ * The Order
+ *
+ * @var BigInteger
+ */
+ protected $order;
+
+ /**
+ * Finite Field Integer factory
+ *
+ * @var FiniteField\Integer
+ */
+ protected $factory;
+
+ /**
+ * Returns a random integer
+ *
+ * @return object
+ */
+ public function randomInteger()
+ {
+ return $this->factory->randomInteger();
+ }
+
+ /**
+ * Converts a BigInteger to a FiniteField\Integer integer
+ *
+ * @return object
+ */
+ public function convertInteger(BigInteger $x)
+ {
+ return $this->factory->newInteger($x);
+ }
+
+ /**
+ * Returns the length, in bytes, of the modulo
+ *
+ * @return integer
+ */
+ public function getLengthInBytes()
+ {
+ return $this->factory->getLengthInBytes();
+ }
+
+ /**
+ * Returns the length, in bits, of the modulo
+ *
+ * @return integer
+ */
+ public function getLength()
+ {
+ return $this->factory->getLength();
+ }
+
+ /**
+ * Multiply a point on the curve by a scalar
+ *
+ * Uses the montgomery ladder technique as described here:
+ *
+ * https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
+ * https://github.com/phpecc/phpecc/issues/16#issuecomment-59176772
+ *
+ * @return array
+ */
+ public function multiplyPoint(array $p, BigInteger $d)
+ {
+ $alreadyInternal = isset($p[2]);
+ $r = $alreadyInternal ?
+ [[], $p] :
+ [[], $this->convertToInternal($p)];
+
+ $d = $d->toBits();
+ for ($i = 0; $i < strlen($d); $i++) {
+ $d_i = (int) $d[$i];
+ $r[1 - $d_i] = $this->addPoint($r[0], $r[1]);
+ $r[$d_i] = $this->doublePoint($r[$d_i]);
+ }
+
+ return $alreadyInternal ? $r[0] : $this->convertToAffine($r[0]);
+ }
+
+ /**
+ * Creates a random scalar multiplier
+ *
+ * @return BigInteger
+ */
+ public function createRandomMultiplier()
+ {
+ static $one;
+ if (!isset($one)) {
+ $one = new BigInteger(1);
+ }
+
+ return BigInteger::randomRange($one, $this->order->subtract($one));
+ }
+
+ /**
+ * Performs range check
+ */
+ public function rangeCheck(BigInteger $x)
+ {
+ static $zero;
+ if (!isset($zero)) {
+ $zero = new BigInteger();
+ }
+
+ if (!isset($this->order)) {
+ throw new \RuntimeException('setOrder needs to be called before this method');
+ }
+ if ($x->compare($this->order) > 0 || $x->compare($zero) <= 0) {
+ throw new \RangeException('x must be between 1 and the order of the curve');
+ }
+ }
+
+ /**
+ * Sets the Order
+ */
+ public function setOrder(BigInteger $order)
+ {
+ $this->order = $order;
+ }
+
+ /**
+ * Returns the Order
+ *
+ * @return BigInteger
+ */
+ public function getOrder()
+ {
+ return $this->order;
+ }
+
+ /**
+ * Use a custom defined modular reduction function
+ *
+ * @return object
+ */
+ public function setReduction(callable $func)
+ {
+ $this->factory->setReduction($func);
+ }
+
+ /**
+ * Returns the affine point
+ *
+ * @return object[]
+ */
+ public function convertToAffine(array $p)
+ {
+ return $p;
+ }
+
+ /**
+ * Converts an affine point to a jacobian coordinate
+ *
+ * @return object[]
+ */
+ public function convertToInternal(array $p)
+ {
+ return $p;
+ }
+
+ /**
+ * Negates a point
+ *
+ * @return object[]
+ */
+ public function negatePoint(array $p)
+ {
+ $temp = [
+ $p[0],
+ $p[1]->negate()
+ ];
+ if (isset($p[2])) {
+ $temp[] = $p[2];
+ }
+ return $temp;
+ }
+
+ /**
+ * Multiply and Add Points
+ *
+ * @return int[]
+ */
+ public function multiplyAddPoints(array $points, array $scalars)
+ {
+ $p1 = $this->convertToInternal($points[0]);
+ $p2 = $this->convertToInternal($points[1]);
+ $p1 = $this->multiplyPoint($p1, $scalars[0]);
+ $p2 = $this->multiplyPoint($p2, $scalars[1]);
+ $r = $this->addPoint($p1, $p2);
+ return $this->convertToAffine($r);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/BaseCurves/Binary.php b/Sources/Phpseclib/Crypt/EC/BaseCurves/Binary.php
new file mode 100644
index 0000000000..66da11da72
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/BaseCurves/Binary.php
@@ -0,0 +1,373 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\BaseCurves;
+
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\BinaryField;
+use phpseclib3\Math\BinaryField\Integer as BinaryInteger;
+
+/**
+ * Curves over y^2 + x*y = x^3 + a*x^2 + b
+ *
+ * @author Jim Wigginton
+ */
+class Binary extends Base
+{
+ /**
+ * Binary Field Integer factory
+ *
+ * @var BinaryField
+ */
+ protected $factory;
+
+ /**
+ * Cofficient for x^1
+ *
+ * @var object
+ */
+ protected $a;
+
+ /**
+ * Cofficient for x^0
+ *
+ * @var object
+ */
+ protected $b;
+
+ /**
+ * Base Point
+ *
+ * @var object
+ */
+ protected $p;
+
+ /**
+ * The number one over the specified finite field
+ *
+ * @var object
+ */
+ protected $one;
+
+ /**
+ * The modulo
+ *
+ * @var BigInteger
+ */
+ protected $modulo;
+
+ /**
+ * The Order
+ *
+ * @var BigInteger
+ */
+ protected $order;
+
+ /**
+ * Sets the modulo
+ */
+ public function setModulo(...$modulo)
+ {
+ $this->modulo = $modulo;
+ $this->factory = new BinaryField(...$modulo);
+
+ $this->one = $this->factory->newInteger("\1");
+ }
+
+ /**
+ * Set coefficients a and b
+ *
+ * @param string $a
+ * @param string $b
+ */
+ public function setCoefficients($a, $b)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->a = $this->factory->newInteger(pack('H*', $a));
+ $this->b = $this->factory->newInteger(pack('H*', $b));
+ }
+
+ /**
+ * Set x and y coordinates for the base point
+ *
+ * @param string|BinaryInteger $x
+ * @param string|BinaryInteger $y
+ */
+ public function setBasePoint($x, $y)
+ {
+ switch (true) {
+ case !is_string($x) && !$x instanceof BinaryInteger:
+ throw new \UnexpectedValueException('Argument 1 passed to Binary::setBasePoint() must be a string or an instance of BinaryField\Integer');
+ case !is_string($y) && !$y instanceof BinaryInteger:
+ throw new \UnexpectedValueException('Argument 2 passed to Binary::setBasePoint() must be a string or an instance of BinaryField\Integer');
+ }
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->p = [
+ is_string($x) ? $this->factory->newInteger(pack('H*', $x)) : $x,
+ is_string($y) ? $this->factory->newInteger(pack('H*', $y)) : $y
+ ];
+ }
+
+ /**
+ * Retrieve the base point as an array
+ *
+ * @return array
+ */
+ public function getBasePoint()
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ /*
+ if (!isset($this->p)) {
+ throw new \RuntimeException('setBasePoint needs to be called before this method');
+ }
+ */
+ return $this->p;
+ }
+
+ /**
+ * Adds two points on the curve
+ *
+ * @return FiniteField[]
+ */
+ public function addPoint(array $p, array $q)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p) || !count($q)) {
+ if (count($q)) {
+ return $q;
+ }
+ if (count($p)) {
+ return $p;
+ }
+ return [];
+ }
+
+ if (!isset($p[2]) || !isset($q[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
+ }
+
+ if ($p[0]->equals($q[0])) {
+ return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p);
+ }
+
+ // formulas from http://hyperelliptic.org/EFD/g12o/auto-shortw-jacobian.html
+
+ list($x1, $y1, $z1) = $p;
+ list($x2, $y2, $z2) = $q;
+
+ $o1 = $z1->multiply($z1);
+ $b = $x2->multiply($o1);
+
+ if ($z2->equals($this->one)) {
+ $d = $y2->multiply($o1)->multiply($z1);
+ $e = $x1->add($b);
+ $f = $y1->add($d);
+ $z3 = $e->multiply($z1);
+ $h = $f->multiply($x2)->add($z3->multiply($y2));
+ $i = $f->add($z3);
+ $g = $z3->multiply($z3);
+ $p1 = $this->a->multiply($g);
+ $p2 = $f->multiply($i);
+ $p3 = $e->multiply($e)->multiply($e);
+ $x3 = $p1->add($p2)->add($p3);
+ $y3 = $i->multiply($x3)->add($g->multiply($h));
+
+ return [$x3, $y3, $z3];
+ }
+
+ $o2 = $z2->multiply($z2);
+ $a = $x1->multiply($o2);
+ $c = $y1->multiply($o2)->multiply($z2);
+ $d = $y2->multiply($o1)->multiply($z1);
+ $e = $a->add($b);
+ $f = $c->add($d);
+ $g = $e->multiply($z1);
+ $h = $f->multiply($x2)->add($g->multiply($y2));
+ $z3 = $g->multiply($z2);
+ $i = $f->add($z3);
+ $p1 = $this->a->multiply($z3->multiply($z3));
+ $p2 = $f->multiply($i);
+ $p3 = $e->multiply($e)->multiply($e);
+ $x3 = $p1->add($p2)->add($p3);
+ $y3 = $i->multiply($x3)->add($g->multiply($g)->multiply($h));
+
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Doubles a point on a curve
+ *
+ * @return FiniteField[]
+ */
+ public function doublePoint(array $p)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p)) {
+ return [];
+ }
+
+ if (!isset($p[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
+ }
+
+ // formulas from http://hyperelliptic.org/EFD/g12o/auto-shortw-jacobian.html
+
+ list($x1, $y1, $z1) = $p;
+
+ $a = $x1->multiply($x1);
+ $b = $a->multiply($a);
+
+ if ($z1->equals($this->one)) {
+ $x3 = $b->add($this->b);
+ $z3 = clone $x1;
+ $p1 = $a->add($y1)->add($z3)->multiply($this->b);
+ $p2 = $a->add($y1)->multiply($b);
+ $y3 = $p1->add($p2);
+
+ return [$x3, $y3, $z3];
+ }
+
+ $c = $z1->multiply($z1);
+ $d = $c->multiply($c);
+ $x3 = $b->add($this->b->multiply($d->multiply($d)));
+ $z3 = $x1->multiply($c);
+ $p1 = $b->multiply($z3);
+ $p2 = $a->add($y1->multiply($z1))->add($z3)->multiply($x3);
+ $y3 = $p1->add($p2);
+
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Returns the X coordinate and the derived Y coordinate
+ *
+ * Not supported because it is covered by patents.
+ * Quoting https://www.openssl.org/docs/man1.1.0/apps/ecparam.html ,
+ *
+ * "Due to patent issues the compressed option is disabled by default for binary curves
+ * and can be enabled by defining the preprocessor macro OPENSSL_EC_BIN_PT_COMP at
+ * compile time."
+ *
+ * @return array
+ */
+ public function derivePoint($m)
+ {
+ throw new \RuntimeException('Point compression on binary finite field elliptic curves is not supported');
+ }
+
+ /**
+ * Tests whether or not the x / y values satisfy the equation
+ *
+ * @return boolean
+ */
+ public function verifyPoint(array $p)
+ {
+ list($x, $y) = $p;
+ $lhs = $y->multiply($y);
+ $lhs = $lhs->add($x->multiply($y));
+ $x2 = $x->multiply($x);
+ $x3 = $x2->multiply($x);
+ $rhs = $x3->add($this->a->multiply($x2))->add($this->b);
+
+ return $lhs->equals($rhs);
+ }
+
+ /**
+ * Returns the modulo
+ *
+ * @return BigInteger
+ */
+ public function getModulo()
+ {
+ return $this->modulo;
+ }
+
+ /**
+ * Returns the a coefficient
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer
+ */
+ public function getA()
+ {
+ return $this->a;
+ }
+
+ /**
+ * Returns the a coefficient
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer
+ */
+ public function getB()
+ {
+ return $this->b;
+ }
+
+ /**
+ * Returns the affine point
+ *
+ * A Jacobian Coordinate is of the form (x, y, z).
+ * To convert a Jacobian Coordinate to an Affine Point
+ * you do (x / z^2, y / z^3)
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer[]
+ */
+ public function convertToAffine(array $p)
+ {
+ if (!isset($p[2])) {
+ return $p;
+ }
+ list($x, $y, $z) = $p;
+ $z = $this->one->divide($z);
+ $z2 = $z->multiply($z);
+ return [
+ $x->multiply($z2),
+ $y->multiply($z2)->multiply($z)
+ ];
+ }
+
+ /**
+ * Converts an affine point to a jacobian coordinate
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer[]
+ */
+ public function convertToInternal(array $p)
+ {
+ if (isset($p[2])) {
+ return $p;
+ }
+
+ $p[2] = clone $this->one;
+ $p['fresh'] = true;
+ return $p;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php b/Sources/Phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php
new file mode 100644
index 0000000000..d8492ebc28
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php
@@ -0,0 +1,335 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\BaseCurves;
+
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\PrimeField;
+
+/**
+ * Curves over y^2 = x^3 + b
+ *
+ * @author Jim Wigginton
+ */
+class KoblitzPrime extends Prime
+{
+ /**
+ * Basis
+ *
+ * @var list
+ */
+ protected $basis;
+
+ /**
+ * Beta
+ *
+ * @var PrimeField\Integer
+ */
+ protected $beta;
+
+ // don't overwrite setCoefficients() with one that only accepts one parameter so that
+ // one might be able to switch between KoblitzPrime and Prime more easily (for benchmarking
+ // purposes).
+
+ /**
+ * Multiply and Add Points
+ *
+ * Uses a efficiently computable endomorphism to achieve a slight speedup
+ *
+ * Adapted from:
+ * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/short.js#L219
+ *
+ * @return int[]
+ */
+ public function multiplyAddPoints(array $points, array $scalars)
+ {
+ static $zero, $one, $two;
+ if (!isset($two)) {
+ $two = new BigInteger(2);
+ $one = new BigInteger(1);
+ }
+
+ if (!isset($this->beta)) {
+ // get roots
+ $inv = $this->one->divide($this->two)->negate();
+ $s = $this->three->negate()->squareRoot()->multiply($inv);
+ $betas = [
+ $inv->add($s),
+ $inv->subtract($s)
+ ];
+ $this->beta = $betas[0]->compare($betas[1]) < 0 ? $betas[0] : $betas[1];
+ //echo strtoupper($this->beta->toHex(true)) . "\n"; exit;
+ }
+
+ if (!isset($this->basis)) {
+ $factory = new PrimeField($this->order);
+ $tempOne = $factory->newInteger($one);
+ $tempTwo = $factory->newInteger($two);
+ $tempThree = $factory->newInteger(new BigInteger(3));
+
+ $inv = $tempOne->divide($tempTwo)->negate();
+ $s = $tempThree->negate()->squareRoot()->multiply($inv);
+
+ $lambdas = [
+ $inv->add($s),
+ $inv->subtract($s)
+ ];
+
+ $lhs = $this->multiplyPoint($this->p, $lambdas[0])[0];
+ $rhs = $this->p[0]->multiply($this->beta);
+ $lambda = $lhs->equals($rhs) ? $lambdas[0] : $lambdas[1];
+
+ $this->basis = static::extendedGCD($lambda->toBigInteger(), $this->order);
+ ///*
+ foreach ($this->basis as $basis) {
+ echo strtoupper($basis['a']->toHex(true)) . "\n";
+ echo strtoupper($basis['b']->toHex(true)) . "\n\n";
+ }
+ exit;
+ //*/
+ }
+
+ $npoints = $nscalars = [];
+ for ($i = 0; $i < count($points); $i++) {
+ $p = $points[$i];
+ $k = $scalars[$i]->toBigInteger();
+
+ // begin split
+ list($v1, $v2) = $this->basis;
+
+ $c1 = $v2['b']->multiply($k);
+ list($c1, $r) = $c1->divide($this->order);
+ if ($this->order->compare($r->multiply($two)) <= 0) {
+ $c1 = $c1->add($one);
+ }
+
+ $c2 = $v1['b']->negate()->multiply($k);
+ list($c2, $r) = $c2->divide($this->order);
+ if ($this->order->compare($r->multiply($two)) <= 0) {
+ $c2 = $c2->add($one);
+ }
+
+ $p1 = $c1->multiply($v1['a']);
+ $p2 = $c2->multiply($v2['a']);
+ $q1 = $c1->multiply($v1['b']);
+ $q2 = $c2->multiply($v2['b']);
+
+ $k1 = $k->subtract($p1)->subtract($p2);
+ $k2 = $q1->add($q2)->negate();
+ // end split
+
+ $beta = [
+ $p[0]->multiply($this->beta),
+ $p[1],
+ clone $this->one
+ ];
+
+ if (isset($p['naf'])) {
+ $beta['naf'] = array_map(function ($p) {
+ return [
+ $p[0]->multiply($this->beta),
+ $p[1],
+ clone $this->one
+ ];
+ }, $p['naf']);
+ $beta['nafwidth'] = $p['nafwidth'];
+ }
+
+ if ($k1->isNegative()) {
+ $k1 = $k1->negate();
+ $p = $this->negatePoint($p);
+ }
+
+ if ($k2->isNegative()) {
+ $k2 = $k2->negate();
+ $beta = $this->negatePoint($beta);
+ }
+
+ $pos = 2 * $i;
+ $npoints[$pos] = $p;
+ $nscalars[$pos] = $this->factory->newInteger($k1);
+
+ $pos++;
+ $npoints[$pos] = $beta;
+ $nscalars[$pos] = $this->factory->newInteger($k2);
+ }
+
+ return parent::multiplyAddPoints($npoints, $nscalars);
+ }
+
+ /**
+ * Returns the numerator and denominator of the slope
+ *
+ * @return FiniteField[]
+ */
+ protected function doublePointHelper(array $p)
+ {
+ $numerator = $this->three->multiply($p[0])->multiply($p[0]);
+ $denominator = $this->two->multiply($p[1]);
+ return [$numerator, $denominator];
+ }
+
+ /**
+ * Doubles a jacobian coordinate on the curve
+ *
+ * See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianDoublePoint(array $p)
+ {
+ list($x1, $y1, $z1) = $p;
+ $a = $x1->multiply($x1);
+ $b = $y1->multiply($y1);
+ $c = $b->multiply($b);
+ $d = $x1->add($b);
+ $d = $d->multiply($d)->subtract($a)->subtract($c)->multiply($this->two);
+ $e = $this->three->multiply($a);
+ $f = $e->multiply($e);
+ $x3 = $f->subtract($this->two->multiply($d));
+ $y3 = $e->multiply($d->subtract($x3))->subtract(
+ $this->eight->multiply($c)
+ );
+ $z3 = $this->two->multiply($y1)->multiply($z1);
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Doubles a "fresh" jacobian coordinate on the curve
+ *
+ * See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-mdbl-2007-bl
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianDoublePointMixed(array $p)
+ {
+ list($x1, $y1) = $p;
+ $xx = $x1->multiply($x1);
+ $yy = $y1->multiply($y1);
+ $yyyy = $yy->multiply($yy);
+ $s = $x1->add($yy);
+ $s = $s->multiply($s)->subtract($xx)->subtract($yyyy)->multiply($this->two);
+ $m = $this->three->multiply($xx);
+ $t = $m->multiply($m)->subtract($this->two->multiply($s));
+ $x3 = $t;
+ $y3 = $s->subtract($t);
+ $y3 = $m->multiply($y3)->subtract($this->eight->multiply($yyyy));
+ $z3 = $this->two->multiply($y1);
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Tests whether or not the x / y values satisfy the equation
+ *
+ * @return boolean
+ */
+ public function verifyPoint(array $p)
+ {
+ list($x, $y) = $p;
+ $lhs = $y->multiply($y);
+ $temp = $x->multiply($x)->multiply($x);
+ $rhs = $temp->add($this->b);
+
+ return $lhs->equals($rhs);
+ }
+
+ /**
+ * Calculates the parameters needed from the Euclidean algorithm as discussed at
+ * http://diamond.boisestate.edu/~liljanab/MATH308/GuideToECC.pdf#page=148
+ *
+ * @param BigInteger $u
+ * @param BigInteger $v
+ * @return BigInteger[]
+ */
+ protected static function extendedGCD(BigInteger $u, BigInteger $v)
+ {
+ $one = new BigInteger(1);
+ $zero = new BigInteger();
+
+ $a = clone $one;
+ $b = clone $zero;
+ $c = clone $zero;
+ $d = clone $one;
+
+ $stop = $v->bitwise_rightShift($v->getLength() >> 1);
+
+ $a1 = clone $zero;
+ $b1 = clone $zero;
+ $a2 = clone $zero;
+ $b2 = clone $zero;
+
+ $postGreatestIndex = 0;
+
+ while (!$v->equals($zero)) {
+ list($q) = $u->divide($v);
+
+ $temp = $u;
+ $u = $v;
+ $v = $temp->subtract($v->multiply($q));
+
+ $temp = $a;
+ $a = $c;
+ $c = $temp->subtract($a->multiply($q));
+
+ $temp = $b;
+ $b = $d;
+ $d = $temp->subtract($b->multiply($q));
+
+ if ($v->compare($stop) > 0) {
+ $a0 = $v;
+ $b0 = $c;
+ } else {
+ $postGreatestIndex++;
+ }
+
+ if ($postGreatestIndex == 1) {
+ $a1 = $v;
+ $b1 = $c->negate();
+ }
+
+ if ($postGreatestIndex == 2) {
+ $rhs = $a0->multiply($a0)->add($b0->multiply($b0));
+ $lhs = $v->multiply($v)->add($b->multiply($b));
+ if ($lhs->compare($rhs) <= 0) {
+ $a2 = $a0;
+ $b2 = $b0->negate();
+ } else {
+ $a2 = $v;
+ $b2 = $c->negate();
+ }
+
+ break;
+ }
+ }
+
+ return [
+ ['a' => $a1, 'b' => $b1],
+ ['a' => $a2, 'b' => $b2]
+ ];
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/BaseCurves/Montgomery.php b/Sources/Phpseclib/Crypt/EC/BaseCurves/Montgomery.php
new file mode 100644
index 0000000000..431f9575c5
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/BaseCurves/Montgomery.php
@@ -0,0 +1,279 @@
+
+ * @copyright 2019 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\BaseCurves;
+
+use phpseclib3\Crypt\EC\Curves\Curve25519;
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\PrimeField;
+use phpseclib3\Math\PrimeField\Integer as PrimeInteger;
+
+/**
+ * Curves over y^2 = x^3 + a*x + x
+ *
+ * @author Jim Wigginton
+ */
+class Montgomery extends Base
+{
+ /**
+ * Prime Field Integer factory
+ *
+ * @var PrimeField
+ */
+ protected $factory;
+
+ /**
+ * Cofficient for x
+ *
+ * @var object
+ */
+ protected $a;
+
+ /**
+ * Constant used for point doubling
+ *
+ * @var object
+ */
+ protected $a24;
+
+ /**
+ * The Number Zero
+ *
+ * @var object
+ */
+ protected $zero;
+
+ /**
+ * The Number One
+ *
+ * @var object
+ */
+ protected $one;
+
+ /**
+ * Base Point
+ *
+ * @var object
+ */
+ protected $p;
+
+ /**
+ * The modulo
+ *
+ * @var BigInteger
+ */
+ protected $modulo;
+
+ /**
+ * The Order
+ *
+ * @var BigInteger
+ */
+ protected $order;
+
+ /**
+ * Sets the modulo
+ */
+ public function setModulo(BigInteger $modulo)
+ {
+ $this->modulo = $modulo;
+ $this->factory = new PrimeField($modulo);
+ $this->zero = $this->factory->newInteger(new BigInteger());
+ $this->one = $this->factory->newInteger(new BigInteger(1));
+ }
+
+ /**
+ * Set coefficients a
+ */
+ public function setCoefficients(BigInteger $a)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->a = $this->factory->newInteger($a);
+ $two = $this->factory->newInteger(new BigInteger(2));
+ $four = $this->factory->newInteger(new BigInteger(4));
+ $this->a24 = $this->a->subtract($two)->divide($four);
+ }
+
+ /**
+ * Set x and y coordinates for the base point
+ *
+ * @param BigInteger|PrimeInteger $x
+ * @param BigInteger|PrimeInteger $y
+ * @return PrimeInteger[]
+ */
+ public function setBasePoint($x, $y)
+ {
+ switch (true) {
+ case !$x instanceof BigInteger && !$x instanceof PrimeInteger:
+ throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
+ case !$y instanceof BigInteger && !$y instanceof PrimeInteger:
+ throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
+ }
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->p = [
+ $x instanceof BigInteger ? $this->factory->newInteger($x) : $x,
+ $y instanceof BigInteger ? $this->factory->newInteger($y) : $y
+ ];
+ }
+
+ /**
+ * Retrieve the base point as an array
+ *
+ * @return array
+ */
+ public function getBasePoint()
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ /*
+ if (!isset($this->p)) {
+ throw new \RuntimeException('setBasePoint needs to be called before this method');
+ }
+ */
+ return $this->p;
+ }
+
+ /**
+ * Doubles and adds a point on a curve
+ *
+ * See https://tools.ietf.org/html/draft-ietf-tls-curve25519-01#appendix-A.1.3
+ *
+ * @return FiniteField[][]
+ */
+ private function doubleAndAddPoint(array $p, array $q, PrimeInteger $x1)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p) || !count($q)) {
+ return [];
+ }
+
+ if (!isset($p[1])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to XZ coordinates');
+ }
+
+ list($x2, $z2) = $p;
+ list($x3, $z3) = $q;
+
+ $a = $x2->add($z2);
+ $aa = $a->multiply($a);
+ $b = $x2->subtract($z2);
+ $bb = $b->multiply($b);
+ $e = $aa->subtract($bb);
+ $c = $x3->add($z3);
+ $d = $x3->subtract($z3);
+ $da = $d->multiply($a);
+ $cb = $c->multiply($b);
+ $temp = $da->add($cb);
+ $x5 = $temp->multiply($temp);
+ $temp = $da->subtract($cb);
+ $z5 = $x1->multiply($temp->multiply($temp));
+ $x4 = $aa->multiply($bb);
+ $temp = static::class == Curve25519::class ? $bb : $aa;
+ $z4 = $e->multiply($temp->add($this->a24->multiply($e)));
+
+ return [
+ [$x4, $z4],
+ [$x5, $z5]
+ ];
+ }
+
+ /**
+ * Multiply a point on the curve by a scalar
+ *
+ * Uses the montgomery ladder technique as described here:
+ *
+ * https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
+ * https://github.com/phpecc/phpecc/issues/16#issuecomment-59176772
+ *
+ * @return array
+ */
+ public function multiplyPoint(array $p, BigInteger $d)
+ {
+ $p1 = [$this->one, $this->zero];
+ $alreadyInternal = isset($p[1]);
+ $p2 = $this->convertToInternal($p);
+ $x = $p[0];
+
+ $b = $d->toBits();
+ $b = str_pad($b, 256, '0', STR_PAD_LEFT);
+ for ($i = 0; $i < strlen($b); $i++) {
+ $b_i = (int) $b[$i];
+ if ($b_i) {
+ list($p2, $p1) = $this->doubleAndAddPoint($p2, $p1, $x);
+ } else {
+ list($p1, $p2) = $this->doubleAndAddPoint($p1, $p2, $x);
+ }
+ }
+
+ return $alreadyInternal ? $p1 : $this->convertToAffine($p1);
+ }
+
+ /**
+ * Converts an affine point to an XZ coordinate
+ *
+ * From https://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html
+ *
+ * XZ coordinates represent x y as X Z satsfying the following equations:
+ *
+ * x=X/Z
+ *
+ * @return PrimeInteger[]
+ */
+ public function convertToInternal(array $p)
+ {
+ if (empty($p)) {
+ return [clone $this->zero, clone $this->one];
+ }
+
+ if (isset($p[1])) {
+ return $p;
+ }
+
+ $p[1] = clone $this->one;
+
+ return $p;
+ }
+
+ /**
+ * Returns the affine point
+ *
+ * @return PrimeInteger[]
+ */
+ public function convertToAffine(array $p)
+ {
+ if (!isset($p[1])) {
+ return $p;
+ }
+ list($x, $z) = $p;
+ return [$x->divide($z)];
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/BaseCurves/Prime.php b/Sources/Phpseclib/Crypt/EC/BaseCurves/Prime.php
new file mode 100644
index 0000000000..b1970557f1
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/BaseCurves/Prime.php
@@ -0,0 +1,785 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\BaseCurves;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\Common\FiniteField\Integer;
+use phpseclib3\Math\PrimeField;
+use phpseclib3\Math\PrimeField\Integer as PrimeInteger;
+
+/**
+ * Curves over y^2 = x^3 + a*x + b
+ *
+ * @author Jim Wigginton
+ */
+class Prime extends Base
+{
+ /**
+ * Prime Field Integer factory
+ *
+ * @var \phpseclib3\Math\PrimeFields
+ */
+ protected $factory;
+
+ /**
+ * Cofficient for x^1
+ *
+ * @var object
+ */
+ protected $a;
+
+ /**
+ * Cofficient for x^0
+ *
+ * @var object
+ */
+ protected $b;
+
+ /**
+ * Base Point
+ *
+ * @var object
+ */
+ protected $p;
+
+ /**
+ * The number one over the specified finite field
+ *
+ * @var object
+ */
+ protected $one;
+
+ /**
+ * The number two over the specified finite field
+ *
+ * @var object
+ */
+ protected $two;
+
+ /**
+ * The number three over the specified finite field
+ *
+ * @var object
+ */
+ protected $three;
+
+ /**
+ * The number four over the specified finite field
+ *
+ * @var object
+ */
+ protected $four;
+
+ /**
+ * The number eight over the specified finite field
+ *
+ * @var object
+ */
+ protected $eight;
+
+ /**
+ * The modulo
+ *
+ * @var BigInteger
+ */
+ protected $modulo;
+
+ /**
+ * The Order
+ *
+ * @var BigInteger
+ */
+ protected $order;
+
+ /**
+ * Sets the modulo
+ */
+ public function setModulo(BigInteger $modulo)
+ {
+ $this->modulo = $modulo;
+ $this->factory = new PrimeField($modulo);
+ $this->two = $this->factory->newInteger(new BigInteger(2));
+ $this->three = $this->factory->newInteger(new BigInteger(3));
+ // used by jacobian coordinates
+ $this->one = $this->factory->newInteger(new BigInteger(1));
+ $this->four = $this->factory->newInteger(new BigInteger(4));
+ $this->eight = $this->factory->newInteger(new BigInteger(8));
+ }
+
+ /**
+ * Set coefficients a and b
+ */
+ public function setCoefficients(BigInteger $a, BigInteger $b)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->a = $this->factory->newInteger($a);
+ $this->b = $this->factory->newInteger($b);
+ }
+
+ /**
+ * Set x and y coordinates for the base point
+ *
+ * @param BigInteger|PrimeInteger $x
+ * @param BigInteger|PrimeInteger $y
+ * @return PrimeInteger[]
+ */
+ public function setBasePoint($x, $y)
+ {
+ switch (true) {
+ case !$x instanceof BigInteger && !$x instanceof PrimeInteger:
+ throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
+ case !$y instanceof BigInteger && !$y instanceof PrimeInteger:
+ throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
+ }
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->p = [
+ $x instanceof BigInteger ? $this->factory->newInteger($x) : $x,
+ $y instanceof BigInteger ? $this->factory->newInteger($y) : $y
+ ];
+ }
+
+ /**
+ * Retrieve the base point as an array
+ *
+ * @return array
+ */
+ public function getBasePoint()
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ /*
+ if (!isset($this->p)) {
+ throw new \RuntimeException('setBasePoint needs to be called before this method');
+ }
+ */
+ return $this->p;
+ }
+
+ /**
+ * Adds two "fresh" jacobian form on the curve
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianAddPointMixedXY(array $p, array $q)
+ {
+ list($u1, $s1) = $p;
+ list($u2, $s2) = $q;
+ if ($u1->equals($u2)) {
+ if (!$s1->equals($s2)) {
+ return [];
+ } else {
+ return $this->doublePoint($p);
+ }
+ }
+ $h = $u2->subtract($u1);
+ $r = $s2->subtract($s1);
+ $h2 = $h->multiply($h);
+ $h3 = $h2->multiply($h);
+ $v = $u1->multiply($h2);
+ $x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two));
+ $y3 = $r->multiply(
+ $v->subtract($x3)
+ )->subtract(
+ $s1->multiply($h3)
+ );
+ return [$x3, $y3, $h];
+ }
+
+ /**
+ * Adds one "fresh" jacobian form on the curve
+ *
+ * The second parameter should be the "fresh" one
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianAddPointMixedX(array $p, array $q)
+ {
+ list($u1, $s1, $z1) = $p;
+ list($x2, $y2) = $q;
+
+ $z12 = $z1->multiply($z1);
+
+ $u2 = $x2->multiply($z12);
+ $s2 = $y2->multiply($z12->multiply($z1));
+ if ($u1->equals($u2)) {
+ if (!$s1->equals($s2)) {
+ return [];
+ } else {
+ return $this->doublePoint($p);
+ }
+ }
+ $h = $u2->subtract($u1);
+ $r = $s2->subtract($s1);
+ $h2 = $h->multiply($h);
+ $h3 = $h2->multiply($h);
+ $v = $u1->multiply($h2);
+ $x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two));
+ $y3 = $r->multiply(
+ $v->subtract($x3)
+ )->subtract(
+ $s1->multiply($h3)
+ );
+ $z3 = $h->multiply($z1);
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Adds two jacobian coordinates on the curve
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianAddPoint(array $p, array $q)
+ {
+ list($x1, $y1, $z1) = $p;
+ list($x2, $y2, $z2) = $q;
+
+ $z12 = $z1->multiply($z1);
+ $z22 = $z2->multiply($z2);
+
+ $u1 = $x1->multiply($z22);
+ $u2 = $x2->multiply($z12);
+ $s1 = $y1->multiply($z22->multiply($z2));
+ $s2 = $y2->multiply($z12->multiply($z1));
+ if ($u1->equals($u2)) {
+ if (!$s1->equals($s2)) {
+ return [];
+ } else {
+ return $this->doublePoint($p);
+ }
+ }
+ $h = $u2->subtract($u1);
+ $r = $s2->subtract($s1);
+ $h2 = $h->multiply($h);
+ $h3 = $h2->multiply($h);
+ $v = $u1->multiply($h2);
+ $x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two));
+ $y3 = $r->multiply(
+ $v->subtract($x3)
+ )->subtract(
+ $s1->multiply($h3)
+ );
+ $z3 = $h->multiply($z1)->multiply($z2);
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Adds two points on the curve
+ *
+ * @return FiniteField[]
+ */
+ public function addPoint(array $p, array $q)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p) || !count($q)) {
+ if (count($q)) {
+ return $q;
+ }
+ if (count($p)) {
+ return $p;
+ }
+ return [];
+ }
+
+ // use jacobian coordinates
+ if (isset($p[2]) && isset($q[2])) {
+ if (isset($p['fresh']) && isset($q['fresh'])) {
+ return $this->jacobianAddPointMixedXY($p, $q);
+ }
+ if (isset($p['fresh'])) {
+ return $this->jacobianAddPointMixedX($q, $p);
+ }
+ if (isset($q['fresh'])) {
+ return $this->jacobianAddPointMixedX($p, $q);
+ }
+ return $this->jacobianAddPoint($p, $q);
+ }
+
+ if (isset($p[2]) || isset($q[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to Jacobi coordinates or vice versa');
+ }
+
+ if ($p[0]->equals($q[0])) {
+ if (!$p[1]->equals($q[1])) {
+ return [];
+ } else { // eg. doublePoint
+ list($numerator, $denominator) = $this->doublePointHelper($p);
+ }
+ } else {
+ $numerator = $q[1]->subtract($p[1]);
+ $denominator = $q[0]->subtract($p[0]);
+ }
+ $slope = $numerator->divide($denominator);
+ $x = $slope->multiply($slope)->subtract($p[0])->subtract($q[0]);
+ $y = $slope->multiply($p[0]->subtract($x))->subtract($p[1]);
+
+ return [$x, $y];
+ }
+
+ /**
+ * Returns the numerator and denominator of the slope
+ *
+ * @return FiniteField[]
+ */
+ protected function doublePointHelper(array $p)
+ {
+ $numerator = $this->three->multiply($p[0])->multiply($p[0])->add($this->a);
+ $denominator = $this->two->multiply($p[1]);
+ return [$numerator, $denominator];
+ }
+
+ /**
+ * Doubles a jacobian coordinate on the curve
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianDoublePoint(array $p)
+ {
+ list($x, $y, $z) = $p;
+ $x2 = $x->multiply($x);
+ $y2 = $y->multiply($y);
+ $z2 = $z->multiply($z);
+ $s = $this->four->multiply($x)->multiply($y2);
+ $m1 = $this->three->multiply($x2);
+ $m2 = $this->a->multiply($z2->multiply($z2));
+ $m = $m1->add($m2);
+ $x1 = $m->multiply($m)->subtract($this->two->multiply($s));
+ $y1 = $m->multiply($s->subtract($x1))->subtract(
+ $this->eight->multiply($y2->multiply($y2))
+ );
+ $z1 = $this->two->multiply($y)->multiply($z);
+ return [$x1, $y1, $z1];
+ }
+
+ /**
+ * Doubles a "fresh" jacobian coordinate on the curve
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianDoublePointMixed(array $p)
+ {
+ list($x, $y) = $p;
+ $x2 = $x->multiply($x);
+ $y2 = $y->multiply($y);
+ $s = $this->four->multiply($x)->multiply($y2);
+ $m1 = $this->three->multiply($x2);
+ $m = $m1->add($this->a);
+ $x1 = $m->multiply($m)->subtract($this->two->multiply($s));
+ $y1 = $m->multiply($s->subtract($x1))->subtract(
+ $this->eight->multiply($y2->multiply($y2))
+ );
+ $z1 = $this->two->multiply($y);
+ return [$x1, $y1, $z1];
+ }
+
+ /**
+ * Doubles a point on a curve
+ *
+ * @return FiniteField[]
+ */
+ public function doublePoint(array $p)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p)) {
+ return [];
+ }
+
+ // use jacobian coordinates
+ if (isset($p[2])) {
+ if (isset($p['fresh'])) {
+ return $this->jacobianDoublePointMixed($p);
+ }
+ return $this->jacobianDoublePoint($p);
+ }
+
+ list($numerator, $denominator) = $this->doublePointHelper($p);
+
+ $slope = $numerator->divide($denominator);
+
+ $x = $slope->multiply($slope)->subtract($p[0])->subtract($p[0]);
+ $y = $slope->multiply($p[0]->subtract($x))->subtract($p[1]);
+
+ return [$x, $y];
+ }
+
+ /**
+ * Returns the X coordinate and the derived Y coordinate
+ *
+ * @return array
+ */
+ public function derivePoint($m)
+ {
+ $y = ord(Strings::shift($m));
+ $x = new BigInteger($m, 256);
+ $xp = $this->convertInteger($x);
+ switch ($y) {
+ case 2:
+ $ypn = false;
+ break;
+ case 3:
+ $ypn = true;
+ break;
+ default:
+ throw new \RuntimeException('Coordinate not in recognized format');
+ }
+ $temp = $xp->multiply($this->a);
+ $temp = $xp->multiply($xp)->multiply($xp)->add($temp);
+ $temp = $temp->add($this->b);
+ $b = $temp->squareRoot();
+ if (!$b) {
+ throw new \RuntimeException('Unable to derive Y coordinate');
+ }
+ $bn = $b->isOdd();
+ $yp = $ypn == $bn ? $b : $b->negate();
+ return [$xp, $yp];
+ }
+
+ /**
+ * Tests whether or not the x / y values satisfy the equation
+ *
+ * @return boolean
+ */
+ public function verifyPoint(array $p)
+ {
+ list($x, $y) = $p;
+ $lhs = $y->multiply($y);
+ $temp = $x->multiply($this->a);
+ $temp = $x->multiply($x)->multiply($x)->add($temp);
+ $rhs = $temp->add($this->b);
+
+ return $lhs->equals($rhs);
+ }
+
+ /**
+ * Returns the modulo
+ *
+ * @return BigInteger
+ */
+ public function getModulo()
+ {
+ return $this->modulo;
+ }
+
+ /**
+ * Returns the a coefficient
+ *
+ * @return PrimeInteger
+ */
+ public function getA()
+ {
+ return $this->a;
+ }
+
+ /**
+ * Returns the a coefficient
+ *
+ * @return PrimeInteger
+ */
+ public function getB()
+ {
+ return $this->b;
+ }
+
+ /**
+ * Multiply and Add Points
+ *
+ * Adapted from:
+ * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/base.js#L125
+ *
+ * @return int[]
+ */
+ public function multiplyAddPoints(array $points, array $scalars)
+ {
+ $length = count($points);
+
+ foreach ($points as &$point) {
+ $point = $this->convertToInternal($point);
+ }
+
+ $wnd = [$this->getNAFPoints($points[0], 7)];
+ $wndWidth = [isset($points[0]['nafwidth']) ? $points[0]['nafwidth'] : 7];
+ for ($i = 1; $i < $length; $i++) {
+ $wnd[] = $this->getNAFPoints($points[$i], 1);
+ $wndWidth[] = isset($points[$i]['nafwidth']) ? $points[$i]['nafwidth'] : 1;
+ }
+
+ $naf = [];
+
+ // comb all window NAFs
+
+ $max = 0;
+ for ($i = $length - 1; $i >= 1; $i -= 2) {
+ $a = $i - 1;
+ $b = $i;
+ if ($wndWidth[$a] != 1 || $wndWidth[$b] != 1) {
+ $naf[$a] = $scalars[$a]->getNAF($wndWidth[$a]);
+ $naf[$b] = $scalars[$b]->getNAF($wndWidth[$b]);
+ $max = max(count($naf[$a]), count($naf[$b]), $max);
+ continue;
+ }
+
+ $comb = [
+ $points[$a], // 1
+ null, // 3
+ null, // 5
+ $points[$b] // 7
+ ];
+
+ $comb[1] = $this->addPoint($points[$a], $points[$b]);
+ $comb[2] = $this->addPoint($points[$a], $this->negatePoint($points[$b]));
+
+ $index = [
+ -3, /* -1 -1 */
+ -1, /* -1 0 */
+ -5, /* -1 1 */
+ -7, /* 0 -1 */
+ 0, /* 0 -1 */
+ 7, /* 0 1 */
+ 5, /* 1 -1 */
+ 1, /* 1 0 */
+ 3 /* 1 1 */
+ ];
+
+ $jsf = self::getJSFPoints($scalars[$a], $scalars[$b]);
+
+ $max = max(count($jsf[0]), $max);
+ if ($max > 0) {
+ $naf[$a] = array_fill(0, $max, 0);
+ $naf[$b] = array_fill(0, $max, 0);
+ } else {
+ $naf[$a] = [];
+ $naf[$b] = [];
+ }
+
+ for ($j = 0; $j < $max; $j++) {
+ $ja = isset($jsf[0][$j]) ? $jsf[0][$j] : 0;
+ $jb = isset($jsf[1][$j]) ? $jsf[1][$j] : 0;
+
+ $naf[$a][$j] = $index[3 * ($ja + 1) + $jb + 1];
+ $naf[$b][$j] = 0;
+ $wnd[$a] = $comb;
+ }
+ }
+
+ $acc = [];
+ $temp = [0, 0, 0, 0];
+ for ($i = $max; $i >= 0; $i--) {
+ $k = 0;
+ while ($i >= 0) {
+ $zero = true;
+ for ($j = 0; $j < $length; $j++) {
+ $temp[$j] = isset($naf[$j][$i]) ? $naf[$j][$i] : 0;
+ if ($temp[$j] != 0) {
+ $zero = false;
+ }
+ }
+ if (!$zero) {
+ break;
+ }
+ $k++;
+ $i--;
+ }
+
+ if ($i >= 0) {
+ $k++;
+ }
+ while ($k--) {
+ $acc = $this->doublePoint($acc);
+ }
+
+ if ($i < 0) {
+ break;
+ }
+
+ for ($j = 0; $j < $length; $j++) {
+ $z = $temp[$j];
+ $p = null;
+ if ($z == 0) {
+ continue;
+ }
+ $p = $z > 0 ?
+ $wnd[$j][($z - 1) >> 1] :
+ $this->negatePoint($wnd[$j][(-$z - 1) >> 1]);
+ $acc = $this->addPoint($acc, $p);
+ }
+ }
+
+ return $this->convertToAffine($acc);
+ }
+
+ /**
+ * Precomputes NAF points
+ *
+ * Adapted from:
+ * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/base.js#L351
+ *
+ * @return int[]
+ */
+ private function getNAFPoints(array $point, $wnd)
+ {
+ if (isset($point['naf'])) {
+ return $point['naf'];
+ }
+
+ $res = [$point];
+ $max = (1 << $wnd) - 1;
+ $dbl = $max == 1 ? null : $this->doublePoint($point);
+ for ($i = 1; $i < $max; $i++) {
+ $res[] = $this->addPoint($res[$i - 1], $dbl);
+ }
+
+ $point['naf'] = $res;
+
+ /*
+ $str = '';
+ foreach ($res as $re) {
+ $re[0] = bin2hex($re[0]->toBytes());
+ $re[1] = bin2hex($re[1]->toBytes());
+ $str.= " ['$re[0]', '$re[1]'],\r\n";
+ }
+ file_put_contents('temp.txt', $str);
+ exit;
+ */
+
+ return $res;
+ }
+
+ /**
+ * Precomputes points in Joint Sparse Form
+ *
+ * Adapted from:
+ * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/utils.js#L96
+ *
+ * @return int[]
+ */
+ private static function getJSFPoints(Integer $k1, Integer $k2)
+ {
+ static $three;
+ if (!isset($three)) {
+ $three = new BigInteger(3);
+ }
+
+ $jsf = [[], []];
+ $k1 = $k1->toBigInteger();
+ $k2 = $k2->toBigInteger();
+ $d1 = 0;
+ $d2 = 0;
+
+ while ($k1->compare(new BigInteger(-$d1)) > 0 || $k2->compare(new BigInteger(-$d2)) > 0) {
+ // first phase
+ $m14 = $k1->testBit(0) + 2 * $k1->testBit(1);
+ $m14 += $d1;
+ $m14 &= 3;
+
+ $m24 = $k2->testBit(0) + 2 * $k2->testBit(1);
+ $m24 += $d2;
+ $m24 &= 3;
+
+ if ($m14 == 3) {
+ $m14 = -1;
+ }
+ if ($m24 == 3) {
+ $m24 = -1;
+ }
+
+ $u1 = 0;
+ if ($m14 & 1) { // if $m14 is odd
+ $m8 = $k1->testBit(0) + 2 * $k1->testBit(1) + 4 * $k1->testBit(2);
+ $m8 += $d1;
+ $m8 &= 7;
+ $u1 = ($m8 == 3 || $m8 == 5) && $m24 == 2 ? -$m14 : $m14;
+ }
+ $jsf[0][] = $u1;
+
+ $u2 = 0;
+ if ($m24 & 1) { // if $m24 is odd
+ $m8 = $k2->testBit(0) + 2 * $k2->testBit(1) + 4 * $k2->testBit(2);
+ $m8 += $d2;
+ $m8 &= 7;
+ $u2 = ($m8 == 3 || $m8 == 5) && $m14 == 2 ? -$m24 : $m24;
+ }
+ $jsf[1][] = $u2;
+
+ // second phase
+ if (2 * $d1 == $u1 + 1) {
+ $d1 = 1 - $d1;
+ }
+ if (2 * $d2 == $u2 + 1) {
+ $d2 = 1 - $d2;
+ }
+ $k1 = $k1->bitwise_rightShift(1);
+ $k2 = $k2->bitwise_rightShift(1);
+ }
+
+ return $jsf;
+ }
+
+ /**
+ * Returns the affine point
+ *
+ * A Jacobian Coordinate is of the form (x, y, z).
+ * To convert a Jacobian Coordinate to an Affine Point
+ * you do (x / z^2, y / z^3)
+ *
+ * @return PrimeInteger[]
+ */
+ public function convertToAffine(array $p)
+ {
+ if (!isset($p[2])) {
+ return $p;
+ }
+ list($x, $y, $z) = $p;
+ $z = $this->one->divide($z);
+ $z2 = $z->multiply($z);
+ return [
+ $x->multiply($z2),
+ $y->multiply($z2)->multiply($z)
+ ];
+ }
+
+ /**
+ * Converts an affine point to a jacobian coordinate
+ *
+ * @return PrimeInteger[]
+ */
+ public function convertToInternal(array $p)
+ {
+ if (isset($p[2])) {
+ return $p;
+ }
+
+ $p[2] = clone $this->one;
+ $p['fresh'] = true;
+ return $p;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php b/Sources/Phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php
new file mode 100644
index 0000000000..99aa38b206
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php
@@ -0,0 +1,215 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\BaseCurves;
+
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\PrimeField;
+use phpseclib3\Math\PrimeField\Integer as PrimeInteger;
+
+/**
+ * Curves over a*x^2 + y^2 = 1 + d*x^2*y^2
+ *
+ * @author Jim Wigginton
+ */
+class TwistedEdwards extends Base
+{
+ /**
+ * The modulo
+ *
+ * @var BigInteger
+ */
+ protected $modulo;
+
+ /**
+ * Cofficient for x^2
+ *
+ * @var object
+ */
+ protected $a;
+
+ /**
+ * Cofficient for x^2*y^2
+ *
+ * @var object
+ */
+ protected $d;
+
+ /**
+ * Base Point
+ *
+ * @var object[]
+ */
+ protected $p;
+
+ /**
+ * The number zero over the specified finite field
+ *
+ * @var object
+ */
+ protected $zero;
+
+ /**
+ * The number one over the specified finite field
+ *
+ * @var object
+ */
+ protected $one;
+
+ /**
+ * The number two over the specified finite field
+ *
+ * @var object
+ */
+ protected $two;
+
+ /**
+ * Sets the modulo
+ */
+ public function setModulo(BigInteger $modulo)
+ {
+ $this->modulo = $modulo;
+ $this->factory = new PrimeField($modulo);
+ $this->zero = $this->factory->newInteger(new BigInteger(0));
+ $this->one = $this->factory->newInteger(new BigInteger(1));
+ $this->two = $this->factory->newInteger(new BigInteger(2));
+ }
+
+ /**
+ * Set coefficients a and b
+ */
+ public function setCoefficients(BigInteger $a, BigInteger $d)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->a = $this->factory->newInteger($a);
+ $this->d = $this->factory->newInteger($d);
+ }
+
+ /**
+ * Set x and y coordinates for the base point
+ */
+ public function setBasePoint($x, $y)
+ {
+ switch (true) {
+ case !$x instanceof BigInteger && !$x instanceof PrimeInteger:
+ throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
+ case !$y instanceof BigInteger && !$y instanceof PrimeInteger:
+ throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
+ }
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->p = [
+ $x instanceof BigInteger ? $this->factory->newInteger($x) : $x,
+ $y instanceof BigInteger ? $this->factory->newInteger($y) : $y
+ ];
+ }
+
+ /**
+ * Returns the a coefficient
+ *
+ * @return PrimeInteger
+ */
+ public function getA()
+ {
+ return $this->a;
+ }
+
+ /**
+ * Returns the a coefficient
+ *
+ * @return PrimeInteger
+ */
+ public function getD()
+ {
+ return $this->d;
+ }
+
+ /**
+ * Retrieve the base point as an array
+ *
+ * @return array
+ */
+ public function getBasePoint()
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ /*
+ if (!isset($this->p)) {
+ throw new \RuntimeException('setBasePoint needs to be called before this method');
+ }
+ */
+ return $this->p;
+ }
+
+ /**
+ * Returns the affine point
+ *
+ * @return PrimeInteger[]
+ */
+ public function convertToAffine(array $p)
+ {
+ if (!isset($p[2])) {
+ return $p;
+ }
+ list($x, $y, $z) = $p;
+ $z = $this->one->divide($z);
+ return [
+ $x->multiply($z),
+ $y->multiply($z)
+ ];
+ }
+
+ /**
+ * Returns the modulo
+ *
+ * @return BigInteger
+ */
+ public function getModulo()
+ {
+ return $this->modulo;
+ }
+
+ /**
+ * Tests whether or not the x / y values satisfy the equation
+ *
+ * @return boolean
+ */
+ public function verifyPoint(array $p)
+ {
+ list($x, $y) = $p;
+ $x2 = $x->multiply($x);
+ $y2 = $y->multiply($y);
+
+ $lhs = $this->a->multiply($x2)->add($y2);
+ $rhs = $this->d->multiply($x2)->multiply($y2)->add($this->one);
+
+ return $lhs->equals($rhs);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/BaseCurves/index.php b/Sources/Phpseclib/Crypt/EC/BaseCurves/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/BaseCurves/index.php
@@ -0,0 +1,8 @@
+
+ * @copyright 2019 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery;
+use phpseclib3\Math\BigInteger;
+
+class Curve25519 extends Montgomery
+{
+ public function __construct()
+ {
+ // 2^255 - 19
+ $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED', 16));
+ $this->a24 = $this->factory->newInteger(new BigInteger('121666'));
+ $this->p = [$this->factory->newInteger(new BigInteger(9))];
+ // 2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed
+ $this->setOrder(new BigInteger('1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED', 16));
+
+ /*
+ $this->setCoefficients(
+ new BigInteger('486662'), // a
+ );
+ $this->setBasePoint(
+ new BigInteger(9),
+ new BigInteger('14781619447589544791020593568409986887264606134616475288964881837755586237401')
+ );
+ */
+ }
+
+ /**
+ * Multiply a point on the curve by a scalar
+ *
+ * Modifies the scalar as described at https://tools.ietf.org/html/rfc7748#page-8
+ *
+ * @return array
+ */
+ public function multiplyPoint(array $p, BigInteger $d)
+ {
+ //$r = strrev(sodium_crypto_scalarmult($d->toBytes(), strrev($p[0]->toBytes())));
+ //return [$this->factory->newInteger(new BigInteger($r, 256))];
+
+ $d = $d->toBytes();
+ $d &= "\xF8" . str_repeat("\xFF", 30) . "\x7F";
+ $d = strrev($d);
+ $d |= "\x40";
+ $d = new BigInteger($d, -256);
+
+ return parent::multiplyPoint($p, $d);
+ }
+
+ /**
+ * Creates a random scalar multiplier
+ *
+ * @return BigInteger
+ */
+ public function createRandomMultiplier()
+ {
+ return BigInteger::random(256);
+ }
+
+ /**
+ * Performs range check
+ */
+ public function rangeCheck(BigInteger $x)
+ {
+ if ($x->getLength() > 256 || $x->isNegative()) {
+ throw new \RangeException('x must be a positive integer less than 256 bytes in length');
+ }
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/Curve448.php b/Sources/Phpseclib/Crypt/EC/Curves/Curve448.php
new file mode 100644
index 0000000000..f4a442315e
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/Curve448.php
@@ -0,0 +1,92 @@
+
+ * @copyright 2019 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery;
+use phpseclib3\Math\BigInteger;
+
+class Curve448 extends Montgomery
+{
+ public function __construct()
+ {
+ // 2^448 - 2^224 - 1
+ $this->setModulo(new BigInteger(
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' .
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
+ 16
+ ));
+ $this->a24 = $this->factory->newInteger(new BigInteger('39081'));
+ $this->p = [$this->factory->newInteger(new BigInteger(5))];
+ // 2^446 - 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d
+ $this->setOrder(new BigInteger(
+ '3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ '7CCA23E9C44EDB49AED63690216CC2728DC58F552378C292AB5844F3',
+ 16
+ ));
+
+ /*
+ $this->setCoefficients(
+ new BigInteger('156326'), // a
+ );
+ $this->setBasePoint(
+ new BigInteger(5),
+ new BigInteger(
+ '355293926785568175264127502063783334808976399387714271831880898' .
+ '435169088786967410002932673765864550910142774147268105838985595290' .
+ '606362')
+ );
+ */
+ }
+
+ /**
+ * Multiply a point on the curve by a scalar
+ *
+ * Modifies the scalar as described at https://tools.ietf.org/html/rfc7748#page-8
+ *
+ * @return array
+ */
+ public function multiplyPoint(array $p, BigInteger $d)
+ {
+ //$r = strrev(sodium_crypto_scalarmult($d->toBytes(), strrev($p[0]->toBytes())));
+ //return [$this->factory->newInteger(new BigInteger($r, 256))];
+
+ $d = $d->toBytes();
+ $d[0] = $d[0] & "\xFC";
+ $d = strrev($d);
+ $d |= "\x80";
+ $d = new BigInteger($d, 256);
+
+ return parent::multiplyPoint($p, $d);
+ }
+
+ /**
+ * Creates a random scalar multiplier
+ *
+ * @return BigInteger
+ */
+ public function createRandomMultiplier()
+ {
+ return BigInteger::random(446);
+ }
+
+ /**
+ * Performs range check
+ */
+ public function rangeCheck(BigInteger $x)
+ {
+ if ($x->getLength() > 448 || $x->isNegative()) {
+ throw new \RangeException('x must be a positive integer less than 446 bytes in length');
+ }
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/Ed25519.php b/Sources/Phpseclib/Crypt/EC/Curves/Ed25519.php
new file mode 100644
index 0000000000..9d3de684f5
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/Ed25519.php
@@ -0,0 +1,333 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Math\BigInteger;
+
+class Ed25519 extends TwistedEdwards
+{
+ const HASH = 'sha512';
+ /*
+ Per https://tools.ietf.org/html/rfc8032#page-6 EdDSA has several parameters, one of which is b:
+
+ 2. An integer b with 2^(b-1) > p. EdDSA public keys have exactly b
+ bits, and EdDSA signatures have exactly 2*b bits. b is
+ recommended to be a multiple of 8, so public key and signature
+ lengths are an integral number of octets.
+
+ SIZE corresponds to b
+ */
+ const SIZE = 32;
+
+ public function __construct()
+ {
+ // 2^255 - 19
+ $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED', 16));
+ $this->setCoefficients(
+ // -1
+ new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC', 16), // a
+ // -121665/121666
+ new BigInteger('52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3', 16) // d
+ );
+ $this->setBasePoint(
+ new BigInteger('216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A', 16),
+ new BigInteger('6666666666666666666666666666666666666666666666666666666666666658', 16)
+ );
+ $this->setOrder(new BigInteger('1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED', 16));
+ // algorithm 14.47 from http://cacr.uwaterloo.ca/hac/about/chap14.pdf#page=16
+ /*
+ $this->setReduction(function($x) {
+ $parts = $x->bitwise_split(255);
+ $className = $this->className;
+
+ if (count($parts) > 2) {
+ list(, $r) = $x->divide($className::$modulo);
+ return $r;
+ }
+
+ $zero = new BigInteger();
+ $c = new BigInteger(19);
+
+ switch (count($parts)) {
+ case 2:
+ list($qi, $ri) = $parts;
+ break;
+ case 1:
+ $qi = $zero;
+ list($ri) = $parts;
+ break;
+ case 0:
+ return $zero;
+ }
+ $r = $ri;
+
+ while ($qi->compare($zero) > 0) {
+ $temp = $qi->multiply($c)->bitwise_split(255);
+ if (count($temp) == 2) {
+ list($qi, $ri) = $temp;
+ } else {
+ $qi = $zero;
+ list($ri) = $temp;
+ }
+ $r = $r->add($ri);
+ }
+
+ while ($r->compare($className::$modulo) > 0) {
+ $r = $r->subtract($className::$modulo);
+ }
+ return $r;
+ });
+ */
+ }
+
+ /**
+ * Recover X from Y
+ *
+ * Implements steps 2-4 at https://tools.ietf.org/html/rfc8032#section-5.1.3
+ *
+ * Used by EC\Keys\Common.php
+ *
+ * @param BigInteger $y
+ * @param boolean $sign
+ * @return object[]
+ */
+ public function recoverX(BigInteger $y, $sign)
+ {
+ $y = $this->factory->newInteger($y);
+
+ $y2 = $y->multiply($y);
+ $u = $y2->subtract($this->one);
+ $v = $this->d->multiply($y2)->add($this->one);
+ $x2 = $u->divide($v);
+ if ($x2->equals($this->zero)) {
+ if ($sign) {
+ throw new \RuntimeException('Unable to recover X coordinate (x2 = 0)');
+ }
+ return clone $this->zero;
+ }
+ // find the square root
+ /* we don't do $x2->squareRoot() because, quoting from
+ https://tools.ietf.org/html/rfc8032#section-5.1.1:
+
+ "For point decoding or "decompression", square roots modulo p are
+ needed. They can be computed using the Tonelli-Shanks algorithm or
+ the special case for p = 5 (mod 8). To find a square root of a,
+ first compute the candidate root x = a^((p+3)/8) (mod p)."
+ */
+ $exp = $this->getModulo()->add(new BigInteger(3));
+ $exp = $exp->bitwise_rightShift(3);
+ $x = $x2->pow($exp);
+
+ // If v x^2 = -u (mod p), set x <-- x * 2^((p-1)/4), which is a square root.
+ if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) {
+ $temp = $this->getModulo()->subtract(new BigInteger(1));
+ $temp = $temp->bitwise_rightShift(2);
+ $temp = $this->two->pow($temp);
+ $x = $x->multiply($temp);
+ if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) {
+ throw new \RuntimeException('Unable to recover X coordinate');
+ }
+ }
+ if ($x->isOdd() != $sign) {
+ $x = $x->negate();
+ }
+
+ return [$x, $y];
+ }
+
+ /**
+ * Extract Secret Scalar
+ *
+ * Implements steps 1-3 at https://tools.ietf.org/html/rfc8032#section-5.1.5
+ *
+ * Used by the various key handlers
+ *
+ * @param string $str
+ * @return array
+ */
+ public function extractSecret($str)
+ {
+ if (strlen($str) != 32) {
+ throw new \LengthException('Private Key should be 32-bytes long');
+ }
+ // 1. Hash the 32-byte private key using SHA-512, storing the digest in
+ // a 64-octet large buffer, denoted h. Only the lower 32 bytes are
+ // used for generating the public key.
+ $hash = new Hash('sha512');
+ $h = $hash->hash($str);
+ $h = substr($h, 0, 32);
+ // 2. Prune the buffer: The lowest three bits of the first octet are
+ // cleared, the highest bit of the last octet is cleared, and the
+ // second highest bit of the last octet is set.
+ $h[0] = $h[0] & chr(0xF8);
+ $h = strrev($h);
+ $h[0] = ($h[0] & chr(0x3F)) | chr(0x40);
+ // 3. Interpret the buffer as the little-endian integer, forming a
+ // secret scalar s.
+ $dA = new BigInteger($h, 256);
+
+ return [
+ 'dA' => $dA,
+ 'secret' => $str
+ ];
+ }
+
+ /**
+ * Encode a point as a string
+ *
+ * @param array $point
+ * @return string
+ */
+ public function encodePoint($point)
+ {
+ list($x, $y) = $point;
+ $y = $y->toBytes();
+ $y[0] = $y[0] & chr(0x7F);
+ if ($x->isOdd()) {
+ $y[0] = $y[0] | chr(0x80);
+ }
+ $y = strrev($y);
+
+ return $y;
+ }
+
+ /**
+ * Creates a random scalar multiplier
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer
+ */
+ public function createRandomMultiplier()
+ {
+ return $this->extractSecret(Random::string(32))['dA'];
+ }
+
+ /**
+ * Converts an affine point to an extended homogeneous coordinate
+ *
+ * From https://tools.ietf.org/html/rfc8032#section-5.1.4 :
+ *
+ * A point (x,y) is represented in extended homogeneous coordinates (X, Y, Z, T),
+ * with x = X/Z, y = Y/Z, x * y = T/Z.
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer[]
+ */
+ public function convertToInternal(array $p)
+ {
+ if (empty($p)) {
+ return [clone $this->zero, clone $this->one, clone $this->one, clone $this->zero];
+ }
+
+ if (isset($p[2])) {
+ return $p;
+ }
+
+ $p[2] = clone $this->one;
+ $p[3] = $p[0]->multiply($p[1]);
+
+ return $p;
+ }
+
+ /**
+ * Doubles a point on a curve
+ *
+ * @return FiniteField[]
+ */
+ public function doublePoint(array $p)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p)) {
+ return [];
+ }
+
+ if (!isset($p[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
+ }
+
+ // from https://tools.ietf.org/html/rfc8032#page-12
+
+ list($x1, $y1, $z1, $t1) = $p;
+
+ $a = $x1->multiply($x1);
+ $b = $y1->multiply($y1);
+ $c = $this->two->multiply($z1)->multiply($z1);
+ $h = $a->add($b);
+ $temp = $x1->add($y1);
+ $e = $h->subtract($temp->multiply($temp));
+ $g = $a->subtract($b);
+ $f = $c->add($g);
+
+ $x3 = $e->multiply($f);
+ $y3 = $g->multiply($h);
+ $t3 = $e->multiply($h);
+ $z3 = $f->multiply($g);
+
+ return [$x3, $y3, $z3, $t3];
+ }
+
+ /**
+ * Adds two points on the curve
+ *
+ * @return FiniteField[]
+ */
+ public function addPoint(array $p, array $q)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p) || !count($q)) {
+ if (count($q)) {
+ return $q;
+ }
+ if (count($p)) {
+ return $p;
+ }
+ return [];
+ }
+
+ if (!isset($p[2]) || !isset($q[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
+ }
+
+ if ($p[0]->equals($q[0])) {
+ return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p);
+ }
+
+ // from https://tools.ietf.org/html/rfc8032#page-12
+
+ list($x1, $y1, $z1, $t1) = $p;
+ list($x2, $y2, $z2, $t2) = $q;
+
+ $a = $y1->subtract($x1)->multiply($y2->subtract($x2));
+ $b = $y1->add($x1)->multiply($y2->add($x2));
+ $c = $t1->multiply($this->two)->multiply($this->d)->multiply($t2);
+ $d = $z1->multiply($this->two)->multiply($z2);
+ $e = $b->subtract($a);
+ $f = $d->subtract($c);
+ $g = $d->add($c);
+ $h = $b->add($a);
+
+ $x3 = $e->multiply($f);
+ $y3 = $g->multiply($h);
+ $t3 = $e->multiply($h);
+ $z3 = $f->multiply($g);
+
+ return [$x3, $y3, $z3, $t3];
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/Ed448.php b/Sources/Phpseclib/Crypt/EC/Curves/Ed448.php
new file mode 100644
index 0000000000..5451f909ff
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/Ed448.php
@@ -0,0 +1,273 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Math\BigInteger;
+
+class Ed448 extends TwistedEdwards
+{
+ const HASH = 'shake256-912';
+ const SIZE = 57;
+
+ public function __construct()
+ {
+ // 2^448 - 2^224 - 1
+ $this->setModulo(new BigInteger(
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' .
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
+ 16
+ ));
+ $this->setCoefficients(
+ new BigInteger(1),
+ // -39081
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' .
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6756', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('4F1970C66BED0DED221D15A622BF36DA9E146570470F1767EA6DE324' .
+ 'A3D3A46412AE1AF72AB66511433B80E18B00938E2626A82BC70CC05E', 16),
+ new BigInteger('693F46716EB6BC248876203756C9C7624BEA73736CA3984087789C1E' .
+ '05A0C2D73AD3FF1CE67C39C4FDBD132C4ED7C8AD9808795BF230FA14', 16)
+ );
+ $this->setOrder(new BigInteger(
+ '3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ '7CCA23E9C44EDB49AED63690216CC2728DC58F552378C292AB5844F3',
+ 16
+ ));
+ }
+
+ /**
+ * Recover X from Y
+ *
+ * Implements steps 2-4 at https://tools.ietf.org/html/rfc8032#section-5.2.3
+ *
+ * Used by EC\Keys\Common.php
+ *
+ * @param BigInteger $y
+ * @param boolean $sign
+ * @return object[]
+ */
+ public function recoverX(BigInteger $y, $sign)
+ {
+ $y = $this->factory->newInteger($y);
+
+ $y2 = $y->multiply($y);
+ $u = $y2->subtract($this->one);
+ $v = $this->d->multiply($y2)->subtract($this->one);
+ $x2 = $u->divide($v);
+ if ($x2->equals($this->zero)) {
+ if ($sign) {
+ throw new \RuntimeException('Unable to recover X coordinate (x2 = 0)');
+ }
+ return clone $this->zero;
+ }
+ // find the square root
+ $exp = $this->getModulo()->add(new BigInteger(1));
+ $exp = $exp->bitwise_rightShift(2);
+ $x = $x2->pow($exp);
+
+ if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) {
+ throw new \RuntimeException('Unable to recover X coordinate');
+ }
+ if ($x->isOdd() != $sign) {
+ $x = $x->negate();
+ }
+
+ return [$x, $y];
+ }
+
+ /**
+ * Extract Secret Scalar
+ *
+ * Implements steps 1-3 at https://tools.ietf.org/html/rfc8032#section-5.2.5
+ *
+ * Used by the various key handlers
+ *
+ * @param string $str
+ * @return array
+ */
+ public function extractSecret($str)
+ {
+ if (strlen($str) != 57) {
+ throw new \LengthException('Private Key should be 57-bytes long');
+ }
+ // 1. Hash the 57-byte private key using SHAKE256(x, 114), storing the
+ // digest in a 114-octet large buffer, denoted h. Only the lower 57
+ // bytes are used for generating the public key.
+ $hash = new Hash('shake256-912');
+ $h = $hash->hash($str);
+ $h = substr($h, 0, 57);
+ // 2. Prune the buffer: The two least significant bits of the first
+ // octet are cleared, all eight bits the last octet are cleared, and
+ // the highest bit of the second to last octet is set.
+ $h[0] = $h[0] & chr(0xFC);
+ $h = strrev($h);
+ $h[0] = "\0";
+ $h[1] = $h[1] | chr(0x80);
+ // 3. Interpret the buffer as the little-endian integer, forming a
+ // secret scalar s.
+ $dA = new BigInteger($h, 256);
+
+ return [
+ 'dA' => $dA,
+ 'secret' => $str
+ ];
+
+ $dA->secret = $str;
+ return $dA;
+ }
+
+ /**
+ * Encode a point as a string
+ *
+ * @param array $point
+ * @return string
+ */
+ public function encodePoint($point)
+ {
+ list($x, $y) = $point;
+ $y = "\0" . $y->toBytes();
+ if ($x->isOdd()) {
+ $y[0] = $y[0] | chr(0x80);
+ }
+ $y = strrev($y);
+
+ return $y;
+ }
+
+ /**
+ * Creates a random scalar multiplier
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer
+ */
+ public function createRandomMultiplier()
+ {
+ return $this->extractSecret(Random::string(57))['dA'];
+ }
+
+ /**
+ * Converts an affine point to an extended homogeneous coordinate
+ *
+ * From https://tools.ietf.org/html/rfc8032#section-5.2.4 :
+ *
+ * A point (x,y) is represented in extended homogeneous coordinates (X, Y, Z, T),
+ * with x = X/Z, y = Y/Z, x * y = T/Z.
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer[]
+ */
+ public function convertToInternal(array $p)
+ {
+ if (empty($p)) {
+ return [clone $this->zero, clone $this->one, clone $this->one];
+ }
+
+ if (isset($p[2])) {
+ return $p;
+ }
+
+ $p[2] = clone $this->one;
+
+ return $p;
+ }
+
+ /**
+ * Doubles a point on a curve
+ *
+ * @return FiniteField[]
+ */
+ public function doublePoint(array $p)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p)) {
+ return [];
+ }
+
+ if (!isset($p[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
+ }
+
+ // from https://tools.ietf.org/html/rfc8032#page-18
+
+ list($x1, $y1, $z1) = $p;
+
+ $b = $x1->add($y1);
+ $b = $b->multiply($b);
+ $c = $x1->multiply($x1);
+ $d = $y1->multiply($y1);
+ $e = $c->add($d);
+ $h = $z1->multiply($z1);
+ $j = $e->subtract($this->two->multiply($h));
+
+ $x3 = $b->subtract($e)->multiply($j);
+ $y3 = $c->subtract($d)->multiply($e);
+ $z3 = $e->multiply($j);
+
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Adds two points on the curve
+ *
+ * @return FiniteField[]
+ */
+ public function addPoint(array $p, array $q)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p) || !count($q)) {
+ if (count($q)) {
+ return $q;
+ }
+ if (count($p)) {
+ return $p;
+ }
+ return [];
+ }
+
+ if (!isset($p[2]) || !isset($q[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
+ }
+
+ if ($p[0]->equals($q[0])) {
+ return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p);
+ }
+
+ // from https://tools.ietf.org/html/rfc8032#page-17
+
+ list($x1, $y1, $z1) = $p;
+ list($x2, $y2, $z2) = $q;
+
+ $a = $z1->multiply($z2);
+ $b = $a->multiply($a);
+ $c = $x1->multiply($x2);
+ $d = $y1->multiply($y2);
+ $e = $this->d->multiply($c)->multiply($d);
+ $f = $b->subtract($e);
+ $g = $b->add($e);
+ $h = $x1->add($y1)->multiply($x2->add($y2));
+
+ $x3 = $a->multiply($f)->multiply($h->subtract($c)->subtract($d));
+ $y3 = $a->multiply($g)->multiply($d->subtract($c));
+ $z3 = $f->multiply($g);
+
+ return [$x3, $y3, $z3];
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP160r1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP160r1.php
new file mode 100644
index 0000000000..7bc2272a1c
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP160r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP160r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620F', 16));
+ $this->setCoefficients(
+ new BigInteger('340E7BE2A280EB74E2BE61BADA745D97E8F7C300', 16),
+ new BigInteger('1E589A8595423412134FAA2DBDEC95C8D8675E58', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('BED5AF16EA3F6A4F62938C4631EB5AF7BDBCDBC3', 16),
+ new BigInteger('1667CB477A1A8EC338F94741669C976316DA6321', 16)
+ );
+ $this->setOrder(new BigInteger('E95E4A5F737059DC60DF5991D45029409E60FC09', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP160t1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP160t1.php
new file mode 100644
index 0000000000..ebfb29aeb6
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP160t1.php
@@ -0,0 +1,47 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP160t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620F', 16));
+ $this->setCoefficients(
+ new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620C', 16), // eg. -3
+ new BigInteger('7A556B6DAE535B7B51ED2C4D7DAA7A0B5C55F380', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('B199B13B9B34EFC1397E64BAEB05ACC265FF2378', 16),
+ new BigInteger('ADD6718B7C7C1961F0991B842443772152C9E0AD', 16)
+ );
+ $this->setOrder(new BigInteger('E95E4A5F737059DC60DF5991D45029409E60FC09', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP192r1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP192r1.php
new file mode 100644
index 0000000000..6ec848bc94
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP192r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP192r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297', 16));
+ $this->setCoefficients(
+ new BigInteger('6A91174076B1E0E19C39C031FE8685C1CAE040E5C69A28EF', 16),
+ new BigInteger('469A28EF7C28CCA3DC721D044F4496BCCA7EF4146FBF25C9', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('C0A0647EAAB6A48753B033C56CB0F0900A2F5C4853375FD6', 16),
+ new BigInteger('14B690866ABD5BB88B5F4828C1490002E6773FA2FA299B8F', 16)
+ );
+ $this->setOrder(new BigInteger('C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP192t1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP192t1.php
new file mode 100644
index 0000000000..e6a86bbd39
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP192t1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP192t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297', 16));
+ $this->setCoefficients(
+ new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86294', 16), // eg. -3
+ new BigInteger('13D56FFAEC78681E68F9DEB43B35BEC2FB68542E27897B79', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('3AE9E58C82F63C30282E1FE7BBF43FA72C446AF6F4618129', 16),
+ new BigInteger('097E2C5667C2223A902AB5CA449D0084B7E5B3DE7CCC01C9', 16)
+ );
+ $this->setOrder(new BigInteger('C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP224r1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP224r1.php
new file mode 100644
index 0000000000..3d7d8726a7
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP224r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP224r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF', 16));
+ $this->setCoefficients(
+ new BigInteger('68A5E62CA9CE6C1C299803A6C1530B514E182AD8B0042A59CAD29F43', 16),
+ new BigInteger('2580F63CCFE44138870713B1A92369E33E2135D266DBB372386C400B', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('0D9029AD2C7E5CF4340823B2A87DC68C9E4CE3174C1E6EFDEE12C07D', 16),
+ new BigInteger('58AA56F772C0726F24C6B89E4ECDAC24354B9E99CAA3F6D3761402CD', 16)
+ );
+ $this->setOrder(new BigInteger('D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP224t1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP224t1.php
new file mode 100644
index 0000000000..3d4f9289ca
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP224t1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP224t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF', 16));
+ $this->setCoefficients(
+ new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FC', 16), // eg. -3
+ new BigInteger('4B337D934104CD7BEF271BF60CED1ED20DA14C08B3BB64F18A60888D', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('6AB1E344CE25FF3896424E7FFE14762ECB49F8928AC0C76029B4D580', 16),
+ new BigInteger('0374E9F5143E568CD23F3F4D7C0D4B1E41C8CC0D1C6ABD5F1A46DB4C', 16)
+ );
+ $this->setOrder(new BigInteger('D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP256r1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP256r1.php
new file mode 100644
index 0000000000..5780da763a
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP256r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP256r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377', 16));
+ $this->setCoefficients(
+ new BigInteger('7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9', 16),
+ new BigInteger('26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('8BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262', 16),
+ new BigInteger('547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997', 16)
+ );
+ $this->setOrder(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP256t1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP256t1.php
new file mode 100644
index 0000000000..724d8b8f1d
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP256t1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP256t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377', 16));
+ $this->setCoefficients(
+ new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5374', 16), // eg. -3
+ new BigInteger('662C61C430D84EA4FE66A7733D0B76B7BF93EBC4AF2F49256AE58101FEE92B04', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('A3E8EB3CC1CFE7B7732213B23A656149AFA142C47AAFBC2B79A191562E1305F4', 16),
+ new BigInteger('2D996C823439C56D7F7B22E14644417E69BCB6DE39D027001DABE8F35B25C9BE', 16)
+ );
+ $this->setOrder(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP320r1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP320r1.php
new file mode 100644
index 0000000000..182e62270b
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP320r1.php
@@ -0,0 +1,40 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP320r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F9' .
+ '2B9EC7893EC28FCD412B1F1B32E27', 16));
+ $this->setCoefficients(
+ new BigInteger('3EE30B568FBAB0F883CCEBD46D3F3BB8A2A73513F5EB79DA66190EB085FFA9F4' .
+ '92F375A97D860EB4', 16),
+ new BigInteger('520883949DFDBC42D3AD198640688A6FE13F41349554B49ACC31DCCD88453981' .
+ '6F5EB4AC8FB1F1A6', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('43BD7E9AFB53D8B85289BCC48EE5BFE6F20137D10A087EB6E7871E2A10A599C7' .
+ '10AF8D0D39E20611', 16),
+ new BigInteger('14FDD05545EC1CC8AB4093247F77275E0743FFED117182EAA9C77877AAAC6AC7' .
+ 'D35245D1692E8EE1', 16)
+ );
+ $this->setOrder(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D4' .
+ '82EC7EE8658E98691555B44C59311', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP320t1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP320t1.php
new file mode 100644
index 0000000000..d5a620d8bf
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP320t1.php
@@ -0,0 +1,40 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP320t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F9' .
+ '2B9EC7893EC28FCD412B1F1B32E27', 16));
+ $this->setCoefficients(
+ new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F92B9EC7893EC28' .
+ 'FCD412B1F1B32E24', 16), // eg. -3
+ new BigInteger('A7F561E038EB1ED560B3D147DB782013064C19F27ED27C6780AAF77FB8A547CE' .
+ 'B5B4FEF422340353', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('925BE9FB01AFC6FB4D3E7D4990010F813408AB106C4F09CB7EE07868CC136FFF' .
+ '3357F624A21BED52', 16),
+ new BigInteger('63BA3A7A27483EBF6671DBEF7ABB30EBEE084E58A0B077AD42A5A0989D1EE71B' .
+ '1B9BC0455FB0D2C3', 16)
+ );
+ $this->setOrder(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D4' .
+ '82EC7EE8658E98691555B44C59311', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP384r1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP384r1.php
new file mode 100644
index 0000000000..a20b4b4465
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP384r1.php
@@ -0,0 +1,58 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP384r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger(
+ '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A7' .
+ '1874700133107EC53',
+ 16
+ ));
+ $this->setCoefficients(
+ new BigInteger(
+ '7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503' .
+ 'AD4EB04A8C7DD22CE2826',
+ 16
+ ),
+ new BigInteger(
+ '4A8C7DD22CE28268B39B55416F0447C2FB77DE107DCD2A62E880EA53EEB62D57CB4390295DB' .
+ 'C9943AB78696FA504C11',
+ 16
+ )
+ );
+ $this->setBasePoint(
+ new BigInteger(
+ '1D1C64F068CF45FFA2A63A81B7C13F6B8847A3E77EF14FE3DB7FCAFE0CBD10E8E826E03436D' .
+ '646AAEF87B2E247D4AF1E',
+ 16
+ ),
+ new BigInteger(
+ '8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E464621779' .
+ '1811142820341263C5315',
+ 16
+ )
+ );
+ $this->setOrder(new BigInteger(
+ '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC31' .
+ '03B883202E9046565',
+ 16
+ ));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP384t1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP384t1.php
new file mode 100644
index 0000000000..366660e68e
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP384t1.php
@@ -0,0 +1,58 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP384t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger(
+ '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A7' .
+ '1874700133107EC53',
+ 16
+ ));
+ $this->setCoefficients(
+ new BigInteger(
+ '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901' .
+ 'D1A71874700133107EC50',
+ 16
+ ), // eg. -3
+ new BigInteger(
+ '7F519EADA7BDA81BD826DBA647910F8C4B9346ED8CCDC64E4B1ABD11756DCE1D2074AA263B8' .
+ '8805CED70355A33B471EE',
+ 16
+ )
+ );
+ $this->setBasePoint(
+ new BigInteger(
+ '18DE98B02DB9A306F2AFCD7235F72A819B80AB12EBD653172476FECD462AABFFC4FF191B946' .
+ 'A5F54D8D0AA2F418808CC',
+ 16
+ ),
+ new BigInteger(
+ '25AB056962D30651A114AFD2755AD336747F93475B7A1FCA3B88F2B6A208CCFE469408584DC' .
+ '2B2912675BF5B9E582928',
+ 16
+ )
+ );
+ $this->setOrder(new BigInteger(
+ '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC31' .
+ '03B883202E9046565',
+ 16
+ ));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP512r1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP512r1.php
new file mode 100644
index 0000000000..5efe5e1ac1
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP512r1.php
@@ -0,0 +1,58 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP512r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger(
+ 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC' .
+ '66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3',
+ 16
+ ));
+ $this->setCoefficients(
+ new BigInteger(
+ '7830A3318B603B89E2327145AC234CC594CBDD8D3DF91610A83441CAEA9863BC2DED5D5AA82' .
+ '53AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CA',
+ 16
+ ),
+ new BigInteger(
+ '3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C' .
+ '1AC4D77FC94CADC083E67984050B75EBAE5DD2809BD638016F723',
+ 16
+ )
+ );
+ $this->setBasePoint(
+ new BigInteger(
+ '81AEE4BDD82ED9645A21322E9C4C6A9385ED9F70B5D916C1B43B62EEF4D0098EFF3B1F78E2D' .
+ '0D48D50D1687B93B97D5F7C6D5047406A5E688B352209BCB9F822',
+ 16
+ ),
+ new BigInteger(
+ '7DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5' .
+ 'F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892',
+ 16
+ )
+ );
+ $this->setOrder(new BigInteger(
+ 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA' .
+ '92619418661197FAC10471DB1D381085DDADDB58796829CA90069',
+ 16
+ ));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP512t1.php b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP512t1.php
new file mode 100644
index 0000000000..745863a63d
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/brainpoolP512t1.php
@@ -0,0 +1,58 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP512t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger(
+ 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC' .
+ '66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3',
+ 16
+ ));
+ $this->setCoefficients(
+ new BigInteger(
+ 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC' .
+ '66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F0',
+ 16
+ ), // eg. -3
+ new BigInteger(
+ '7CBBBCF9441CFAB76E1890E46884EAE321F70C0BCB4981527897504BEC3E36A62BCDFA23049' .
+ '76540F6450085F2DAE145C22553B465763689180EA2571867423E',
+ 16
+ )
+ );
+ $this->setBasePoint(
+ new BigInteger(
+ '640ECE5C12788717B9C1BA06CBC2A6FEBA85842458C56DDE9DB1758D39C0313D82BA51735CD' .
+ 'B3EA499AA77A7D6943A64F7A3F25FE26F06B51BAA2696FA9035DA',
+ 16
+ ),
+ new BigInteger(
+ '5B534BD595F5AF0FA2C892376C84ACE1BB4E3019B71634C01131159CAE03CEE9D9932184BEE' .
+ 'F216BD71DF2DADF86A627306ECFF96DBB8BACE198B61E00F8B332',
+ 16
+ )
+ );
+ $this->setOrder(new BigInteger(
+ 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA' .
+ '92619418661197FAC10471DB1D381085DDADDB58796829CA90069',
+ 16
+ ));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/index.php b/Sources/Phpseclib/Crypt/EC/Curves/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/index.php
@@ -0,0 +1,8 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistb233 extends sect233r1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/nistb409.php b/Sources/Phpseclib/Crypt/EC/Curves/nistb409.php
new file mode 100644
index 0000000000..a46153d3ce
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/nistb409.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistb409 extends sect409r1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/nistk163.php b/Sources/Phpseclib/Crypt/EC/Curves/nistk163.php
new file mode 100644
index 0000000000..8b26376179
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/nistk163.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistk163 extends sect163k1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/nistk233.php b/Sources/Phpseclib/Crypt/EC/Curves/nistk233.php
new file mode 100644
index 0000000000..69e1413822
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/nistk233.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistk233 extends sect233k1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/nistk283.php b/Sources/Phpseclib/Crypt/EC/Curves/nistk283.php
new file mode 100644
index 0000000000..9e95f10e73
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/nistk283.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistk283 extends sect283k1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/nistk409.php b/Sources/Phpseclib/Crypt/EC/Curves/nistk409.php
new file mode 100644
index 0000000000..06bd9af765
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/nistk409.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistk409 extends sect409k1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/nistp192.php b/Sources/Phpseclib/Crypt/EC/Curves/nistp192.php
new file mode 100644
index 0000000000..ddead3cffe
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/nistp192.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistp192 extends secp192r1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/nistp224.php b/Sources/Phpseclib/Crypt/EC/Curves/nistp224.php
new file mode 100644
index 0000000000..746571b4df
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/nistp224.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistp224 extends secp224r1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/nistp256.php b/Sources/Phpseclib/Crypt/EC/Curves/nistp256.php
new file mode 100644
index 0000000000..a26c0f9928
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/nistp256.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistp256 extends secp256r1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/nistp384.php b/Sources/Phpseclib/Crypt/EC/Curves/nistp384.php
new file mode 100644
index 0000000000..1f20c02d1b
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/nistp384.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistp384 extends secp384r1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/nistp521.php b/Sources/Phpseclib/Crypt/EC/Curves/nistp521.php
new file mode 100644
index 0000000000..86fa05084c
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/nistp521.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistp521 extends secp521r1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/nistt571.php b/Sources/Phpseclib/Crypt/EC/Curves/nistt571.php
new file mode 100644
index 0000000000..7908b38b92
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/nistt571.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistt571 extends sect571k1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/prime192v1.php b/Sources/Phpseclib/Crypt/EC/Curves/prime192v1.php
new file mode 100644
index 0000000000..e9c13cd8c7
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/prime192v1.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class prime192v1 extends secp192r1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/prime192v2.php b/Sources/Phpseclib/Crypt/EC/Curves/prime192v2.php
new file mode 100644
index 0000000000..e3e341f264
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/prime192v2.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class prime192v2 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC', 16),
+ new BigInteger('CC22D6DFB95C6B25E49C0D6364A4E5980C393AA21668D953', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('EEA2BAE7E1497842F2DE7769CFE9C989C072AD696F48034A', 16),
+ new BigInteger('6574D11D69B6EC7A672BB82A083DF2F2B0847DE970B2DE15', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFE5FB1A724DC80418648D8DD31', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/prime192v3.php b/Sources/Phpseclib/Crypt/EC/Curves/prime192v3.php
new file mode 100644
index 0000000000..1e97992dc3
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/prime192v3.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class prime192v3 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC', 16),
+ new BigInteger('22123DC2395A05CAA7423DAECCC94760A7D462256BD56916', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('7D29778100C65A1DA1783716588DCE2B8B4AEE8E228F1896', 16),
+ new BigInteger('38A90F22637337334B49DCB66A6DC8F9978ACA7648A943B0', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFF7A62D031C83F4294F640EC13', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/prime239v1.php b/Sources/Phpseclib/Crypt/EC/Curves/prime239v1.php
new file mode 100644
index 0000000000..084be9d7c5
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/prime239v1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class prime239v1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFC', 16),
+ new BigInteger('6B016C3BDCF18941D0D654921475CA71A9DB2FB27D1D37796185C2942C0A', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('0FFA963CDCA8816CCC33B8642BEDF905C3D358573D3F27FBBD3B3CB9AAAF', 16),
+ new BigInteger('7DEBE8E4E90A5DAE6E4054CA530BA04654B36818CE226B39FCCB7B02F1AE', 16)
+ );
+ $this->setOrder(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFF9E5E9A9F5D9071FBD1522688909D0B', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/prime239v2.php b/Sources/Phpseclib/Crypt/EC/Curves/prime239v2.php
new file mode 100644
index 0000000000..21941b834a
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/prime239v2.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class prime239v2 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFC', 16),
+ new BigInteger('617FAB6832576CBBFED50D99F0249C3FEE58B94BA0038C7AE84C8C832F2C', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('38AF09D98727705120C921BB5E9E26296A3CDCF2F35757A0EAFD87B830E7', 16),
+ new BigInteger('5B0125E4DBEA0EC7206DA0FC01D9B081329FB555DE6EF460237DFF8BE4BA', 16)
+ );
+ $this->setOrder(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF800000CFA7E8594377D414C03821BC582063', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/prime239v3.php b/Sources/Phpseclib/Crypt/EC/Curves/prime239v3.php
new file mode 100644
index 0000000000..78c50f0694
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/prime239v3.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class prime239v3 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFC', 16),
+ new BigInteger('255705FA2A306654B1F4CB03D6A750A30C250102D4988717D9BA15AB6D3E', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('6768AE8E18BB92CFCF005C949AA2C6D94853D0E660BBF854B1C9505FE95A', 16),
+ new BigInteger('1607E6898F390C06BC1D552BAD226F3B6FCFE48B6E818499AF18E3ED6CF3', 16)
+ );
+ $this->setOrder(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFF975DEB41B3A6057C3C432146526551', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/prime256v1.php b/Sources/Phpseclib/Crypt/EC/Curves/prime256v1.php
new file mode 100644
index 0000000000..c72b22e8a6
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/prime256v1.php
@@ -0,0 +1,18 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class prime256v1 extends secp256r1
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp112r1.php b/Sources/Phpseclib/Crypt/EC/Curves/secp112r1.php
new file mode 100644
index 0000000000..d1d3194b2a
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp112r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp112r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('DB7C2ABF62E35E668076BEAD208B', 16));
+ $this->setCoefficients(
+ new BigInteger('DB7C2ABF62E35E668076BEAD2088', 16),
+ new BigInteger('659EF8BA043916EEDE8911702B22', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('09487239995A5EE76B55F9C2F098', 16),
+ new BigInteger('A89CE5AF8724C0A23E0E0FF77500', 16)
+ );
+ $this->setOrder(new BigInteger('DB7C2ABF62E35E7628DFAC6561C5', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp112r2.php b/Sources/Phpseclib/Crypt/EC/Curves/secp112r2.php
new file mode 100644
index 0000000000..da44e7fd82
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp112r2.php
@@ -0,0 +1,35 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp112r2 extends Prime
+{
+ public function __construct()
+ {
+ // same modulo as secp112r1
+ $this->setModulo(new BigInteger('DB7C2ABF62E35E668076BEAD208B', 16));
+ $this->setCoefficients(
+ new BigInteger('6127C24C05F38A0AAAF65C0EF02C', 16),
+ new BigInteger('51DEF1815DB5ED74FCC34C85D709', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('4BA30AB5E892B4E1649DD0928643', 16),
+ new BigInteger('ADCD46F5882E3747DEF36E956E97', 16)
+ );
+ $this->setOrder(new BigInteger('36DF0AAFD8B8D7597CA10520D04B', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp128r1.php b/Sources/Phpseclib/Crypt/EC/Curves/secp128r1.php
new file mode 100644
index 0000000000..34456bc075
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp128r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp128r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFC', 16),
+ new BigInteger('E87579C11079F43DD824993C2CEE5ED3', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('161FF7528B899B2D0C28607CA52C5B86', 16),
+ new BigInteger('CF5AC8395BAFEB13C02DA292DDED7A83', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFE0000000075A30D1B9038A115', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp128r2.php b/Sources/Phpseclib/Crypt/EC/Curves/secp128r2.php
new file mode 100644
index 0000000000..e102c3409a
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp128r2.php
@@ -0,0 +1,35 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp128r2 extends Prime
+{
+ public function __construct()
+ {
+ // same as secp128r1
+ $this->setModulo(new BigInteger('FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('D6031998D1B3BBFEBF59CC9BBFF9AEE1', 16),
+ new BigInteger('5EEEFCA380D02919DC2C6558BB6D8A5D', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('7B6AA5D85E572983E6FB32A7CDEBC140', 16),
+ new BigInteger('27B6916A894D3AEE7106FE805FC34B44', 16)
+ );
+ $this->setOrder(new BigInteger('3FFFFFFF7FFFFFFFBE0024720613B5A3', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp160k1.php b/Sources/Phpseclib/Crypt/EC/Curves/secp160k1.php
new file mode 100644
index 0000000000..c6a33344a1
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp160k1.php
@@ -0,0 +1,46 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\KoblitzPrime;
+use phpseclib3\Math\BigInteger;
+
+class secp160k1 extends KoblitzPrime
+{
+ public function __construct()
+ {
+ // same as secp160r2
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73', 16));
+ $this->setCoefficients(
+ new BigInteger('0000000000000000000000000000000000000000', 16),
+ new BigInteger('0000000000000000000000000000000000000007', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('3B4C382CE37AA192A4019E763036F4F5DD4D7EBB', 16),
+ new BigInteger('938CF935318FDCED6BC28286531733C3F03C4FEE', 16)
+ );
+ $this->setOrder(new BigInteger('0100000000000000000001B8FA16DFAB9ACA16B6B3', 16));
+
+ $this->basis = [];
+ $this->basis[] = [
+ 'a' => new BigInteger('0096341F1138933BC2F505', -16),
+ 'b' => new BigInteger('FF6E9D0418C67BB8D5F562', -16)
+ ];
+ $this->basis[] = [
+ 'a' => new BigInteger('01BDCB3A09AAAABEAFF4A8', -16),
+ 'b' => new BigInteger('04D12329FF0EF498EA67', -16)
+ ];
+ $this->beta = $this->factory->newInteger(new BigInteger('645B7345A143464942CC46D7CF4D5D1E1E6CBB68', -16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp160r1.php b/Sources/Phpseclib/Crypt/EC/Curves/secp160r1.php
new file mode 100644
index 0000000000..af46877490
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp160r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp160r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFC', 16),
+ new BigInteger('1C97BEFC54BD7A8B65ACF89F81D4D4ADC565FA45', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('4A96B5688EF573284664698968C38BB913CBFC82', 16),
+ new BigInteger('23A628553168947D59DCC912042351377AC5FB32', 16)
+ );
+ $this->setOrder(new BigInteger('0100000000000000000001F4C8F927AED3CA752257', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp160r2.php b/Sources/Phpseclib/Crypt/EC/Curves/secp160r2.php
new file mode 100644
index 0000000000..9bd23d23c0
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp160r2.php
@@ -0,0 +1,35 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp160r2 extends Prime
+{
+ public function __construct()
+ {
+ // same as secp160k1
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC70', 16),
+ new BigInteger('B4E134D3FB59EB8BAB57274904664D5AF50388BA', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('52DCB034293A117E1F4FF11B30F7199D3144CE6D', 16),
+ new BigInteger('FEAFFEF2E331F296E071FA0DF9982CFEA7D43F2E', 16)
+ );
+ $this->setOrder(new BigInteger('0100000000000000000000351EE786A818F3A1A16B', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp192k1.php b/Sources/Phpseclib/Crypt/EC/Curves/secp192k1.php
new file mode 100644
index 0000000000..79ff2e097a
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp192k1.php
@@ -0,0 +1,45 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\KoblitzPrime;
+use phpseclib3\Math\BigInteger;
+
+class secp192k1 extends KoblitzPrime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37', 16));
+ $this->setCoefficients(
+ new BigInteger('000000000000000000000000000000000000000000000000', 16),
+ new BigInteger('000000000000000000000000000000000000000000000003', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D', 16),
+ new BigInteger('9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D', 16));
+
+ $this->basis = [];
+ $this->basis[] = [
+ 'a' => new BigInteger('00B3FB3400DEC5C4ADCEB8655C', -16),
+ 'b' => new BigInteger('8EE96418CCF4CFC7124FDA0F', -16)
+ ];
+ $this->basis[] = [
+ 'a' => new BigInteger('01D90D03E8F096B9948B20F0A9', -16),
+ 'b' => new BigInteger('42E49819ABBA9474E1083F6B', -16)
+ ];
+ $this->beta = $this->factory->newInteger(new BigInteger('447A96E6C647963E2F7809FEAAB46947F34B0AA3CA0BBA74', -16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp192r1.php b/Sources/Phpseclib/Crypt/EC/Curves/secp192r1.php
new file mode 100644
index 0000000000..83ab1c7060
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp192r1.php
@@ -0,0 +1,78 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp192r1 extends Prime
+{
+ public function __construct()
+ {
+ $modulo = new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF', 16);
+ $this->setModulo($modulo);
+
+ // algorithm 2.27 from http://diamond.boisestate.edu/~liljanab/MATH308/GuideToECC.pdf#page=66
+ /* in theory this should be faster than regular modular reductions save for one small issue.
+ to convert to / from base-2**8 with BCMath you have to call bcmul() and bcdiv() a lot.
+ to convert to / from base-2**8 with PHP64 you have to call base256_rshift() a lot.
+ in short, converting to / from base-2**8 is pretty expensive and that expense is
+ enough to offset whatever else might be gained by a simplified reduction algorithm.
+ now, if PHP supported unsigned integers things might be different. no bit-shifting
+ would be required for the PHP engine and it'd be a lot faster. but as is, BigInteger
+ uses base-2**31 or base-2**26 depending on whether or not the system is has a 32-bit
+ or a 64-bit OS.
+ */
+ /*
+ $m_length = $this->getLengthInBytes();
+ $this->setReduction(function($c) use ($m_length) {
+ $cBytes = $c->toBytes();
+ $className = $this->className;
+
+ if (strlen($cBytes) > 2 * $m_length) {
+ list(, $r) = $c->divide($className::$modulo);
+ return $r;
+ }
+
+ $c = str_pad($cBytes, 48, "\0", STR_PAD_LEFT);
+ $c = array_reverse(str_split($c, 8));
+
+ $null = "\0\0\0\0\0\0\0\0";
+ $s1 = new BigInteger($c[2] . $c[1] . $c[0], 256);
+ $s2 = new BigInteger($null . $c[3] . $c[3], 256);
+ $s3 = new BigInteger($c[4] . $c[4] . $null, 256);
+ $s4 = new BigInteger($c[5] . $c[5] . $c[5], 256);
+
+ $r = $s1->add($s2)->add($s3)->add($s4);
+ while ($r->compare($className::$modulo) >= 0) {
+ $r = $r->subtract($className::$modulo);
+ }
+
+ return $r;
+ });
+ */
+
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC', 16),
+ new BigInteger('64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012', 16),
+ new BigInteger('07192B95FFC8DA78631011ED6B24CDD573F977A11E794811', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp224k1.php b/Sources/Phpseclib/Crypt/EC/Curves/secp224k1.php
new file mode 100644
index 0000000000..79a5c5417b
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp224k1.php
@@ -0,0 +1,45 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\KoblitzPrime;
+use phpseclib3\Math\BigInteger;
+
+class secp224k1 extends KoblitzPrime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D', 16));
+ $this->setCoefficients(
+ new BigInteger('00000000000000000000000000000000000000000000000000000000', 16),
+ new BigInteger('00000000000000000000000000000000000000000000000000000005', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C', 16),
+ new BigInteger('7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5', 16)
+ );
+ $this->setOrder(new BigInteger('010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7', 16));
+
+ $this->basis = [];
+ $this->basis[] = [
+ 'a' => new BigInteger('00B8ADF1378A6EB73409FA6C9C637D', -16),
+ 'b' => new BigInteger('94730F82B358A3776A826298FA6F', -16)
+ ];
+ $this->basis[] = [
+ 'a' => new BigInteger('01DCE8D2EC6184CAF0A972769FCC8B', -16),
+ 'b' => new BigInteger('4D2100BA3DC75AAB747CCF355DEC', -16)
+ ];
+ $this->beta = $this->factory->newInteger(new BigInteger('01F178FFA4B17C89E6F73AECE2AAD57AF4C0A748B63C830947B27E04', -16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp224r1.php b/Sources/Phpseclib/Crypt/EC/Curves/secp224r1.php
new file mode 100644
index 0000000000..a9e474a3c0
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp224r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp224r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE', 16),
+ new BigInteger('B4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21', 16),
+ new BigInteger('BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp256k1.php b/Sources/Phpseclib/Crypt/EC/Curves/secp256k1.php
new file mode 100644
index 0000000000..462e7a1caa
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp256k1.php
@@ -0,0 +1,49 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+//use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Crypt\EC\BaseCurves\KoblitzPrime;
+use phpseclib3\Math\BigInteger;
+
+//class secp256k1 extends Prime
+class secp256k1 extends KoblitzPrime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', 16));
+ $this->setCoefficients(
+ new BigInteger('0000000000000000000000000000000000000000000000000000000000000000', 16),
+ new BigInteger('0000000000000000000000000000000000000000000000000000000000000007', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16));
+ $this->setBasePoint(
+ new BigInteger('79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', 16),
+ new BigInteger('483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8', 16)
+ );
+
+ $this->basis = [];
+ $this->basis[] = [
+ 'a' => new BigInteger('3086D221A7D46BCDE86C90E49284EB15', -16),
+ 'b' => new BigInteger('FF1BBC8129FEF177D790AB8056F5401B3D', -16)
+ ];
+ $this->basis[] = [
+ 'a' => new BigInteger('114CA50F7A8E2F3F657C1108D9D44CFD8', -16),
+ 'b' => new BigInteger('3086D221A7D46BCDE86C90E49284EB15', -16)
+ ];
+ $this->beta = $this->factory->newInteger(new BigInteger('7AE96A2B657C07106E64479EAC3434E99CF0497512F58995C1396C28719501EE', -16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp256r1.php b/Sources/Phpseclib/Crypt/EC/Curves/secp256r1.php
new file mode 100644
index 0000000000..9003373cf9
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp256r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp256r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC', 16),
+ new BigInteger('5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296', 16),
+ new BigInteger('4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp384r1.php b/Sources/Phpseclib/Crypt/EC/Curves/secp384r1.php
new file mode 100644
index 0000000000..98764a3419
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp384r1.php
@@ -0,0 +1,52 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp384r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger(
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF',
+ 16
+ ));
+ $this->setCoefficients(
+ new BigInteger(
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC',
+ 16
+ ),
+ new BigInteger(
+ 'B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF',
+ 16
+ )
+ );
+ $this->setBasePoint(
+ new BigInteger(
+ 'AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7',
+ 16
+ ),
+ new BigInteger(
+ '3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F',
+ 16
+ )
+ );
+ $this->setOrder(new BigInteger(
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973',
+ 16
+ ));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/secp521r1.php b/Sources/Phpseclib/Crypt/EC/Curves/secp521r1.php
new file mode 100644
index 0000000000..b89a4ea744
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/secp521r1.php
@@ -0,0 +1,46 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp521r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ 'FFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ 'FFFC', 16),
+ new BigInteger('0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF1' .
+ '09E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B50' .
+ '3F00', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D' .
+ '3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5' .
+ 'BD66', 16),
+ new BigInteger('011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E' .
+ '662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD1' .
+ '6650', 16)
+ );
+ $this->setOrder(new BigInteger('01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ 'FFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E9138' .
+ '6409', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect113r1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect113r1.php
new file mode 100644
index 0000000000..77ec7603a8
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect113r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect113r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(113, 9, 0);
+ $this->setCoefficients(
+ '003088250CA6E7C7FE649CE85820F7',
+ '00E8BEE4D3E2260744188BE0E9C723'
+ );
+ $this->setBasePoint(
+ '009D73616F35F4AB1407D73562C10F',
+ '00A52830277958EE84D1315ED31886'
+ );
+ $this->setOrder(new BigInteger('0100000000000000D9CCEC8A39E56F', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect113r2.php b/Sources/Phpseclib/Crypt/EC/Curves/sect113r2.php
new file mode 100644
index 0000000000..2185d60e34
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect113r2.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect113r2 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(113, 9, 0);
+ $this->setCoefficients(
+ '00689918DBEC7E5A0DD6DFC0AA55C7',
+ '0095E9A9EC9B297BD4BF36E059184F'
+ );
+ $this->setBasePoint(
+ '01A57A6A7B26CA5EF52FCDB8164797',
+ '00B3ADC94ED1FE674C06E695BABA1D'
+ );
+ $this->setOrder(new BigInteger('010000000000000108789B2496AF93', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect131r1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect131r1.php
new file mode 100644
index 0000000000..1365cb6015
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect131r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect131r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(131, 8, 3, 2, 0);
+ $this->setCoefficients(
+ '07A11B09A76B562144418FF3FF8C2570B8',
+ '0217C05610884B63B9C6C7291678F9D341'
+ );
+ $this->setBasePoint(
+ '0081BAF91FDF9833C40F9C181343638399',
+ '078C6E7EA38C001F73C8134B1B4EF9E150'
+ );
+ $this->setOrder(new BigInteger('0400000000000000023123953A9464B54D', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect131r2.php b/Sources/Phpseclib/Crypt/EC/Curves/sect131r2.php
new file mode 100644
index 0000000000..93c11b2a3d
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect131r2.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect131r2 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(131, 8, 3, 2, 0);
+ $this->setCoefficients(
+ '03E5A88919D7CAFCBF415F07C2176573B2',
+ '04B8266A46C55657AC734CE38F018F2192'
+ );
+ $this->setBasePoint(
+ '0356DCD8F2F95031AD652D23951BB366A8',
+ '0648F06D867940A5366D9E265DE9EB240F'
+ );
+ $this->setOrder(new BigInteger('0400000000000000016954A233049BA98F', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect163k1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect163k1.php
new file mode 100644
index 0000000000..3c8574bb32
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect163k1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect163k1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(163, 7, 6, 3, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000001',
+ '000000000000000000000000000000000000000001'
+ );
+ $this->setBasePoint(
+ '02FE13C0537BBC11ACAA07D793DE4E6D5E5C94EEE8',
+ '0289070FB05D38FF58321F2E800536D538CCDAA3D9'
+ );
+ $this->setOrder(new BigInteger('04000000000000000000020108A2E0CC0D99F8A5EF', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect163r1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect163r1.php
new file mode 100644
index 0000000000..26afd87e41
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect163r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect163r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(163, 7, 6, 3, 0);
+ $this->setCoefficients(
+ '07B6882CAAEFA84F9554FF8428BD88E246D2782AE2',
+ '0713612DCDDCB40AAB946BDA29CA91F73AF958AFD9'
+ );
+ $this->setBasePoint(
+ '0369979697AB43897789566789567F787A7876A654',
+ '00435EDB42EFAFB2989D51FEFCE3C80988F41FF883'
+ );
+ $this->setOrder(new BigInteger('03FFFFFFFFFFFFFFFFFFFF48AAB689C29CA710279B', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect163r2.php b/Sources/Phpseclib/Crypt/EC/Curves/sect163r2.php
new file mode 100644
index 0000000000..38f94661c6
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect163r2.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect163r2 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(163, 7, 6, 3, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000001',
+ '020A601907B8C953CA1481EB10512F78744A3205FD'
+ );
+ $this->setBasePoint(
+ '03F0EBA16286A2D57EA0991168D4994637E8343E36',
+ '00D51FBC6C71A0094FA2CDD545B11C5C0C797324F1'
+ );
+ $this->setOrder(new BigInteger('040000000000000000000292FE77E70C12A4234C33', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect193r1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect193r1.php
new file mode 100644
index 0000000000..951f261eba
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect193r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect193r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(193, 15, 0);
+ $this->setCoefficients(
+ '0017858FEB7A98975169E171F77B4087DE098AC8A911DF7B01',
+ '00FDFB49BFE6C3A89FACADAA7A1E5BBC7CC1C2E5D831478814'
+ );
+ $this->setBasePoint(
+ '01F481BC5F0FF84A74AD6CDF6FDEF4BF6179625372D8C0C5E1',
+ '0025E399F2903712CCF3EA9E3A1AD17FB0B3201B6AF7CE1B05'
+ );
+ $this->setOrder(new BigInteger('01000000000000000000000000C7F34A778F443ACC920EBA49', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect193r2.php b/Sources/Phpseclib/Crypt/EC/Curves/sect193r2.php
new file mode 100644
index 0000000000..e3ff47ac79
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect193r2.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect193r2 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(193, 15, 0);
+ $this->setCoefficients(
+ '0163F35A5137C2CE3EA6ED8667190B0BC43ECD69977702709B',
+ '00C9BB9E8927D4D64C377E2AB2856A5B16E3EFB7F61D4316AE'
+ );
+ $this->setBasePoint(
+ '00D9B67D192E0367C803F39E1A7E82CA14A651350AAE617E8F',
+ '01CE94335607C304AC29E7DEFBD9CA01F596F927224CDECF6C'
+ );
+ $this->setOrder(new BigInteger('010000000000000000000000015AAB561B005413CCD4EE99D5', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect233k1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect233k1.php
new file mode 100644
index 0000000000..eea3f7ad5b
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect233k1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect233k1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(233, 74, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000000',
+ '000000000000000000000000000000000000000000000000000000000001'
+ );
+ $this->setBasePoint(
+ '017232BA853A7E731AF129F22FF4149563A419C26BF50A4C9D6EEFAD6126',
+ '01DB537DECE819B7F70F555A67C427A8CD9BF18AEB9B56E0C11056FAE6A3'
+ );
+ $this->setOrder(new BigInteger('8000000000000000000000000000069D5BB915BCD46EFB1AD5F173ABDF', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect233r1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect233r1.php
new file mode 100644
index 0000000000..68219f0ea1
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect233r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect233r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(233, 74, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000001',
+ '0066647EDE6C332C7F8C0923BB58213B333B20E9CE4281FE115F7D8F90AD'
+ );
+ $this->setBasePoint(
+ '00FAC9DFCBAC8313BB2139F1BB755FEF65BC391F8B36F8F8EB7371FD558B',
+ '01006A08A41903350678E58528BEBF8A0BEFF867A7CA36716F7E01F81052'
+ );
+ $this->setOrder(new BigInteger('01000000000000000000000000000013E974E72F8A6922031D2603CFE0D7', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect239k1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect239k1.php
new file mode 100644
index 0000000000..0e6994ba37
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect239k1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect239k1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(239, 158, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000000',
+ '000000000000000000000000000000000000000000000000000000000001'
+ );
+ $this->setBasePoint(
+ '29A0B6A887A983E9730988A68727A8B2D126C44CC2CC7B2A6555193035DC',
+ '76310804F12E549BDB011C103089E73510ACB275FC312A5DC6B76553F0CA'
+ );
+ $this->setOrder(new BigInteger('2000000000000000000000000000005A79FEC67CB6E91F1C1DA800E478A5', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect283k1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect283k1.php
new file mode 100644
index 0000000000..279c24aac1
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect283k1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect283k1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(283, 12, 7, 5, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000000000000000000',
+ '000000000000000000000000000000000000000000000000000000000000000000000001'
+ );
+ $this->setBasePoint(
+ '0503213F78CA44883F1A3B8162F188E553CD265F23C1567A16876913B0C2AC2458492836',
+ '01CCDA380F1C9E318D90F95D07E5426FE87E45C0E8184698E45962364E34116177DD2259'
+ );
+ $this->setOrder(new BigInteger('01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9AE2ED07577265DFF7F94451E061E163C61', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect283r1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect283r1.php
new file mode 100644
index 0000000000..e44a60765b
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect283r1.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect283r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(283, 12, 7, 5, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000000000000000001',
+ '027B680AC8B8596DA5A4AF8A19A0303FCA97FD7645309FA2A581485AF6263E313B79A2F5'
+ );
+ $this->setBasePoint(
+ '05F939258DB7DD90E1934F8C70B0DFEC2EED25B8557EAC9C80E2E198F8CDBECD86B12053',
+ '03676854FE24141CB98FE6D4B20D02B4516FF702350EDDB0826779C813F0DF45BE8112F4'
+ );
+ $this->setOrder(new BigInteger('03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF90399660FC938A90165B042A7CEFADB307', 16));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect409k1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect409k1.php
new file mode 100644
index 0000000000..1fe329d8ca
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect409k1.php
@@ -0,0 +1,38 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect409k1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(409, 87, 0);
+ $this->setCoefficients(
+ '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+ '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001'
+ );
+ $this->setBasePoint(
+ '0060F05F658F49C1AD3AB1890F7184210EFD0987E307C84C27ACCFB8F9F67CC2C460189EB5AAAA62EE222EB1B35540CFE9023746',
+ '01E369050B7C4E42ACBA1DACBF04299C3460782F918EA427E6325165E9EA10E3DA5F6C42E9C55215AA9CA27A5863EC48D8E0286B'
+ );
+ $this->setOrder(new BigInteger(
+ '7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5F' .
+ '83B2D4EA20400EC4557D5ED3E3E7CA5B4B5C83B8E01E5FCF',
+ 16
+ ));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect409r1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect409r1.php
new file mode 100644
index 0000000000..3e209ef8ff
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect409r1.php
@@ -0,0 +1,38 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect409r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(409, 87, 0);
+ $this->setCoefficients(
+ '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001',
+ '0021A5C2C8EE9FEB5C4B9A753B7B476B7FD6422EF1F3DD674761FA99D6AC27C8A9A197B272822F6CD57A55AA4F50AE317B13545F'
+ );
+ $this->setBasePoint(
+ '015D4860D088DDB3496B0C6064756260441CDE4AF1771D4DB01FFE5B34E59703DC255A868A1180515603AEAB60794E54BB7996A7',
+ '0061B1CFAB6BE5F32BBFA78324ED106A7636B9C5A7BD198D0158AA4F5488D08F38514F1FDF4B4F40D2181B3681C364BA0273C706'
+ );
+ $this->setOrder(new BigInteger(
+ '010000000000000000000000000000000000000000000000000001E2' .
+ 'AAD6A612F33307BE5FA47C3C9E052F838164CD37D9A21173',
+ 16
+ ));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect571k1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect571k1.php
new file mode 100644
index 0000000000..3c54eabdbb
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect571k1.php
@@ -0,0 +1,42 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect571k1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(571, 10, 5, 2, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000000000000000000' .
+ '000000000000000000000000000000000000000000000000000000000000000000000000',
+ '000000000000000000000000000000000000000000000000000000000000000000000000' .
+ '000000000000000000000000000000000000000000000000000000000000000000000001'
+ );
+ $this->setBasePoint(
+ '026EB7A859923FBC82189631F8103FE4AC9CA2970012D5D46024804801841CA443709584' .
+ '93B205E647DA304DB4CEB08CBBD1BA39494776FB988B47174DCA88C7E2945283A01C8972',
+ '0349DC807F4FBF374F4AEADE3BCA95314DD58CEC9F307A54FFC61EFC006D8A2C9D4979C0' .
+ 'AC44AEA74FBEBBB9F772AEDCB620B01A7BA7AF1B320430C8591984F601CD4C143EF1C7A3'
+ );
+ $this->setOrder(new BigInteger(
+ '020000000000000000000000000000000000000000000000000000000000000000000000' .
+ '131850E1F19A63E4B391A8DB917F4138B630D84BE5D639381E91DEB45CFE778F637C1001',
+ 16
+ ));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Curves/sect571r1.php b/Sources/Phpseclib/Crypt/EC/Curves/sect571r1.php
new file mode 100644
index 0000000000..172c1af9cd
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Curves/sect571r1.php
@@ -0,0 +1,42 @@
+
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect571r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(571, 10, 5, 2, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000000000000000000' .
+ '000000000000000000000000000000000000000000000000000000000000000000000001',
+ '02F40E7E2221F295DE297117B7F3D62F5C6A97FFCB8CEFF1CD6BA8CE4A9A18AD84FFABBD' .
+ '8EFA59332BE7AD6756A66E294AFD185A78FF12AA520E4DE739BACA0C7FFEFF7F2955727A'
+ );
+ $this->setBasePoint(
+ '0303001D34B856296C16C0D40D3CD7750A93D1D2955FA80AA5F40FC8DB7B2ABDBDE53950' .
+ 'F4C0D293CDD711A35B67FB1499AE60038614F1394ABFA3B4C850D927E1E7769C8EEC2D19',
+ '037BF27342DA639B6DCCFFFEB73D69D78C6C27A6009CBBCA1980F8533921E8A684423E43' .
+ 'BAB08A576291AF8F461BB2A8B3531D2F0485C19B16E2F1516E23DD3C1A4827AF1B8AC15B'
+ );
+ $this->setOrder(new BigInteger(
+ '03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ 'E661CE18FF55987308059B186823851EC7DD9CA1161DE93D5174D66E8382E9BB2FE84E47',
+ 16
+ ));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Keys/Common.php b/Sources/Phpseclib/Crypt/EC/Formats/Keys/Common.php
new file mode 100644
index 0000000000..743c07c3ec
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Keys/Common.php
@@ -0,0 +1,549 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\BaseCurves\Binary as BinaryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\Prime as PrimeCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Generic EC Key Parsing Helper functions
+ *
+ * @author Jim Wigginton
+ */
+trait Common
+{
+ /**
+ * Curve OIDs
+ *
+ * @var array
+ */
+ private static $curveOIDs = [];
+
+ /**
+ * Child OIDs loaded
+ *
+ * @var bool
+ */
+ protected static $childOIDsLoaded = false;
+
+ /**
+ * Use Named Curves
+ *
+ * @var bool
+ */
+ private static $useNamedCurves = true;
+
+ /**
+ * Initialize static variables
+ */
+ private static function initialize_static_variables()
+ {
+ if (empty(self::$curveOIDs)) {
+ // the sec* curves are from the standards for efficient cryptography group
+ // sect* curves are curves over binary finite fields
+ // secp* curves are curves over prime finite fields
+ // sec*r* curves are regular curves; sec*k* curves are koblitz curves
+ // brainpool*r* curves are regular prime finite field curves
+ // brainpool*t* curves are twisted versions of the brainpool*r* curves
+ self::$curveOIDs = [
+ 'prime192v1' => '1.2.840.10045.3.1.1', // J.5.1, example 1 (aka secp192r1)
+ 'prime192v2' => '1.2.840.10045.3.1.2', // J.5.1, example 2
+ 'prime192v3' => '1.2.840.10045.3.1.3', // J.5.1, example 3
+ 'prime239v1' => '1.2.840.10045.3.1.4', // J.5.2, example 1
+ 'prime239v2' => '1.2.840.10045.3.1.5', // J.5.2, example 2
+ 'prime239v3' => '1.2.840.10045.3.1.6', // J.5.2, example 3
+ 'prime256v1' => '1.2.840.10045.3.1.7', // J.5.3, example 1 (aka secp256r1)
+
+ // https://tools.ietf.org/html/rfc5656#section-10
+ 'nistp256' => '1.2.840.10045.3.1.7', // aka secp256r1
+ 'nistp384' => '1.3.132.0.34', // aka secp384r1
+ 'nistp521' => '1.3.132.0.35', // aka secp521r1
+
+ 'nistk163' => '1.3.132.0.1', // aka sect163k1
+ 'nistp192' => '1.2.840.10045.3.1.1', // aka secp192r1
+ 'nistp224' => '1.3.132.0.33', // aka secp224r1
+ 'nistk233' => '1.3.132.0.26', // aka sect233k1
+ 'nistb233' => '1.3.132.0.27', // aka sect233r1
+ 'nistk283' => '1.3.132.0.16', // aka sect283k1
+ 'nistk409' => '1.3.132.0.36', // aka sect409k1
+ 'nistb409' => '1.3.132.0.37', // aka sect409r1
+ 'nistt571' => '1.3.132.0.38', // aka sect571k1
+
+ // from https://tools.ietf.org/html/rfc5915
+ 'secp192r1' => '1.2.840.10045.3.1.1', // aka prime192v1
+ 'sect163k1' => '1.3.132.0.1',
+ 'sect163r2' => '1.3.132.0.15',
+ 'secp224r1' => '1.3.132.0.33',
+ 'sect233k1' => '1.3.132.0.26',
+ 'sect233r1' => '1.3.132.0.27',
+ 'secp256r1' => '1.2.840.10045.3.1.7', // aka prime256v1
+ 'sect283k1' => '1.3.132.0.16',
+ 'sect283r1' => '1.3.132.0.17',
+ 'secp384r1' => '1.3.132.0.34',
+ 'sect409k1' => '1.3.132.0.36',
+ 'sect409r1' => '1.3.132.0.37',
+ 'secp521r1' => '1.3.132.0.35',
+ 'sect571k1' => '1.3.132.0.38',
+ 'sect571r1' => '1.3.132.0.39',
+ // from http://www.secg.org/SEC2-Ver-1.0.pdf
+ 'secp112r1' => '1.3.132.0.6',
+ 'secp112r2' => '1.3.132.0.7',
+ 'secp128r1' => '1.3.132.0.28',
+ 'secp128r2' => '1.3.132.0.29',
+ 'secp160k1' => '1.3.132.0.9',
+ 'secp160r1' => '1.3.132.0.8',
+ 'secp160r2' => '1.3.132.0.30',
+ 'secp192k1' => '1.3.132.0.31',
+ 'secp224k1' => '1.3.132.0.32',
+ 'secp256k1' => '1.3.132.0.10',
+
+ 'sect113r1' => '1.3.132.0.4',
+ 'sect113r2' => '1.3.132.0.5',
+ 'sect131r1' => '1.3.132.0.22',
+ 'sect131r2' => '1.3.132.0.23',
+ 'sect163r1' => '1.3.132.0.2',
+ 'sect193r1' => '1.3.132.0.24',
+ 'sect193r2' => '1.3.132.0.25',
+ 'sect239k1' => '1.3.132.0.3',
+
+ // from http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.202.2977&rep=rep1&type=pdf#page=36
+ /*
+ 'c2pnb163v1' => '1.2.840.10045.3.0.1', // J.4.1, example 1
+ 'c2pnb163v2' => '1.2.840.10045.3.0.2', // J.4.1, example 2
+ 'c2pnb163v3' => '1.2.840.10045.3.0.3', // J.4.1, example 3
+ 'c2pnb172w1' => '1.2.840.10045.3.0.4', // J.4.2, example 1
+ 'c2tnb191v1' => '1.2.840.10045.3.0.5', // J.4.3, example 1
+ 'c2tnb191v2' => '1.2.840.10045.3.0.6', // J.4.3, example 2
+ 'c2tnb191v3' => '1.2.840.10045.3.0.7', // J.4.3, example 3
+ 'c2onb191v4' => '1.2.840.10045.3.0.8', // J.4.3, example 4
+ 'c2onb191v5' => '1.2.840.10045.3.0.9', // J.4.3, example 5
+ 'c2pnb208w1' => '1.2.840.10045.3.0.10', // J.4.4, example 1
+ 'c2tnb239v1' => '1.2.840.10045.3.0.11', // J.4.5, example 1
+ 'c2tnb239v2' => '1.2.840.10045.3.0.12', // J.4.5, example 2
+ 'c2tnb239v3' => '1.2.840.10045.3.0.13', // J.4.5, example 3
+ 'c2onb239v4' => '1.2.840.10045.3.0.14', // J.4.5, example 4
+ 'c2onb239v5' => '1.2.840.10045.3.0.15', // J.4.5, example 5
+ 'c2pnb272w1' => '1.2.840.10045.3.0.16', // J.4.6, example 1
+ 'c2pnb304w1' => '1.2.840.10045.3.0.17', // J.4.7, example 1
+ 'c2tnb359v1' => '1.2.840.10045.3.0.18', // J.4.8, example 1
+ 'c2pnb368w1' => '1.2.840.10045.3.0.19', // J.4.9, example 1
+ 'c2tnb431r1' => '1.2.840.10045.3.0.20', // J.4.10, example 1
+ */
+
+ // http://www.ecc-brainpool.org/download/Domain-parameters.pdf
+ // https://tools.ietf.org/html/rfc5639
+ 'brainpoolP160r1' => '1.3.36.3.3.2.8.1.1.1',
+ 'brainpoolP160t1' => '1.3.36.3.3.2.8.1.1.2',
+ 'brainpoolP192r1' => '1.3.36.3.3.2.8.1.1.3',
+ 'brainpoolP192t1' => '1.3.36.3.3.2.8.1.1.4',
+ 'brainpoolP224r1' => '1.3.36.3.3.2.8.1.1.5',
+ 'brainpoolP224t1' => '1.3.36.3.3.2.8.1.1.6',
+ 'brainpoolP256r1' => '1.3.36.3.3.2.8.1.1.7',
+ 'brainpoolP256t1' => '1.3.36.3.3.2.8.1.1.8',
+ 'brainpoolP320r1' => '1.3.36.3.3.2.8.1.1.9',
+ 'brainpoolP320t1' => '1.3.36.3.3.2.8.1.1.10',
+ 'brainpoolP384r1' => '1.3.36.3.3.2.8.1.1.11',
+ 'brainpoolP384t1' => '1.3.36.3.3.2.8.1.1.12',
+ 'brainpoolP512r1' => '1.3.36.3.3.2.8.1.1.13',
+ 'brainpoolP512t1' => '1.3.36.3.3.2.8.1.1.14'
+ ];
+ ASN1::loadOIDs([
+ 'prime-field' => '1.2.840.10045.1.1',
+ 'characteristic-two-field' => '1.2.840.10045.1.2',
+ 'characteristic-two-basis' => '1.2.840.10045.1.2.3',
+ // per http://www.secg.org/SEC1-Ver-1.0.pdf#page=84, gnBasis "not used here"
+ 'gnBasis' => '1.2.840.10045.1.2.3.1', // NULL
+ 'tpBasis' => '1.2.840.10045.1.2.3.2', // Trinomial
+ 'ppBasis' => '1.2.840.10045.1.2.3.3' // Pentanomial
+ ] + self::$curveOIDs);
+ }
+ }
+
+ /**
+ * Explicitly set the curve
+ *
+ * If the key contains an implicit curve phpseclib needs the curve
+ * to be explicitly provided
+ *
+ * @param BaseCurve $curve
+ */
+ public static function setImplicitCurve(BaseCurve $curve)
+ {
+ self::$implicitCurve = $curve;
+ }
+
+ /**
+ * Returns an instance of \phpseclib3\Crypt\EC\BaseCurves\Base based
+ * on the curve parameters
+ *
+ * @param array $params
+ * @return BaseCurve|false
+ */
+ protected static function loadCurveByParam(array $params)
+ {
+ if (count($params) > 1) {
+ throw new \RuntimeException('No parameters are present');
+ }
+ if (isset($params['namedCurve'])) {
+ $curve = '\phpseclib3\Crypt\EC\Curves\\' . $params['namedCurve'];
+ if (!class_exists($curve)) {
+ throw new UnsupportedCurveException('Named Curve of ' . $params['namedCurve'] . ' is not supported');
+ }
+ return new $curve();
+ }
+ if (isset($params['implicitCurve'])) {
+ if (!isset(self::$implicitCurve)) {
+ throw new \RuntimeException('Implicit curves can be provided by calling setImplicitCurve');
+ }
+ return self::$implicitCurve;
+ }
+ if (isset($params['specifiedCurve'])) {
+ $data = $params['specifiedCurve'];
+ switch ($data['fieldID']['fieldType']) {
+ case 'prime-field':
+ $curve = new PrimeCurve();
+ $curve->setModulo($data['fieldID']['parameters']);
+ $curve->setCoefficients(
+ new BigInteger($data['curve']['a'], 256),
+ new BigInteger($data['curve']['b'], 256)
+ );
+ $point = self::extractPoint("\0" . $data['base'], $curve);
+ $curve->setBasePoint(...$point);
+ $curve->setOrder($data['order']);
+ return $curve;
+ case 'characteristic-two-field':
+ $curve = new BinaryCurve();
+ $params = ASN1::decodeBER($data['fieldID']['parameters']);
+ $params = ASN1::asn1map($params[0], Maps\Characteristic_two::MAP);
+ $modulo = [(int) $params['m']->toString()];
+ switch ($params['basis']) {
+ case 'tpBasis':
+ $modulo[] = (int) $params['parameters']->toString();
+ break;
+ case 'ppBasis':
+ $temp = ASN1::decodeBER($params['parameters']);
+ $temp = ASN1::asn1map($temp[0], Maps\Pentanomial::MAP);
+ $modulo[] = (int) $temp['k3']->toString();
+ $modulo[] = (int) $temp['k2']->toString();
+ $modulo[] = (int) $temp['k1']->toString();
+ }
+ $modulo[] = 0;
+ $curve->setModulo(...$modulo);
+ $len = ceil($modulo[0] / 8);
+ $curve->setCoefficients(
+ Strings::bin2hex($data['curve']['a']),
+ Strings::bin2hex($data['curve']['b'])
+ );
+ $point = self::extractPoint("\0" . $data['base'], $curve);
+ $curve->setBasePoint(...$point);
+ $curve->setOrder($data['order']);
+ return $curve;
+ default:
+ throw new UnsupportedCurveException('Field Type of ' . $data['fieldID']['fieldType'] . ' is not supported');
+ }
+ }
+ throw new \RuntimeException('No valid parameters are present');
+ }
+
+ /**
+ * Extract points from a string
+ *
+ * Supports both compressed and uncompressed points
+ *
+ * @param string $str
+ * @param BaseCurve $curve
+ * @return object[]
+ */
+ public static function extractPoint($str, BaseCurve $curve)
+ {
+ if ($curve instanceof TwistedEdwardsCurve) {
+ // first step of point deciding as discussed at the following URL's:
+ // https://tools.ietf.org/html/rfc8032#section-5.1.3
+ // https://tools.ietf.org/html/rfc8032#section-5.2.3
+ $y = $str;
+ $y = strrev($y);
+ $sign = (bool) (ord($y[0]) & 0x80);
+ $y[0] = $y[0] & chr(0x7F);
+ $y = new BigInteger($y, 256);
+ if ($y->compare($curve->getModulo()) >= 0) {
+ throw new \RuntimeException('The Y coordinate should not be >= the modulo');
+ }
+ $point = $curve->recoverX($y, $sign);
+ if (!$curve->verifyPoint($point)) {
+ throw new \RuntimeException('Unable to verify that point exists on curve');
+ }
+ return $point;
+ }
+
+ // 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
+ if (($val = Strings::shift($str)) != "\0") {
+ throw new \UnexpectedValueException('extractPoint expects the first byte to be null - not ' . Strings::bin2hex($val));
+ }
+ if ($str == "\0") {
+ return [];
+ }
+
+ $keylen = strlen($str);
+ $order = $curve->getLengthInBytes();
+ // point compression is being used
+ if ($keylen == $order + 1) {
+ return $curve->derivePoint($str);
+ }
+
+ // point compression is not being used
+ if ($keylen == 2 * $order + 1) {
+ preg_match("#(.)(.{{$order}})(.{{$order}})#s", $str, $matches);
+ list(, $w, $x, $y) = $matches;
+ if ($w != "\4") {
+ throw new \UnexpectedValueException('The first byte of an uncompressed point should be 04 - not ' . Strings::bin2hex($val));
+ }
+ $point = [
+ $curve->convertInteger(new BigInteger($x, 256)),
+ $curve->convertInteger(new BigInteger($y, 256))
+ ];
+
+ if (!$curve->verifyPoint($point)) {
+ throw new \RuntimeException('Unable to verify that point exists on curve');
+ }
+
+ return $point;
+ }
+
+ throw new \UnexpectedValueException('The string representation of the points is not of an appropriate length');
+ }
+
+ /**
+ * Encode Parameters
+ *
+ * @todo Maybe at some point this could be moved to __toString() for each of the curves?
+ * @param BaseCurve $curve
+ * @param bool $returnArray optional
+ * @param array $options optional
+ * @return string|false
+ */
+ private static function encodeParameters(BaseCurve $curve, $returnArray = false, array $options = [])
+ {
+ $useNamedCurves = isset($options['namedCurve']) ? $options['namedCurve'] : self::$useNamedCurves;
+
+ $reflect = new \ReflectionClass($curve);
+ $name = $reflect->getShortName();
+ if ($useNamedCurves) {
+ if (isset(self::$curveOIDs[$name])) {
+ if ($reflect->isFinal()) {
+ $reflect = $reflect->getParentClass();
+ $name = $reflect->getShortName();
+ }
+ return $returnArray ?
+ ['namedCurve' => $name] :
+ ASN1::encodeDER(['namedCurve' => $name], Maps\ECParameters::MAP);
+ }
+ foreach (new \DirectoryIterator(__DIR__ . '/../../Curves/') as $file) {
+ if ($file->getExtension() != 'php') {
+ continue;
+ }
+ $testName = $file->getBasename('.php');
+ $class = 'phpseclib3\Crypt\EC\Curves\\' . $testName;
+ $reflect = new \ReflectionClass($class);
+ if ($reflect->isFinal()) {
+ continue;
+ }
+ $candidate = new $class();
+ switch ($name) {
+ case 'Prime':
+ if (!$candidate instanceof PrimeCurve) {
+ break;
+ }
+ if (!$candidate->getModulo()->equals($curve->getModulo())) {
+ break;
+ }
+ if ($candidate->getA()->toBytes() != $curve->getA()->toBytes()) {
+ break;
+ }
+ if ($candidate->getB()->toBytes() != $curve->getB()->toBytes()) {
+ break;
+ }
+
+ list($candidateX, $candidateY) = $candidate->getBasePoint();
+ list($curveX, $curveY) = $curve->getBasePoint();
+ if ($candidateX->toBytes() != $curveX->toBytes()) {
+ break;
+ }
+ if ($candidateY->toBytes() != $curveY->toBytes()) {
+ break;
+ }
+
+ return $returnArray ?
+ ['namedCurve' => $testName] :
+ ASN1::encodeDER(['namedCurve' => $testName], Maps\ECParameters::MAP);
+ case 'Binary':
+ if (!$candidate instanceof BinaryCurve) {
+ break;
+ }
+ if ($candidate->getModulo() != $curve->getModulo()) {
+ break;
+ }
+ if ($candidate->getA()->toBytes() != $curve->getA()->toBytes()) {
+ break;
+ }
+ if ($candidate->getB()->toBytes() != $curve->getB()->toBytes()) {
+ break;
+ }
+
+ list($candidateX, $candidateY) = $candidate->getBasePoint();
+ list($curveX, $curveY) = $curve->getBasePoint();
+ if ($candidateX->toBytes() != $curveX->toBytes()) {
+ break;
+ }
+ if ($candidateY->toBytes() != $curveY->toBytes()) {
+ break;
+ }
+
+ return $returnArray ?
+ ['namedCurve' => $testName] :
+ ASN1::encodeDER(['namedCurve' => $testName], Maps\ECParameters::MAP);
+ }
+ }
+ }
+
+ $order = $curve->getOrder();
+ // we could try to calculate the order thusly:
+ // https://crypto.stackexchange.com/a/27914/4520
+ // https://en.wikipedia.org/wiki/Schoof%E2%80%93Elkies%E2%80%93Atkin_algorithm
+ if (!$order) {
+ throw new \RuntimeException('Specified Curves need the order to be specified');
+ }
+ $point = $curve->getBasePoint();
+ $x = $point[0]->toBytes();
+ $y = $point[1]->toBytes();
+
+ if ($curve instanceof PrimeCurve) {
+ /*
+ * valid versions are:
+ *
+ * ecdpVer1:
+ * - neither the curve or the base point are generated verifiably randomly.
+ * ecdpVer2:
+ * - curve and base point are generated verifiably at random and curve.seed is present
+ * ecdpVer3:
+ * - base point is generated verifiably at random but curve is not. curve.seed is present
+ */
+ // other (optional) parameters can be calculated using the methods discused at
+ // https://crypto.stackexchange.com/q/28947/4520
+ $data = [
+ 'version' => 'ecdpVer1',
+ 'fieldID' => [
+ 'fieldType' => 'prime-field',
+ 'parameters' => $curve->getModulo()
+ ],
+ 'curve' => [
+ 'a' => $curve->getA()->toBytes(),
+ 'b' => $curve->getB()->toBytes()
+ ],
+ 'base' => "\4" . $x . $y,
+ 'order' => $order
+ ];
+
+ return $returnArray ?
+ ['specifiedCurve' => $data] :
+ ASN1::encodeDER(['specifiedCurve' => $data], Maps\ECParameters::MAP);
+ }
+ if ($curve instanceof BinaryCurve) {
+ $modulo = $curve->getModulo();
+ $basis = count($modulo);
+ $m = array_shift($modulo);
+ array_pop($modulo); // the last parameter should always be 0
+ //rsort($modulo);
+ switch ($basis) {
+ case 3:
+ $basis = 'tpBasis';
+ $modulo = new BigInteger($modulo[0]);
+ break;
+ case 5:
+ $basis = 'ppBasis';
+ // these should be in strictly ascending order (hence the commented out rsort above)
+ $modulo = [
+ 'k1' => new BigInteger($modulo[2]),
+ 'k2' => new BigInteger($modulo[1]),
+ 'k3' => new BigInteger($modulo[0])
+ ];
+ $modulo = ASN1::encodeDER($modulo, Maps\Pentanomial::MAP);
+ $modulo = new ASN1\Element($modulo);
+ }
+ $params = ASN1::encodeDER([
+ 'm' => new BigInteger($m),
+ 'basis' => $basis,
+ 'parameters' => $modulo
+ ], Maps\Characteristic_two::MAP);
+ $params = new ASN1\Element($params);
+ $a = ltrim($curve->getA()->toBytes(), "\0");
+ if (!strlen($a)) {
+ $a = "\0";
+ }
+ $b = ltrim($curve->getB()->toBytes(), "\0");
+ if (!strlen($b)) {
+ $b = "\0";
+ }
+ $data = [
+ 'version' => 'ecdpVer1',
+ 'fieldID' => [
+ 'fieldType' => 'characteristic-two-field',
+ 'parameters' => $params
+ ],
+ 'curve' => [
+ 'a' => $a,
+ 'b' => $b
+ ],
+ 'base' => "\4" . $x . $y,
+ 'order' => $order
+ ];
+
+ return $returnArray ?
+ ['specifiedCurve' => $data] :
+ ASN1::encodeDER(['specifiedCurve' => $data], Maps\ECParameters::MAP);
+ }
+
+ throw new UnsupportedCurveException('Curve cannot be serialized');
+ }
+
+ /**
+ * Use Specified Curve
+ *
+ * A specified curve has all the coefficients, the base points, etc, explicitely included.
+ * A specified curve is a more verbose way of representing a curve
+ */
+ public static function useSpecifiedCurve()
+ {
+ self::$useNamedCurves = false;
+ }
+
+ /**
+ * Use Named Curve
+ *
+ * A named curve does not include any parameters. It is up to the EC parameters to
+ * know what the coefficients, the base points, etc, are from the name of the curve.
+ * A named curve is a more concise way of representing a curve
+ */
+ public static function useNamedCurve()
+ {
+ self::$useNamedCurves = true;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Keys/JWK.php b/Sources/Phpseclib/Crypt/EC/Formats/Keys/JWK.php
new file mode 100644
index 0000000000..5bc5184f79
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Keys/JWK.php
@@ -0,0 +1,189 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\JWK as Progenitor;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Crypt\EC\Curves\secp256k1;
+use phpseclib3\Crypt\EC\Curves\secp256r1;
+use phpseclib3\Crypt\EC\Curves\secp384r1;
+use phpseclib3\Crypt\EC\Curves\secp521r1;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * JWK Formatted EC Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class JWK extends Progenitor
+{
+ use Common;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ switch ($key->kty) {
+ case 'EC':
+ switch ($key->crv) {
+ case 'P-256':
+ case 'P-384':
+ case 'P-521':
+ case 'secp256k1':
+ break;
+ default:
+ throw new UnsupportedCurveException('Only P-256, P-384, P-521 and secp256k1 curves are accepted (' . $key->crv . ' provided)');
+ }
+ break;
+ case 'OKP':
+ switch ($key->crv) {
+ case 'Ed25519':
+ case 'Ed448':
+ break;
+ default:
+ throw new UnsupportedCurveException('Only Ed25519 and Ed448 curves are accepted (' . $key->crv . ' provided)');
+ }
+ break;
+ default:
+ throw new \Exception('Only EC and OKP JWK keys are supported');
+ }
+
+ $curve = '\phpseclib3\Crypt\EC\Curves\\' . str_replace('P-', 'nistp', $key->crv);
+ $curve = new $curve();
+
+ if ($curve instanceof TwistedEdwardsCurve) {
+ $QA = self::extractPoint(Strings::base64url_decode($key->x), $curve);
+ if (!isset($key->d)) {
+ return compact('curve', 'QA');
+ }
+ $arr = $curve->extractSecret(Strings::base64url_decode($key->d));
+ return compact('curve', 'QA') + $arr;
+ }
+
+ $QA = [
+ $curve->convertInteger(new BigInteger(Strings::base64url_decode($key->x), 256)),
+ $curve->convertInteger(new BigInteger(Strings::base64url_decode($key->y), 256))
+ ];
+
+ if (!$curve->verifyPoint($QA)) {
+ throw new \RuntimeException('Unable to verify that point exists on curve');
+ }
+
+ if (!isset($key->d)) {
+ return compact('curve', 'QA');
+ }
+
+ $dA = new BigInteger(Strings::base64url_decode($key->d), 256);
+
+ $curve->rangeCheck($dA);
+
+ return compact('curve', 'dA', 'QA');
+ }
+
+ /**
+ * Returns the alias that corresponds to a curve
+ *
+ * @return string
+ */
+ private static function getAlias(BaseCurve $curve)
+ {
+ switch (true) {
+ case $curve instanceof secp256r1:
+ return 'P-256';
+ case $curve instanceof secp384r1:
+ return 'P-384';
+ case $curve instanceof secp521r1:
+ return 'P-521';
+ case $curve instanceof secp256k1:
+ return 'secp256k1';
+ }
+
+ $reflect = new \ReflectionClass($curve);
+ $curveName = $reflect->isFinal() ?
+ $reflect->getParentClass()->getShortName() :
+ $reflect->getShortName();
+ throw new UnsupportedCurveException("$curveName is not a supported curve");
+ }
+
+ /**
+ * Return the array superstructure for an EC public key
+ *
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @return array
+ */
+ private static function savePublicKeyHelper(BaseCurve $curve, array $publicKey)
+ {
+ if ($curve instanceof TwistedEdwardsCurve) {
+ return [
+ 'kty' => 'OKP',
+ 'crv' => $curve instanceof Ed25519 ? 'Ed25519' : 'Ed448',
+ 'x' => Strings::base64url_encode($curve->encodePoint($publicKey))
+ ];
+ }
+
+ return [
+ 'kty' => 'EC',
+ 'crv' => self::getAlias($curve),
+ 'x' => Strings::base64url_encode($publicKey[0]->toBytes()),
+ 'y' => Strings::base64url_encode($publicKey[1]->toBytes())
+ ];
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = [])
+ {
+ $key = self::savePublicKeyHelper($curve, $publicKey);
+
+ return self::wrapKey($key, $options);
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param Ed25519 $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = [])
+ {
+ $key = self::savePublicKeyHelper($curve, $publicKey);
+ $key['d'] = $curve instanceof TwistedEdwardsCurve ? $secret : $privateKey->toBytes();
+ $key['d'] = Strings::base64url_encode($key['d']);
+
+ return self::wrapKey($key, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php b/Sources/Phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php
new file mode 100644
index 0000000000..aa64f79abe
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php
@@ -0,0 +1,101 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\Curves\Curve25519;
+use phpseclib3\Crypt\EC\Curves\Curve448;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Montgomery Curve Private Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class MontgomeryPrivate
+{
+ /**
+ * Is invisible flag
+ *
+ */
+ const IS_INVISIBLE = true;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ switch (strlen($key)) {
+ case 32:
+ $curve = new Curve25519();
+ break;
+ case 56:
+ $curve = new Curve448();
+ break;
+ default:
+ throw new \LengthException('The only supported lengths are 32 and 56');
+ }
+
+ $components = ['curve' => $curve];
+ $components['dA'] = new BigInteger($key, 256);
+ $curve->rangeCheck($components['dA']);
+ // note that EC::getEncodedCoordinates does some additional "magic" (it does strrev on the result)
+ $components['QA'] = $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']);
+
+ return $components;
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param MontgomeryCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @return string
+ */
+ public static function savePublicKey(MontgomeryCurve $curve, array $publicKey)
+ {
+ return strrev($publicKey[0]->toBytes());
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param MontgomeryCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, MontgomeryCurve $curve, array $publicKey, $secret = null, $password = '')
+ {
+ if (!empty($password) && is_string($password)) {
+ throw new UnsupportedFormatException('MontgomeryPrivate private keys do not support encryption');
+ }
+
+ return $privateKey->toBytes();
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php b/Sources/Phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php
new file mode 100644
index 0000000000..257c26e878
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php
@@ -0,0 +1,71 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\Curves\Curve25519;
+use phpseclib3\Crypt\EC\Curves\Curve448;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Montgomery Public Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class MontgomeryPublic
+{
+ /**
+ * Is invisible flag
+ *
+ */
+ const IS_INVISIBLE = true;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ switch (strlen($key)) {
+ case 32:
+ $curve = new Curve25519();
+ break;
+ case 56:
+ $curve = new Curve448();
+ break;
+ default:
+ throw new \LengthException('The only supported lengths are 32 and 56');
+ }
+
+ $components = ['curve' => $curve];
+ $components['QA'] = [$components['curve']->convertInteger(new BigInteger(strrev($key), 256))];
+
+ return $components;
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param MontgomeryCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @return string
+ */
+ public static function savePublicKey(MontgomeryCurve $curve, array $publicKey)
+ {
+ return strrev($publicKey[0]->toBytes());
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php b/Sources/Phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php
new file mode 100644
index 0000000000..0ef1160441
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php
@@ -0,0 +1,209 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * OpenSSH Formatted EC Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class OpenSSH extends Progenitor
+{
+ use Common;
+
+ /**
+ * Supported Key Types
+ *
+ * @var array
+ */
+ protected static $types = [
+ 'ecdsa-sha2-nistp256',
+ 'ecdsa-sha2-nistp384',
+ 'ecdsa-sha2-nistp521',
+ 'ssh-ed25519'
+ ];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $parsed = parent::load($key, $password);
+
+ if (isset($parsed['paddedKey'])) {
+ $paddedKey = $parsed['paddedKey'];
+ list($type) = Strings::unpackSSH2('s', $paddedKey);
+ if ($type != $parsed['type']) {
+ throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
+ }
+ if ($type == 'ssh-ed25519') {
+ list(, $key, $comment) = Strings::unpackSSH2('sss', $paddedKey);
+ $key = libsodium::load($key);
+ $key['comment'] = $comment;
+ return $key;
+ }
+ list($curveName, $publicKey, $privateKey, $comment) = Strings::unpackSSH2('ssis', $paddedKey);
+ $curve = self::loadCurveByParam(['namedCurve' => $curveName]);
+ $curve->rangeCheck($privateKey);
+ return [
+ 'curve' => $curve,
+ 'dA' => $privateKey,
+ 'QA' => self::extractPoint("\0$publicKey", $curve),
+ 'comment' => $comment
+ ];
+ }
+
+ if ($parsed['type'] == 'ssh-ed25519') {
+ if (Strings::shift($parsed['publicKey'], 4) != "\0\0\0\x20") {
+ throw new \RuntimeException('Length of ssh-ed25519 key should be 32');
+ }
+
+ $curve = new Ed25519();
+ $qa = self::extractPoint($parsed['publicKey'], $curve);
+ } else {
+ list($curveName, $publicKey) = Strings::unpackSSH2('ss', $parsed['publicKey']);
+ $curveName = '\phpseclib3\Crypt\EC\Curves\\' . $curveName;
+ $curve = new $curveName();
+
+ $qa = self::extractPoint("\0" . $publicKey, $curve);
+ }
+
+ return [
+ 'curve' => $curve,
+ 'QA' => $qa,
+ 'comment' => $parsed['comment']
+ ];
+ }
+
+ /**
+ * Returns the alias that corresponds to a curve
+ *
+ * @return string
+ */
+ private static function getAlias(BaseCurve $curve)
+ {
+ self::initialize_static_variables();
+
+ $reflect = new \ReflectionClass($curve);
+ $name = $reflect->getShortName();
+
+ $oid = self::$curveOIDs[$name];
+ $aliases = array_filter(self::$curveOIDs, function ($v) use ($oid) {
+ return $v == $oid;
+ });
+ $aliases = array_keys($aliases);
+
+ for ($i = 0; $i < count($aliases); $i++) {
+ if (in_array('ecdsa-sha2-' . $aliases[$i], self::$types)) {
+ $alias = $aliases[$i];
+ break;
+ }
+ }
+
+ if (!isset($alias)) {
+ throw new UnsupportedCurveException($name . ' is not a curve that the OpenSSH plugin supports');
+ }
+
+ return $alias;
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = [])
+ {
+ $comment = isset($options['comment']) ? $options['comment'] : self::$comment;
+
+ if ($curve instanceof Ed25519) {
+ $key = Strings::packSSH2('ss', 'ssh-ed25519', $curve->encodePoint($publicKey));
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $key;
+ }
+
+ $key = 'ssh-ed25519 ' . base64_encode($key) . ' ' . $comment;
+ return $key;
+ }
+
+ $alias = self::getAlias($curve);
+
+ $points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
+ $key = Strings::packSSH2('sss', 'ecdsa-sha2-' . $alias, $alias, $points);
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $key;
+ }
+
+ $key = 'ecdsa-sha2-' . $alias . ' ' . base64_encode($key) . ' ' . $comment;
+
+ return $key;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param Ed25519 $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = [])
+ {
+ if ($curve instanceof Ed25519) {
+ if (!isset($secret)) {
+ throw new \RuntimeException('Private Key does not have a secret set');
+ }
+ if (strlen($secret) != 32) {
+ throw new \RuntimeException('Private Key secret is not of the correct length');
+ }
+
+ $pubKey = $curve->encodePoint($publicKey);
+
+ $publicKey = Strings::packSSH2('ss', 'ssh-ed25519', $pubKey);
+ $privateKey = Strings::packSSH2('sss', 'ssh-ed25519', $pubKey, $secret . $pubKey);
+
+ return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
+ }
+
+ $alias = self::getAlias($curve);
+
+ $points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
+ $publicKey = self::savePublicKey($curve, $publicKey, ['binary' => true]);
+
+ $privateKey = Strings::packSSH2('sssi', 'ecdsa-sha2-' . $alias, $alias, $points, $privateKey);
+
+ return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Keys/PKCS1.php b/Sources/Phpseclib/Crypt/EC/Formats/Keys/PKCS1.php
new file mode 100644
index 0000000000..756ffb957b
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Keys/PKCS1.php
@@ -0,0 +1,194 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * "PKCS1" (RFC5915) Formatted EC Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class PKCS1 extends Progenitor
+{
+ use Common;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ self::initialize_static_variables();
+
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ if (strpos($key, 'BEGIN EC PARAMETERS') && strpos($key, 'BEGIN EC PRIVATE KEY')) {
+ $components = [];
+
+ preg_match('#-*BEGIN EC PRIVATE KEY-*[^-]*-*END EC PRIVATE KEY-*#s', $key, $matches);
+ $decoded = parent::load($matches[0], $password);
+ $decoded = ASN1::decodeBER($decoded);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ $ecPrivate = ASN1::asn1map($decoded[0], Maps\ECPrivateKey::MAP);
+ if (!is_array($ecPrivate)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping');
+ }
+
+ if (isset($ecPrivate['parameters'])) {
+ $components['curve'] = self::loadCurveByParam($ecPrivate['parameters']);
+ }
+
+ preg_match('#-*BEGIN EC PARAMETERS-*[^-]*-*END EC PARAMETERS-*#s', $key, $matches);
+ $decoded = parent::load($matches[0], '');
+ $decoded = ASN1::decodeBER($decoded);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $ecParams = ASN1::asn1map($decoded[0], Maps\ECParameters::MAP);
+ if (!is_array($ecParams)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping');
+ }
+ $ecParams = self::loadCurveByParam($ecParams);
+
+ // comparing $ecParams and $components['curve'] directly won't work because they'll have different Math\Common\FiniteField classes
+ // even if the modulo is the same
+ if (isset($components['curve']) && self::encodeParameters($ecParams, false, []) != self::encodeParameters($components['curve'], false, [])) {
+ throw new \RuntimeException('EC PARAMETERS does not correspond to EC PRIVATE KEY');
+ }
+
+ if (!isset($components['curve'])) {
+ $components['curve'] = $ecParams;
+ }
+
+ $components['dA'] = new BigInteger($ecPrivate['privateKey'], 256);
+ $components['curve']->rangeCheck($components['dA']);
+ $components['QA'] = isset($ecPrivate['publicKey']) ?
+ self::extractPoint($ecPrivate['publicKey'], $components['curve']) :
+ $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']);
+
+ return $components;
+ }
+
+ $key = parent::load($key, $password);
+
+ $decoded = ASN1::decodeBER($key);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\ECParameters::MAP);
+ if (is_array($key)) {
+ return ['curve' => self::loadCurveByParam($key)];
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\ECPrivateKey::MAP);
+ if (!is_array($key)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping');
+ }
+ if (!isset($key['parameters'])) {
+ throw new \RuntimeException('Key cannot be loaded without parameters');
+ }
+
+ $components = [];
+ $components['curve'] = self::loadCurveByParam($key['parameters']);
+ $components['dA'] = new BigInteger($key['privateKey'], 256);
+ $components['QA'] = isset($ecPrivate['publicKey']) ?
+ self::extractPoint($ecPrivate['publicKey'], $components['curve']) :
+ $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']);
+
+ return $components;
+ }
+
+ /**
+ * Convert EC parameters to the appropriate format
+ *
+ * @return string
+ */
+ public static function saveParameters(BaseCurve $curve, array $options = [])
+ {
+ self::initialize_static_variables();
+
+ if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) {
+ throw new UnsupportedCurveException('TwistedEdwards and Montgomery Curves are not supported');
+ }
+
+ $key = self::encodeParameters($curve, false, $options);
+
+ return "-----BEGIN EC PARAMETERS-----\r\n" .
+ chunk_split(Strings::base64_encode($key), 64) .
+ "-----END EC PARAMETERS-----\r\n";
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = [])
+ {
+ self::initialize_static_variables();
+
+ if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) {
+ throw new UnsupportedCurveException('TwistedEdwards Curves are not supported');
+ }
+
+ $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
+
+ $key = [
+ 'version' => 'ecPrivkeyVer1',
+ 'privateKey' => $privateKey->toBytes(),
+ 'parameters' => new ASN1\Element(self::encodeParameters($curve)),
+ 'publicKey' => "\0" . $publicKey
+ ];
+
+ $key = ASN1::encodeDER($key, Maps\ECPrivateKey::MAP);
+
+ return self::wrapPrivateKey($key, 'EC', $password, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Keys/PKCS8.php b/Sources/Phpseclib/Crypt/EC/Formats/Keys/PKCS8.php
new file mode 100644
index 0000000000..9fc84054e8
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Keys/PKCS8.php
@@ -0,0 +1,237 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Crypt\EC\Curves\Ed448;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#8 Formatted EC Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class PKCS8 extends Progenitor
+{
+ use Common;
+
+ /**
+ * OID Name
+ *
+ * @var array
+ */
+ const OID_NAME = ['id-ecPublicKey', 'id-Ed25519', 'id-Ed448'];
+
+ /**
+ * OID Value
+ *
+ * @var string
+ */
+ const OID_VALUE = ['1.2.840.10045.2.1', '1.3.101.112', '1.3.101.113'];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ // initialize_static_variables() is defined in both the trait and the parent class
+ // when it's defined in two places it's the traits one that's called
+ // the parent one is needed, as well, but the parent one is called by other methods
+ // in the parent class as needed and in the context of the parent it's the parent
+ // one that's called
+ self::initialize_static_variables();
+
+ $key = parent::load($key, $password);
+
+ $type = isset($key['privateKey']) ? 'privateKey' : 'publicKey';
+
+ switch ($key[$type . 'Algorithm']['algorithm']) {
+ case 'id-Ed25519':
+ case 'id-Ed448':
+ return self::loadEdDSA($key);
+ }
+
+ $decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $params = ASN1::asn1map($decoded[0], Maps\ECParameters::MAP);
+ if (!$params) {
+ throw new \RuntimeException('Unable to decode the parameters using Maps\ECParameters');
+ }
+
+ $components = [];
+ $components['curve'] = self::loadCurveByParam($params);
+
+ if ($type == 'publicKey') {
+ $components['QA'] = self::extractPoint("\0" . $key['publicKey'], $components['curve']);
+
+ return $components;
+ }
+
+ $decoded = ASN1::decodeBER($key['privateKey']);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $key = ASN1::asn1map($decoded[0], Maps\ECPrivateKey::MAP);
+ if (isset($key['parameters']) && $params != $key['parameters']) {
+ throw new \RuntimeException('The PKCS8 parameter field does not match the private key parameter field');
+ }
+
+ $components['dA'] = new BigInteger($key['privateKey'], 256);
+ $components['curve']->rangeCheck($components['dA']);
+ $components['QA'] = isset($key['publicKey']) ?
+ self::extractPoint($key['publicKey'], $components['curve']) :
+ $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']);
+
+ return $components;
+ }
+
+ /**
+ * Break a public or private EdDSA key down into its constituent components
+ *
+ * @return array
+ */
+ private static function loadEdDSA(array $key)
+ {
+ $components = [];
+
+ if (isset($key['privateKey'])) {
+ $components['curve'] = $key['privateKeyAlgorithm']['algorithm'] == 'id-Ed25519' ? new Ed25519() : new Ed448();
+ $expected = chr(ASN1::TYPE_OCTET_STRING) . ASN1::encodeLength($components['curve']::SIZE);
+ if (substr($key['privateKey'], 0, 2) != $expected) {
+ throw new \RuntimeException(
+ 'The first two bytes of the ' .
+ $key['privateKeyAlgorithm']['algorithm'] .
+ ' private key field should be 0x' . bin2hex($expected)
+ );
+ }
+ $arr = $components['curve']->extractSecret(substr($key['privateKey'], 2));
+ $components['dA'] = $arr['dA'];
+ $components['secret'] = $arr['secret'];
+ }
+
+ if (isset($key['publicKey'])) {
+ if (!isset($components['curve'])) {
+ $components['curve'] = $key['publicKeyAlgorithm']['algorithm'] == 'id-Ed25519' ? new Ed25519() : new Ed448();
+ }
+
+ $components['QA'] = self::extractPoint($key['publicKey'], $components['curve']);
+ }
+
+ if (isset($key['privateKey']) && !isset($components['QA'])) {
+ $components['QA'] = $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']);
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = [])
+ {
+ self::initialize_static_variables();
+
+ if ($curve instanceof MontgomeryCurve) {
+ throw new UnsupportedCurveException('Montgomery Curves are not supported');
+ }
+
+ if ($curve instanceof TwistedEdwardsCurve) {
+ return self::wrapPublicKey(
+ $curve->encodePoint($publicKey),
+ null,
+ $curve instanceof Ed25519 ? 'id-Ed25519' : 'id-Ed448',
+ $options
+ );
+ }
+
+ $params = new ASN1\Element(self::encodeParameters($curve, false, $options));
+
+ $key = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
+
+ return self::wrapPublicKey($key, $params, 'id-ecPublicKey', $options);
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = [])
+ {
+ self::initialize_static_variables();
+
+ if ($curve instanceof MontgomeryCurve) {
+ throw new UnsupportedCurveException('Montgomery Curves are not supported');
+ }
+
+ if ($curve instanceof TwistedEdwardsCurve) {
+ return self::wrapPrivateKey(
+ chr(ASN1::TYPE_OCTET_STRING) . ASN1::encodeLength($curve::SIZE) . $secret,
+ [],
+ null,
+ $password,
+ $curve instanceof Ed25519 ? 'id-Ed25519' : 'id-Ed448'
+ );
+ }
+
+ $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
+
+ $params = new ASN1\Element(self::encodeParameters($curve, false, $options));
+
+ $key = [
+ 'version' => 'ecPrivkeyVer1',
+ 'privateKey' => $privateKey->toBytes(),
+ //'parameters' => $params,
+ 'publicKey' => "\0" . $publicKey
+ ];
+
+ $key = ASN1::encodeDER($key, Maps\ECPrivateKey::MAP);
+
+ return self::wrapPrivateKey($key, [], $params, $password, 'id-ecPublicKey', '', $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Keys/PuTTY.php b/Sources/Phpseclib/Crypt/EC/Formats/Keys/PuTTY.php
new file mode 100644
index 0000000000..7e1e9170f5
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Keys/PuTTY.php
@@ -0,0 +1,138 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PuTTY as Progenitor;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PuTTY Formatted EC Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class PuTTY extends Progenitor
+{
+ use Common;
+
+ /**
+ * Public Handler
+ *
+ * @var string
+ */
+ const PUBLIC_HANDLER = 'phpseclib3\Crypt\EC\Formats\Keys\OpenSSH';
+
+ /**
+ * Supported Key Types
+ *
+ * @var array
+ */
+ protected static $types = [
+ 'ecdsa-sha2-nistp256',
+ 'ecdsa-sha2-nistp384',
+ 'ecdsa-sha2-nistp521',
+ 'ssh-ed25519'
+ ];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $components = parent::load($key, $password);
+ if (!isset($components['private'])) {
+ return $components;
+ }
+
+ $private = $components['private'];
+
+ $temp = Strings::base64_encode(Strings::packSSH2('s', $components['type']) . $components['public']);
+ $components = OpenSSH::load($components['type'] . ' ' . $temp . ' ' . $components['comment']);
+
+ if ($components['curve'] instanceof TwistedEdwardsCurve) {
+ if (Strings::shift($private, 4) != "\0\0\0\x20") {
+ throw new \RuntimeException('Length of ssh-ed25519 key should be 32');
+ }
+ $arr = $components['curve']->extractSecret($private);
+ $components['dA'] = $arr['dA'];
+ $components['secret'] = $arr['secret'];
+ } else {
+ list($components['dA']) = Strings::unpackSSH2('i', $private);
+ $components['curve']->rangeCheck($components['dA']);
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = false, array $options = [])
+ {
+ self::initialize_static_variables();
+
+ $public = explode(' ', OpenSSH::savePublicKey($curve, $publicKey));
+ $name = $public[0];
+ $public = Strings::base64_decode($public[1]);
+ list(, $length) = unpack('N', Strings::shift($public, 4));
+ Strings::shift($public, $length);
+
+ // PuTTY pads private keys with a null byte per the following:
+ // https://github.com/github/putty/blob/a3d14d77f566a41fc61dfdc5c2e0e384c9e6ae8b/sshecc.c#L1926
+ if (!$curve instanceof TwistedEdwardsCurve) {
+ $private = $privateKey->toBytes();
+ if (!(strlen($privateKey->toBits()) & 7)) {
+ $private = "\0$private";
+ }
+ }
+
+ $private = $curve instanceof TwistedEdwardsCurve ?
+ Strings::packSSH2('s', $secret) :
+ Strings::packSSH2('s', $private);
+
+ return self::wrapPrivateKey($public, $private, $name, $password, $options);
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField[] $publicKey
+ * @return string
+ */
+ public static function savePublicKey(BaseCurve $curve, array $publicKey)
+ {
+ $public = explode(' ', OpenSSH::savePublicKey($curve, $publicKey));
+ $type = $public[0];
+ $public = Strings::base64_decode($public[1]);
+ list(, $length) = unpack('N', Strings::shift($public, 4));
+ Strings::shift($public, $length);
+
+ return self::wrapPublicKey($public, $type);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Keys/XML.php b/Sources/Phpseclib/Crypt/EC/Formats/Keys/XML.php
new file mode 100644
index 0000000000..7f6cf63451
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Keys/XML.php
@@ -0,0 +1,486 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\Prime as PrimeCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Exception\BadConfigurationException;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * XML Formatted EC Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class XML
+{
+ use Common;
+
+ /**
+ * Default namespace
+ *
+ * @var string
+ */
+ private static $namespace;
+
+ /**
+ * Flag for using RFC4050 syntax
+ *
+ * @var bool
+ */
+ private static $rfc4050 = false;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ self::initialize_static_variables();
+
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ if (!class_exists('DOMDocument')) {
+ throw new BadConfigurationException('The dom extension is not setup correctly on this system');
+ }
+
+ $use_errors = libxml_use_internal_errors(true);
+
+ if (substr($key, 0, 5) != '' . $key . '';
+ }
+
+ $temp = self::isolateNamespace($key, 'http://www.w3.org/2009/xmldsig11#');
+ if ($temp) {
+ $key = $temp;
+ }
+
+ $temp = self::isolateNamespace($key, 'http://www.w3.org/2001/04/xmldsig-more#');
+ if ($temp) {
+ $key = $temp;
+ }
+
+ $dom = new \DOMDocument();
+
+ if (!$dom->loadXML($key)) {
+ libxml_use_internal_errors($use_errors);
+ throw new \UnexpectedValueException('Key does not appear to contain XML');
+ }
+ $xpath = new \DOMXPath($dom);
+ libxml_use_internal_errors($use_errors);
+ $curve = self::loadCurveByParam($xpath);
+
+ $pubkey = self::query($xpath, 'publickey', 'Public Key is not present');
+
+ $QA = self::query($xpath, 'ecdsakeyvalue')->length ?
+ self::extractPointRFC4050($xpath, $curve) :
+ self::extractPoint("\0" . $pubkey, $curve);
+
+ libxml_use_internal_errors($use_errors);
+
+ return compact('curve', 'QA');
+ }
+
+ /**
+ * Case-insensitive xpath query
+ *
+ * @param \DOMXPath $xpath
+ * @param string $name
+ * @param string $error optional
+ * @param bool $decode optional
+ * @return \DOMNodeList
+ */
+ private static function query(\DOMXPath $xpath, $name, $error = null, $decode = true)
+ {
+ $query = '/';
+ $names = explode('/', $name);
+ foreach ($names as $name) {
+ $query .= "/*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$name']";
+ }
+ $result = $xpath->query($query);
+ if (!isset($error)) {
+ return $result;
+ }
+
+ if (!$result->length) {
+ throw new \RuntimeException($error);
+ }
+ return $decode ? self::decodeValue($result->item(0)->textContent) : $result->item(0)->textContent;
+ }
+
+ /**
+ * Finds the first element in the relevant namespace, strips the namespacing and returns the XML for that element.
+ *
+ * @param string $xml
+ * @param string $ns
+ */
+ private static function isolateNamespace($xml, $ns)
+ {
+ $dom = new \DOMDocument();
+ if (!$dom->loadXML($xml)) {
+ return false;
+ }
+ $xpath = new \DOMXPath($dom);
+ $nodes = $xpath->query("//*[namespace::*[.='$ns'] and not(../namespace::*[.='$ns'])]");
+ if (!$nodes->length) {
+ return false;
+ }
+ $node = $nodes->item(0);
+ $ns_name = $node->lookupPrefix($ns);
+ if ($ns_name) {
+ $node->removeAttributeNS($ns, $ns_name);
+ }
+ return $dom->saveXML($node);
+ }
+
+ /**
+ * Decodes the value
+ *
+ * @param string $value
+ */
+ private static function decodeValue($value)
+ {
+ return Strings::base64_decode(str_replace(["\r", "\n", ' ', "\t"], '', $value));
+ }
+
+ /**
+ * Extract points from an XML document
+ *
+ * @param \DOMXPath $xpath
+ * @param BaseCurve $curve
+ * @return object[]
+ */
+ private static function extractPointRFC4050(\DOMXPath $xpath, BaseCurve $curve)
+ {
+ $x = self::query($xpath, 'publickey/x');
+ $y = self::query($xpath, 'publickey/y');
+ if (!$x->length || !$x->item(0)->hasAttribute('Value')) {
+ throw new \RuntimeException('Public Key / X coordinate not found');
+ }
+ if (!$y->length || !$y->item(0)->hasAttribute('Value')) {
+ throw new \RuntimeException('Public Key / Y coordinate not found');
+ }
+ $point = [
+ $curve->convertInteger(new BigInteger($x->item(0)->getAttribute('Value'))),
+ $curve->convertInteger(new BigInteger($y->item(0)->getAttribute('Value')))
+ ];
+ if (!$curve->verifyPoint($point)) {
+ throw new \RuntimeException('Unable to verify that point exists on curve');
+ }
+ return $point;
+ }
+
+ /**
+ * Returns an instance of \phpseclib3\Crypt\EC\BaseCurves\Base based
+ * on the curve parameters
+ *
+ * @param \DomXPath $xpath
+ * @return BaseCurve|false
+ */
+ private static function loadCurveByParam(\DOMXPath $xpath)
+ {
+ $namedCurve = self::query($xpath, 'namedcurve');
+ if ($namedCurve->length == 1) {
+ $oid = $namedCurve->item(0)->getAttribute('URN');
+ $oid = preg_replace('#[^\d.]#', '', $oid);
+ $name = array_search($oid, self::$curveOIDs);
+ if ($name === false) {
+ throw new UnsupportedCurveException('Curve with OID of ' . $oid . ' is not supported');
+ }
+
+ $curve = '\phpseclib3\Crypt\EC\Curves\\' . $name;
+ if (!class_exists($curve)) {
+ throw new UnsupportedCurveException('Named Curve of ' . $name . ' is not supported');
+ }
+ return new $curve();
+ }
+
+ $params = self::query($xpath, 'explicitparams');
+ if ($params->length) {
+ return self::loadCurveByParamRFC4050($xpath);
+ }
+
+ $params = self::query($xpath, 'ecparameters');
+ if (!$params->length) {
+ throw new \RuntimeException('No parameters are present');
+ }
+
+ $fieldTypes = [
+ 'prime-field' => ['fieldid/prime/p'],
+ 'gnb' => ['fieldid/gnb/m'],
+ 'tnb' => ['fieldid/tnb/k'],
+ 'pnb' => ['fieldid/pnb/k1', 'fieldid/pnb/k2', 'fieldid/pnb/k3'],
+ 'unknown' => []
+ ];
+
+ foreach ($fieldTypes as $type => $queries) {
+ foreach ($queries as $query) {
+ $result = self::query($xpath, $query);
+ if (!$result->length) {
+ continue 2;
+ }
+ $param = preg_replace('#.*/#', '', $query);
+ $$param = self::decodeValue($result->item(0)->textContent);
+ }
+ break;
+ }
+
+ $a = self::query($xpath, 'curve/a', 'A coefficient is not present');
+ $b = self::query($xpath, 'curve/b', 'B coefficient is not present');
+ $base = self::query($xpath, 'base', 'Base point is not present');
+ $order = self::query($xpath, 'order', 'Order is not present');
+
+ switch ($type) {
+ case 'prime-field':
+ $curve = new PrimeCurve();
+ $curve->setModulo(new BigInteger($p, 256));
+ $curve->setCoefficients(
+ new BigInteger($a, 256),
+ new BigInteger($b, 256)
+ );
+ $point = self::extractPoint("\0" . $base, $curve);
+ $curve->setBasePoint(...$point);
+ $curve->setOrder(new BigInteger($order, 256));
+ return $curve;
+ case 'gnb':
+ case 'tnb':
+ case 'pnb':
+ default:
+ throw new UnsupportedCurveException('Field Type of ' . $type . ' is not supported');
+ }
+ }
+
+ /**
+ * Returns an instance of \phpseclib3\Crypt\EC\BaseCurves\Base based
+ * on the curve parameters
+ *
+ * @param \DomXPath $xpath
+ * @return BaseCurve|false
+ */
+ private static function loadCurveByParamRFC4050(\DOMXPath $xpath)
+ {
+ $fieldTypes = [
+ 'prime-field' => ['primefieldparamstype/p'],
+ 'unknown' => []
+ ];
+
+ foreach ($fieldTypes as $type => $queries) {
+ foreach ($queries as $query) {
+ $result = self::query($xpath, $query);
+ if (!$result->length) {
+ continue 2;
+ }
+ $param = preg_replace('#.*/#', '', $query);
+ $$param = $result->item(0)->textContent;
+ }
+ break;
+ }
+
+ $a = self::query($xpath, 'curveparamstype/a', 'A coefficient is not present', false);
+ $b = self::query($xpath, 'curveparamstype/b', 'B coefficient is not present', false);
+ $x = self::query($xpath, 'basepointparams/basepoint/ecpointtype/x', 'Base Point X is not present', false);
+ $y = self::query($xpath, 'basepointparams/basepoint/ecpointtype/y', 'Base Point Y is not present', false);
+ $order = self::query($xpath, 'order', 'Order is not present', false);
+
+ switch ($type) {
+ case 'prime-field':
+ $curve = new PrimeCurve();
+
+ $p = str_replace(["\r", "\n", ' ', "\t"], '', $p);
+ $curve->setModulo(new BigInteger($p));
+
+ $a = str_replace(["\r", "\n", ' ', "\t"], '', $a);
+ $b = str_replace(["\r", "\n", ' ', "\t"], '', $b);
+ $curve->setCoefficients(
+ new BigInteger($a),
+ new BigInteger($b)
+ );
+
+ $x = str_replace(["\r", "\n", ' ', "\t"], '', $x);
+ $y = str_replace(["\r", "\n", ' ', "\t"], '', $y);
+ $curve->setBasePoint(
+ new BigInteger($x),
+ new BigInteger($y)
+ );
+
+ $order = str_replace(["\r", "\n", ' ', "\t"], '', $order);
+ $curve->setOrder(new BigInteger($order));
+ return $curve;
+ default:
+ throw new UnsupportedCurveException('Field Type of ' . $type . ' is not supported');
+ }
+ }
+
+ /**
+ * Sets the namespace. dsig11 is the most common one.
+ *
+ * Set to null to unset. Used only for creating public keys.
+ *
+ * @param string $namespace
+ */
+ public static function setNamespace($namespace)
+ {
+ self::$namespace = $namespace;
+ }
+
+ /**
+ * Uses the XML syntax specified in https://tools.ietf.org/html/rfc4050
+ */
+ public static function enableRFC4050Syntax()
+ {
+ self::$rfc4050 = true;
+ }
+
+ /**
+ * Uses the XML syntax specified in https://www.w3.org/TR/xmldsig-core/#sec-ECParameters
+ */
+ public static function disableRFC4050Syntax()
+ {
+ self::$rfc4050 = false;
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = [])
+ {
+ self::initialize_static_variables();
+
+ if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) {
+ throw new UnsupportedCurveException('TwistedEdwards and Montgomery Curves are not supported');
+ }
+
+ if (empty(static::$namespace)) {
+ $pre = $post = '';
+ } else {
+ $pre = static::$namespace . ':';
+ $post = ':' . static::$namespace;
+ }
+
+ if (self::$rfc4050) {
+ return '<' . $pre . 'ECDSAKeyValue xmlns' . $post . '="http://www.w3.org/2001/04/xmldsig-more#">' . "\r\n" .
+ self::encodeXMLParameters($curve, $pre, $options) . "\r\n" .
+ '<' . $pre . 'PublicKey>' . "\r\n" .
+ '<' . $pre . 'X Value="' . $publicKey[0] . '" />' . "\r\n" .
+ '<' . $pre . 'Y Value="' . $publicKey[1] . '" />' . "\r\n" .
+ '' . $pre . 'PublicKey>' . "\r\n" .
+ '' . $pre . 'ECDSAKeyValue>';
+ }
+
+ $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
+
+ return '<' . $pre . 'ECDSAKeyValue xmlns' . $post . '="http://www.w3.org/2009/xmldsig11#">' . "\r\n" .
+ self::encodeXMLParameters($curve, $pre, $options) . "\r\n" .
+ '<' . $pre . 'PublicKey>' . Strings::base64_encode($publicKey) . '' . $pre . 'PublicKey>' . "\r\n" .
+ '' . $pre . 'ECDSAKeyValue>';
+ }
+
+ /**
+ * Encode Parameters
+ *
+ * @param BaseCurve $curve
+ * @param string $pre
+ * @param array $options optional
+ * @return string|false
+ */
+ private static function encodeXMLParameters(BaseCurve $curve, $pre, array $options = [])
+ {
+ $result = self::encodeParameters($curve, true, $options);
+
+ if (isset($result['namedCurve'])) {
+ $namedCurve = '<' . $pre . 'NamedCurve URI="urn:oid:' . self::$curveOIDs[$result['namedCurve']] . '" />';
+ return self::$rfc4050 ?
+ '' . str_replace('URI', 'URN', $namedCurve) . '' :
+ $namedCurve;
+ }
+
+ if (self::$rfc4050) {
+ $xml = '<' . $pre . 'ExplicitParams>' . "\r\n" .
+ '<' . $pre . 'FieldParams>' . "\r\n";
+ $temp = $result['specifiedCurve'];
+ switch ($temp['fieldID']['fieldType']) {
+ case 'prime-field':
+ $xml .= '<' . $pre . 'PrimeFieldParamsType>' . "\r\n" .
+ '<' . $pre . 'P>' . $temp['fieldID']['parameters'] . '' . $pre . 'P>' . "\r\n" .
+ '' . $pre . 'PrimeFieldParamsType>' . "\r\n";
+ $a = $curve->getA();
+ $b = $curve->getB();
+ list($x, $y) = $curve->getBasePoint();
+ break;
+ default:
+ throw new UnsupportedCurveException('Field Type of ' . $temp['fieldID']['fieldType'] . ' is not supported');
+ }
+ $xml .= '' . $pre . 'FieldParams>' . "\r\n" .
+ '<' . $pre . 'CurveParamsType>' . "\r\n" .
+ '<' . $pre . 'A>' . $a . '' . $pre . 'A>' . "\r\n" .
+ '<' . $pre . 'B>' . $b . '' . $pre . 'B>' . "\r\n" .
+ '' . $pre . 'CurveParamsType>' . "\r\n" .
+ '<' . $pre . 'BasePointParams>' . "\r\n" .
+ '<' . $pre . 'BasePoint>' . "\r\n" .
+ '<' . $pre . 'ECPointType>' . "\r\n" .
+ '<' . $pre . 'X>' . $x . '' . $pre . 'X>' . "\r\n" .
+ '<' . $pre . 'Y>' . $y . '' . $pre . 'Y>' . "\r\n" .
+ '' . $pre . 'ECPointType>' . "\r\n" .
+ '' . $pre . 'BasePoint>' . "\r\n" .
+ '<' . $pre . 'Order>' . $curve->getOrder() . '' . $pre . 'Order>' . "\r\n" .
+ '' . $pre . 'BasePointParams>' . "\r\n" .
+ '' . $pre . 'ExplicitParams>' . "\r\n";
+
+ return $xml;
+ }
+
+ if (isset($result['specifiedCurve'])) {
+ $xml = '<' . $pre . 'ECParameters>' . "\r\n" .
+ '<' . $pre . 'FieldID>' . "\r\n";
+ $temp = $result['specifiedCurve'];
+ switch ($temp['fieldID']['fieldType']) {
+ case 'prime-field':
+ $xml .= '<' . $pre . 'Prime>' . "\r\n" .
+ '<' . $pre . 'P>' . Strings::base64_encode($temp['fieldID']['parameters']->toBytes()) . '' . $pre . 'P>' . "\r\n" .
+ '' . $pre . 'Prime>' . "\r\n" ;
+ break;
+ default:
+ throw new UnsupportedCurveException('Field Type of ' . $temp['fieldID']['fieldType'] . ' is not supported');
+ }
+ $xml .= '' . $pre . 'FieldID>' . "\r\n" .
+ '<' . $pre . 'Curve>' . "\r\n" .
+ '<' . $pre . 'A>' . Strings::base64_encode($temp['curve']['a']) . '' . $pre . 'A>' . "\r\n" .
+ '<' . $pre . 'B>' . Strings::base64_encode($temp['curve']['b']) . '' . $pre . 'B>' . "\r\n" .
+ '' . $pre . 'Curve>' . "\r\n" .
+ '<' . $pre . 'Base>' . Strings::base64_encode($temp['base']) . '' . $pre . 'Base>' . "\r\n" .
+ '<' . $pre . 'Order>' . Strings::base64_encode($temp['order']) . '' . $pre . 'Order>' . "\r\n" .
+ '' . $pre . 'ECParameters>';
+ return $xml;
+ }
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Keys/index.php b/Sources/Phpseclib/Crypt/EC/Formats/Keys/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Keys/index.php
@@ -0,0 +1,8 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * libsodium Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class libsodium
+{
+ use Common;
+
+ /**
+ * Is invisible flag
+ *
+ */
+ const IS_INVISIBLE = true;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ switch (strlen($key)) {
+ case 32:
+ $public = $key;
+ break;
+ case 64:
+ $private = substr($key, 0, 32);
+ $public = substr($key, -32);
+ break;
+ case 96:
+ $public = substr($key, -32);
+ if (substr($key, 32, 32) != $public) {
+ throw new \RuntimeException('Keys with 96 bytes should have the 2nd and 3rd set of 32 bytes match');
+ }
+ $private = substr($key, 0, 32);
+ break;
+ default:
+ throw new \RuntimeException('libsodium keys need to either be 32 bytes long, 64 bytes long or 96 bytes long');
+ }
+
+ $curve = new Ed25519();
+ $components = ['curve' => $curve];
+ if (isset($private)) {
+ $arr = $curve->extractSecret($private);
+ $components['dA'] = $arr['dA'];
+ $components['secret'] = $arr['secret'];
+ }
+ $components['QA'] = isset($public) ?
+ self::extractPoint($public, $curve) :
+ $curve->multiplyPoint($curve->getBasePoint(), $components['dA']);
+
+ return $components;
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param Ed25519 $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @return string
+ */
+ public static function savePublicKey(Ed25519 $curve, array $publicKey)
+ {
+ return $curve->encodePoint($publicKey);
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param Ed25519 $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, Ed25519 $curve, array $publicKey, $secret = null, $password = '')
+ {
+ if (!isset($secret)) {
+ throw new \RuntimeException('Private Key does not have a secret set');
+ }
+ if (strlen($secret) != 32) {
+ throw new \RuntimeException('Private Key secret is not of the correct length');
+ }
+ if (!empty($password) && is_string($password)) {
+ throw new UnsupportedFormatException('libsodium private keys do not support encryption');
+ }
+ return $secret . $curve->encodePoint($publicKey);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Signature/ASN1.php b/Sources/Phpseclib/Crypt/EC/Formats/Signature/ASN1.php
new file mode 100644
index 0000000000..385028b3aa
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Signature/ASN1.php
@@ -0,0 +1,62 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Signature;
+
+use phpseclib3\File\ASN1 as Encoder;
+use phpseclib3\File\ASN1\Maps\EcdsaSigValue;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * ASN1 Signature Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class ASN1
+{
+ /**
+ * Loads a signature
+ *
+ * @param string $sig
+ * @return array
+ */
+ public static function load($sig)
+ {
+ if (!is_string($sig)) {
+ return false;
+ }
+
+ $decoded = Encoder::decodeBER($sig);
+ if (empty($decoded)) {
+ return false;
+ }
+ $components = Encoder::asn1map($decoded[0], EcdsaSigValue::MAP);
+
+ return $components;
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @param BigInteger $r
+ * @param BigInteger $s
+ * @return string
+ */
+ public static function save(BigInteger $r, BigInteger $s)
+ {
+ return Encoder::encodeDER(compact('r', 's'), EcdsaSigValue::MAP);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Signature/IEEE.php b/Sources/Phpseclib/Crypt/EC/Formats/Signature/IEEE.php
new file mode 100644
index 0000000000..c5e622a12d
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Signature/IEEE.php
@@ -0,0 +1,68 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Signature;
+
+use phpseclib3\Math\BigInteger;
+
+/**
+ * ASN1 Signature Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class IEEE
+{
+ /**
+ * Loads a signature
+ *
+ * @param string $sig
+ * @return array
+ */
+ public static function load($sig)
+ {
+ if (!is_string($sig)) {
+ return false;
+ }
+
+ $len = strlen($sig);
+ if ($len & 1) {
+ return false;
+ }
+
+ $r = new BigInteger(substr($sig, 0, $len >> 1), 256);
+ $s = new BigInteger(substr($sig, $len >> 1), 256);
+
+ return compact('r', 's');
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @param BigInteger $r
+ * @param BigInteger $s
+ * @param string $curve
+ * @param int $length
+ * @return string
+ */
+ public static function save(BigInteger $r, BigInteger $s, $curve, $length)
+ {
+ $r = $r->toBytes();
+ $s = $s->toBytes();
+ $length = (int) ceil($length / 8);
+ return str_pad($r, $length, "\0", STR_PAD_LEFT) . str_pad($s, $length, "\0", STR_PAD_LEFT);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Signature/Raw.php b/Sources/Phpseclib/Crypt/EC/Formats/Signature/Raw.php
new file mode 100644
index 0000000000..7e4b47fe6c
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Signature/Raw.php
@@ -0,0 +1,25 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Signature;
+
+use phpseclib3\Crypt\Common\Formats\Signature\Raw as Progenitor;
+
+/**
+ * Raw DSA Signature Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class Raw extends Progenitor
+{
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Signature/SSH2.php b/Sources/Phpseclib/Crypt/EC/Formats/Signature/SSH2.php
new file mode 100644
index 0000000000..698c8e4cec
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Signature/SSH2.php
@@ -0,0 +1,94 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Signature;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * SSH2 Signature Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class SSH2
+{
+ /**
+ * Loads a signature
+ *
+ * @param string $sig
+ * @return mixed
+ */
+ public static function load($sig)
+ {
+ if (!is_string($sig)) {
+ return false;
+ }
+
+ $result = Strings::unpackSSH2('ss', $sig);
+ if ($result === false) {
+ return false;
+ }
+ list($type, $blob) = $result;
+ switch ($type) {
+ // see https://tools.ietf.org/html/rfc5656#section-3.1.2
+ case 'ecdsa-sha2-nistp256':
+ case 'ecdsa-sha2-nistp384':
+ case 'ecdsa-sha2-nistp521':
+ break;
+ default:
+ return false;
+ }
+
+ $result = Strings::unpackSSH2('ii', $blob);
+ if ($result === false) {
+ return false;
+ }
+
+ return [
+ 'r' => $result[0],
+ 's' => $result[1]
+ ];
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @param BigInteger $r
+ * @param BigInteger $s
+ * @param string $curve
+ * @return string
+ */
+ public static function save(BigInteger $r, BigInteger $s, $curve)
+ {
+ switch ($curve) {
+ case 'secp256r1':
+ $curve = 'nistp256';
+ break;
+ case 'secp384r1':
+ $curve = 'nistp384';
+ break;
+ case 'secp521r1':
+ $curve = 'nistp521';
+ break;
+ default:
+ return false;
+ }
+
+ $blob = Strings::packSSH2('ii', $r, $s);
+
+ return Strings::packSSH2('ss', 'ecdsa-sha2-' . $curve, $blob);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/Formats/Signature/index.php b/Sources/Phpseclib/Crypt/EC/Formats/Signature/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/Formats/Signature/index.php
@@ -0,0 +1,8 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC;
+
+use phpseclib3\Crypt\EC;
+
+/**
+ * EC Parameters
+ *
+ * @author Jim Wigginton
+ */
+final class Parameters extends EC
+{
+ /**
+ * Returns the parameters
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type = 'PKCS1', array $options = [])
+ {
+ $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
+
+ return $type::saveParameters($this->curve, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/PrivateKey.php b/Sources/Phpseclib/Crypt/EC/PrivateKey.php
new file mode 100644
index 0000000000..9947bb7d52
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/PrivateKey.php
@@ -0,0 +1,283 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\EC;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Crypt\EC\Curves\Curve25519;
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
+use phpseclib3\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Exception\UnsupportedOperationException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * EC Private Key
+ *
+ * @author Jim Wigginton
+ */
+final class PrivateKey extends EC implements Common\PrivateKey
+{
+ use Common\Traits\PasswordProtected;
+
+ /**
+ * Private Key dA
+ *
+ * sign() converts this to a BigInteger so one might wonder why this is a FiniteFieldInteger instead of
+ * a BigInteger. That's because a FiniteFieldInteger, when converted to a byte string, is null padded by
+ * a certain amount whereas a BigInteger isn't.
+ *
+ * @var object
+ */
+ protected $dA;
+
+ /**
+ * @var string
+ */
+ protected $secret;
+
+ /**
+ * Multiplies an encoded point by the private key
+ *
+ * Used by ECDH
+ *
+ * @param string $coordinates
+ * @return string
+ */
+ public function multiply($coordinates)
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ if ($this->curve instanceof Curve25519 && self::$engines['libsodium']) {
+ return sodium_crypto_scalarmult($this->dA->toBytes(), $coordinates);
+ }
+
+ $point = [$this->curve->convertInteger(new BigInteger(strrev($coordinates), 256))];
+ $point = $this->curve->multiplyPoint($point, $this->dA);
+ return strrev($point[0]->toBytes(true));
+ }
+ if (!$this->curve instanceof TwistedEdwardsCurve) {
+ $coordinates = "\0$coordinates";
+ }
+ $point = PKCS1::extractPoint($coordinates, $this->curve);
+ $point = $this->curve->multiplyPoint($point, $this->dA);
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ return $this->curve->encodePoint($point);
+ }
+ if (empty($point)) {
+ throw new \RuntimeException('The infinity point is invalid');
+ }
+ return "\4" . $point[0]->toBytes(true) . $point[1]->toBytes(true);
+ }
+
+ /**
+ * Create a signature
+ *
+ * @see self::verify()
+ * @param string $message
+ * @return mixed
+ */
+ public function sign($message)
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
+ }
+
+ $dA = $this->dA;
+ $order = $this->curve->getOrder();
+
+ $shortFormat = $this->shortFormat;
+ $format = $this->sigFormat;
+ if ($format === false) {
+ return false;
+ }
+
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) {
+ $result = sodium_crypto_sign_detached($message, $this->withPassword()->toString('libsodium'));
+ return $shortFormat == 'SSH2' ? Strings::packSSH2('ss', 'ssh-' . strtolower($this->getCurve()), $result) : $result;
+ }
+
+ // contexts (Ed25519ctx) are supported but prehashing (Ed25519ph) is not.
+ // quoting https://tools.ietf.org/html/rfc8032#section-8.5 ,
+ // "The Ed25519ph and Ed448ph variants ... SHOULD NOT be used"
+ $A = $this->curve->encodePoint($this->QA);
+ $curve = $this->curve;
+ $hash = new Hash($curve::HASH);
+
+ $secret = substr($hash->hash($this->secret), $curve::SIZE);
+
+ if ($curve instanceof Ed25519) {
+ $dom = !isset($this->context) ? '' :
+ 'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context;
+ } else {
+ $context = isset($this->context) ? $this->context : '';
+ $dom = 'SigEd448' . "\0" . chr(strlen($context)) . $context;
+ }
+ // SHA-512(dom2(F, C) || prefix || PH(M))
+ $r = $hash->hash($dom . $secret . $message);
+ $r = strrev($r);
+ $r = new BigInteger($r, 256);
+ list(, $r) = $r->divide($order);
+ $R = $curve->multiplyPoint($curve->getBasePoint(), $r);
+ $R = $curve->encodePoint($R);
+ $k = $hash->hash($dom . $R . $A . $message);
+ $k = strrev($k);
+ $k = new BigInteger($k, 256);
+ list(, $k) = $k->divide($order);
+ $S = $k->multiply($dA)->add($r);
+ list(, $S) = $S->divide($order);
+ $S = str_pad(strrev($S->toBytes()), $curve::SIZE, "\0");
+ return $shortFormat == 'SSH2' ? Strings::packSSH2('ss', 'ssh-' . strtolower($this->getCurve()), $R . $S) : $R . $S;
+ }
+
+ if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
+ $signature = '';
+ // altho PHP's OpenSSL bindings only supported EC key creation in PHP 7.1 they've long
+ // supported signing / verification
+ // we use specified curves to avoid issues with OpenSSL possibly not supporting a given named curve;
+ // doing this may mean some curve-specific optimizations can't be used but idk if OpenSSL even
+ // has curve-specific optimizations
+ $result = openssl_sign($message, $signature, $this->withPassword()->toString('PKCS8', ['namedCurve' => false]), $this->hash->getHash());
+
+ if ($result) {
+ if ($shortFormat == 'ASN1') {
+ return $signature;
+ }
+
+ $loaded = ASN1Signature::load($signature);
+ $r = $loaded['r'];
+ $s = $loaded['s'];
+
+
+ return $this->formatSignature($r, $s);
+ }
+ }
+
+ $e = $this->hash->hash($message);
+ $e = new BigInteger($e, 256);
+
+ $Ln = $this->hash->getLength() - $order->getLength();
+ $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e;
+
+ while (true) {
+ $k = BigInteger::randomRange(self::$one, $order->subtract(self::$one));
+ list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $k);
+ $x = $x->toBigInteger();
+ list(, $r) = $x->divide($order);
+ if ($r->equals(self::$zero)) {
+ continue;
+ }
+ $kinv = $k->modInverse($order);
+ $temp = $z->add($dA->multiply($r));
+ $temp = $kinv->multiply($temp);
+ list(, $s) = $temp->divide($order);
+ if (!$s->equals(self::$zero)) {
+ break;
+ }
+ }
+
+ // the following is an RFC6979 compliant implementation of deterministic ECDSA
+ // it's unused because it's mainly intended for use when a good CSPRNG isn't
+ // available. if phpseclib's CSPRNG isn't good then even key generation is
+ // suspect
+ /*
+ // if this were actually being used it'd probably be better if this lived in load() and createKey()
+ $this->q = $this->curve->getOrder();
+ $dA = $this->dA->toBigInteger();
+ $this->x = $dA;
+
+ $h1 = $this->hash->hash($message);
+ $k = $this->computek($h1);
+ list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $k);
+ $x = $x->toBigInteger();
+ list(, $r) = $x->divide($this->q);
+ $kinv = $k->modInverse($this->q);
+ $h1 = $this->bits2int($h1);
+ $temp = $h1->add($dA->multiply($r));
+ $temp = $kinv->multiply($temp);
+ list(, $s) = $temp->divide($this->q);
+ */
+
+ return $this->formatSignature($r, $s);
+ }
+
+ /**
+ * Returns the private key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePrivateKey');
+
+ return $type::savePrivateKey($this->dA, $this->curve, $this->QA, $this->secret, $this->password, $options);
+ }
+
+ /**
+ * Returns the public key
+ *
+ * @see self::getPrivateKey()
+ * @return mixed
+ */
+ public function getPublicKey()
+ {
+ $format = 'PKCS8';
+ if ($this->curve instanceof MontgomeryCurve) {
+ $format = 'MontgomeryPublic';
+ }
+
+ $type = self::validatePlugin('Keys', $format, 'savePublicKey');
+
+ $key = $type::savePublicKey($this->curve, $this->QA);
+ $key = EC::loadFormat($format, $key);
+ if ($this->curve instanceof MontgomeryCurve) {
+ return $key;
+ }
+ $key = $key
+ ->withHash($this->hash->getHash())
+ ->withSignatureFormat($this->shortFormat);
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ $key = $key->withContext($this->context);
+ }
+ return $key;
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @return string
+ */
+ private function formatSignature(BigInteger $r, BigInteger $s)
+ {
+ $format = $this->sigFormat;
+
+ $temp = new \ReflectionMethod($format, 'save');
+ $paramCount = $temp->getNumberOfRequiredParameters();
+
+ // @codingStandardsIgnoreStart
+ switch ($paramCount) {
+ case 2: return $format::save($r, $s);
+ case 3: return $format::save($r, $s, $this->getCurve());
+ case 4: return $format::save($r, $s, $this->getCurve(), $this->getLength());
+ }
+ // @codingStandardsIgnoreEnd
+
+ // presumably the only way you could get to this is if you were using a custom plugin
+ throw new UnsupportedOperationException("$format::save() has $paramCount parameters - the only valid parameter counts are 2 or 3");
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/PublicKey.php b/Sources/Phpseclib/Crypt/EC/PublicKey.php
new file mode 100644
index 0000000000..d34c6c4dd3
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/PublicKey.php
@@ -0,0 +1,173 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\EC;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
+use phpseclib3\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Exception\UnsupportedOperationException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * EC Public Key
+ *
+ * @author Jim Wigginton
+ */
+final class PublicKey extends EC implements Common\PublicKey
+{
+ use Common\Traits\Fingerprint;
+
+ /**
+ * Verify a signature
+ *
+ * @see self::verify()
+ * @param string $message
+ * @param string $signature
+ * @return mixed
+ */
+ public function verify($message, $signature)
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
+ }
+
+ $shortFormat = $this->shortFormat;
+ $format = $this->sigFormat;
+ if ($format === false) {
+ return false;
+ }
+
+ $order = $this->curve->getOrder();
+
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ if ($shortFormat == 'SSH2') {
+ list(, $signature) = Strings::unpackSSH2('ss', $signature);
+ }
+
+ if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) {
+ return sodium_crypto_sign_verify_detached($signature, $message, $this->toString('libsodium'));
+ }
+
+ $curve = $this->curve;
+ if (strlen($signature) != 2 * $curve::SIZE) {
+ return false;
+ }
+
+ $R = substr($signature, 0, $curve::SIZE);
+ $S = substr($signature, $curve::SIZE);
+
+ try {
+ $R = PKCS1::extractPoint($R, $curve);
+ $R = $this->curve->convertToInternal($R);
+ } catch (\Exception $e) {
+ return false;
+ }
+
+ $S = strrev($S);
+ $S = new BigInteger($S, 256);
+
+ if ($S->compare($order) >= 0) {
+ return false;
+ }
+
+ $A = $curve->encodePoint($this->QA);
+
+ if ($curve instanceof Ed25519) {
+ $dom2 = !isset($this->context) ? '' :
+ 'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context;
+ } else {
+ $context = isset($this->context) ? $this->context : '';
+ $dom2 = 'SigEd448' . "\0" . chr(strlen($context)) . $context;
+ }
+
+ $hash = new Hash($curve::HASH);
+ $k = $hash->hash($dom2 . substr($signature, 0, $curve::SIZE) . $A . $message);
+ $k = strrev($k);
+ $k = new BigInteger($k, 256);
+ list(, $k) = $k->divide($order);
+
+ $qa = $curve->convertToInternal($this->QA);
+
+ $lhs = $curve->multiplyPoint($curve->getBasePoint(), $S);
+ $rhs = $curve->multiplyPoint($qa, $k);
+ $rhs = $curve->addPoint($rhs, $R);
+ $rhs = $curve->convertToAffine($rhs);
+
+ return $lhs[0]->equals($rhs[0]) && $lhs[1]->equals($rhs[1]);
+ }
+
+ $params = $format::load($signature);
+ if ($params === false || count($params) != 2) {
+ return false;
+ }
+ $r = $params['r'];
+ $s = $params['s'];
+
+ if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
+ $sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature;
+
+ $result = openssl_verify($message, $sig, $this->toString('PKCS8', ['namedCurve' => false]), $this->hash->getHash());
+
+ if ($result != -1) {
+ return (bool) $result;
+ }
+ }
+
+ $n_1 = $order->subtract(self::$one);
+ if (!$r->between(self::$one, $n_1) || !$s->between(self::$one, $n_1)) {
+ return false;
+ }
+
+ $e = $this->hash->hash($message);
+ $e = new BigInteger($e, 256);
+
+ $Ln = $this->hash->getLength() - $order->getLength();
+ $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e;
+
+ $w = $s->modInverse($order);
+ list(, $u1) = $z->multiply($w)->divide($order);
+ list(, $u2) = $r->multiply($w)->divide($order);
+
+ $u1 = $this->curve->convertInteger($u1);
+ $u2 = $this->curve->convertInteger($u2);
+
+ list($x1, $y1) = $this->curve->multiplyAddPoints(
+ [$this->curve->getBasePoint(), $this->QA],
+ [$u1, $u2]
+ );
+
+ $x1 = $x1->toBigInteger();
+ list(, $x1) = $x1->divide($order);
+
+ return $x1->equals($r);
+ }
+
+ /**
+ * Returns the public key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePublicKey');
+
+ return $type::savePublicKey($this->curve, $this->QA, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/EC/index.php b/Sources/Phpseclib/Crypt/EC/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/EC/index.php
@@ -0,0 +1,8 @@
+
+ * setKey('abcdefg');
+ *
+ * echo base64_encode($hash->hash('abcdefg'));
+ * ?>
+ *
+ *
+ * @author Jim Wigginton
+ * @copyright 2015 Jim Wigginton
+ * @author Andreas Fischer
+ * @copyright 2015 Andreas Fischer
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Exception\InsufficientSetupException;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\PrimeField;
+
+/**
+ * @author Jim Wigginton
+ * @author Andreas Fischer
+ */
+class Hash
+{
+ /**
+ * Padding Types
+ *
+ */
+ const PADDING_KECCAK = 1;
+
+ /**
+ * Padding Types
+ *
+ */
+ const PADDING_SHA3 = 2;
+
+ /**
+ * Padding Types
+ *
+ */
+ const PADDING_SHAKE = 3;
+
+ /**
+ * Padding Type
+ *
+ * Only used by SHA3
+ *
+ * @var int
+ */
+ private $paddingType = 0;
+
+ /**
+ * Hash Parameter
+ *
+ * @see self::setHash()
+ * @var int
+ */
+ private $hashParam;
+
+ /**
+ * Byte-length of hash output (Internal HMAC)
+ *
+ * @see self::setHash()
+ * @var int
+ */
+ private $length;
+
+ /**
+ * Hash Algorithm
+ *
+ * @see self::setHash()
+ * @var string
+ */
+ private $algo;
+
+ /**
+ * Key
+ *
+ * @see self::setKey()
+ * @var string
+ */
+ private $key = false;
+
+ /**
+ * Nonce
+ *
+ * @see self::setNonce()
+ * @var string
+ */
+ private $nonce = false;
+
+ /**
+ * Hash Parameters
+ *
+ * @var array
+ */
+ private $parameters = [];
+
+ /**
+ * Computed Key
+ *
+ * @see self::_computeKey()
+ * @var string
+ */
+ private $computedKey = false;
+
+ /**
+ * Outer XOR (Internal HMAC)
+ *
+ * Used only for sha512
+ *
+ * @see self::hash()
+ * @var string
+ */
+ private $opad;
+
+ /**
+ * Inner XOR (Internal HMAC)
+ *
+ * Used only for sha512
+ *
+ * @see self::hash()
+ * @var string
+ */
+ private $ipad;
+
+ /**
+ * Recompute AES Key
+ *
+ * Used only for umac
+ *
+ * @see self::hash()
+ * @var boolean
+ */
+ private $recomputeAESKey;
+
+ /**
+ * umac cipher object
+ *
+ * @see self::hash()
+ * @var AES
+ */
+ private $c;
+
+ /**
+ * umac pad
+ *
+ * @see self::hash()
+ * @var string
+ */
+ private $pad;
+
+ /**
+ * Block Size
+ *
+ * @var int
+ */
+ private $blockSize;
+
+ /**#@+
+ * UMAC variables
+ *
+ * @var PrimeField
+ */
+ private static $factory36;
+ private static $factory64;
+ private static $factory128;
+ private static $offset64;
+ private static $offset128;
+ private static $marker64;
+ private static $marker128;
+ private static $maxwordrange64;
+ private static $maxwordrange128;
+ /**#@-*/
+
+ /**#@+
+ * AES_CMAC variables
+ *
+ * @var string
+ */
+ private $k1;
+ private $k2;
+ /**#@-*/
+
+ /**
+ * Default Constructor.
+ *
+ * @param string $hash
+ */
+ public function __construct($hash = 'sha256')
+ {
+ $this->setHash($hash);
+ }
+
+ /**
+ * Sets the key for HMACs
+ *
+ * Keys can be of any length.
+ *
+ * @param string $key
+ */
+ public function setKey($key = false)
+ {
+ $this->key = $key;
+ $this->computeKey();
+ $this->recomputeAESKey = true;
+ }
+
+ /**
+ * Sets the nonce for UMACs
+ *
+ * Keys can be of any length.
+ *
+ * @param string $nonce
+ */
+ public function setNonce($nonce = false)
+ {
+ switch (true) {
+ case !is_string($nonce):
+ case strlen($nonce) > 0 && strlen($nonce) <= 16:
+ $this->recomputeAESKey = true;
+ $this->nonce = $nonce;
+ return;
+ }
+
+ throw new \LengthException('The nonce length must be between 1 and 16 bytes, inclusive');
+ }
+
+ /**
+ * Pre-compute the key used by the HMAC
+ *
+ * Quoting http://tools.ietf.org/html/rfc2104#section-2, "Applications that use keys longer than B bytes
+ * will first hash the key using H and then use the resultant L byte string as the actual key to HMAC."
+ *
+ * As documented in https://www.reddit.com/r/PHP/comments/9nct2l/symfonypolyfill_hash_pbkdf2_correct_fix_for/
+ * when doing an HMAC multiple times it's faster to compute the hash once instead of computing it during
+ * every call
+ *
+ */
+ private function computeKey()
+ {
+ if ($this->key === false) {
+ $this->computedKey = false;
+ return;
+ }
+
+ if (strlen($this->key) <= $this->getBlockLengthInBytes()) {
+ $this->computedKey = $this->key;
+ return;
+ }
+
+ $this->computedKey = is_array($this->algo) ?
+ call_user_func($this->algo, $this->key) :
+ hash($this->algo, $this->key, true);
+ }
+
+ /**
+ * Gets the hash function.
+ *
+ * As set by the constructor or by the setHash() method.
+ *
+ * @return string
+ */
+ public function getHash()
+ {
+ return $this->hashParam;
+ }
+
+ /**
+ * Sets the hash function.
+ *
+ * @param string $hash
+ */
+ public function setHash($hash)
+ {
+ $oldHash = $this->hashParam;
+ $this->hashParam = $hash = strtolower($hash);
+ switch ($hash) {
+ case 'umac-32':
+ case 'umac-64':
+ case 'umac-96':
+ case 'umac-128':
+ if ($oldHash != $this->hashParam) {
+ $this->recomputeAESKey = true;
+ }
+ $this->blockSize = 128;
+ $this->length = abs(substr($hash, -3)) >> 3;
+ $this->algo = 'umac';
+ return;
+ case 'aes_cmac':
+ if ($oldHash != $this->hashParam) {
+ $this->recomputeAESKey = true;
+ }
+ $this->blockSize = 128;
+ $this->length = 16;
+ $this->algo = 'aes_cmac';
+ return;
+ case 'md2-96':
+ case 'md5-96':
+ case 'sha1-96':
+ case 'sha224-96':
+ case 'sha256-96':
+ case 'sha384-96':
+ case 'sha512-96':
+ case 'sha512/224-96':
+ case 'sha512/256-96':
+ $hash = substr($hash, 0, -3);
+ $this->length = 12; // 96 / 8 = 12
+ break;
+ case 'md2':
+ case 'md5':
+ $this->length = 16;
+ break;
+ case 'sha1':
+ $this->length = 20;
+ break;
+ case 'sha224':
+ case 'sha512/224':
+ case 'sha3-224':
+ $this->length = 28;
+ break;
+ case 'keccak256':
+ $this->paddingType = self::PADDING_KECCAK;
+ // fall-through
+ case 'sha256':
+ case 'sha512/256':
+ case 'sha3-256':
+ $this->length = 32;
+ break;
+ case 'sha384':
+ case 'sha3-384':
+ $this->length = 48;
+ break;
+ case 'sha512':
+ case 'sha3-512':
+ $this->length = 64;
+ break;
+ default:
+ if (preg_match('#^(shake(?:128|256))-(\d+)$#', $hash, $matches)) {
+ $this->paddingType = self::PADDING_SHAKE;
+ $hash = $matches[1];
+ $this->length = $matches[2] >> 3;
+ } else {
+ throw new UnsupportedAlgorithmException(
+ "$hash is not a supported algorithm"
+ );
+ }
+ }
+
+ switch ($hash) {
+ case 'md2':
+ case 'md2-96':
+ $this->blockSize = 128;
+ break;
+ case 'md5-96':
+ case 'sha1-96':
+ case 'sha224-96':
+ case 'sha256-96':
+ case 'md5':
+ case 'sha1':
+ case 'sha224':
+ case 'sha256':
+ $this->blockSize = 512;
+ break;
+ case 'sha3-224':
+ $this->blockSize = 1152; // 1600 - 2*224
+ break;
+ case 'sha3-256':
+ case 'shake256':
+ case 'keccak256':
+ $this->blockSize = 1088; // 1600 - 2*256
+ break;
+ case 'sha3-384':
+ $this->blockSize = 832; // 1600 - 2*384
+ break;
+ case 'sha3-512':
+ $this->blockSize = 576; // 1600 - 2*512
+ break;
+ case 'shake128':
+ $this->blockSize = 1344; // 1600 - 2*128
+ break;
+ default:
+ $this->blockSize = 1024;
+ }
+
+ if (in_array(substr($hash, 0, 5), ['sha3-', 'shake', 'kecca'])) {
+ // PHP 7.1.0 introduced support for "SHA3 fixed mode algorithms":
+ // http://php.net/ChangeLog-7.php#7.1.0
+ if (version_compare(PHP_VERSION, '7.1.0') < 0 || substr($hash, 0, 5) != 'sha3-') {
+ //preg_match('#(\d+)$#', $hash, $matches);
+ //$this->parameters['capacity'] = 2 * $matches[1]; // 1600 - $this->blockSize
+ //$this->parameters['rate'] = 1600 - $this->parameters['capacity']; // == $this->blockSize
+ if (!$this->paddingType) {
+ $this->paddingType = self::PADDING_SHA3;
+ }
+ $this->parameters = [
+ 'capacity' => 1600 - $this->blockSize,
+ 'rate' => $this->blockSize,
+ 'length' => $this->length,
+ 'padding' => $this->paddingType
+ ];
+ $hash = ['phpseclib3\Crypt\Hash', PHP_INT_SIZE == 8 ? 'sha3_64' : 'sha3_32'];
+ }
+ }
+
+ if ($hash == 'sha512/224' || $hash == 'sha512/256') {
+ // PHP 7.1.0 introduced sha512/224 and sha512/256 support:
+ // http://php.net/ChangeLog-7.php#7.1.0
+ if (version_compare(PHP_VERSION, '7.1.0') < 0) {
+ // from http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf#page=24
+ $initial = $hash == 'sha512/256' ?
+ [
+ '22312194FC2BF72C', '9F555FA3C84C64C2', '2393B86B6F53B151', '963877195940EABD',
+ '96283EE2A88EFFE3', 'BE5E1E2553863992', '2B0199FC2C85B8AA', '0EB72DDC81C52CA2'
+ ] :
+ [
+ '8C3D37C819544DA2', '73E1996689DCD4D6', '1DFAB7AE32FF9C82', '679DD514582F9FCF',
+ '0F6D2B697BD44DA8', '77E36F7304C48942', '3F9D85A86A1D36C8', '1112E6AD91D692A1'
+ ];
+ for ($i = 0; $i < 8; $i++) {
+ if (PHP_INT_SIZE == 8) {
+ list(, $initial[$i]) = unpack('J', pack('H*', $initial[$i]));
+ } else {
+ $initial[$i] = new BigInteger($initial[$i], 16);
+ $initial[$i]->setPrecision(64);
+ }
+ }
+
+ $this->parameters = compact('initial');
+
+ $hash = ['phpseclib3\Crypt\Hash', PHP_INT_SIZE == 8 ? 'sha512_64' : 'sha512'];
+ }
+ }
+
+ if (is_array($hash)) {
+ $b = $this->blockSize >> 3;
+ $this->ipad = str_repeat(chr(0x36), $b);
+ $this->opad = str_repeat(chr(0x5C), $b);
+ }
+
+ $this->algo = $hash;
+
+ $this->computeKey();
+ }
+
+ /**
+ * KDF: Key-Derivation Function
+ *
+ * The key-derivation function generates pseudorandom bits used to key the hash functions.
+ *
+ * @param int $index a non-negative integer less than 2^64
+ * @param int $numbytes a non-negative integer less than 2^64
+ * @return string string of length numbytes bytes
+ */
+ private function kdf($index, $numbytes)
+ {
+ $this->c->setIV(pack('N4', 0, $index, 0, 1));
+
+ return $this->c->encrypt(str_repeat("\0", $numbytes));
+ }
+
+ /**
+ * PDF Algorithm
+ *
+ * @return string string of length taglen bytes.
+ */
+ private function pdf()
+ {
+ $k = $this->key;
+ $nonce = $this->nonce;
+ $taglen = $this->length;
+
+ //
+ // Extract and zero low bit(s) of Nonce if needed
+ //
+ if ($taglen <= 8) {
+ $last = strlen($nonce) - 1;
+ $mask = $taglen == 4 ? "\3" : "\1";
+ $index = $nonce[$last] & $mask;
+ $nonce[$last] = $nonce[$last] ^ $index;
+ }
+
+ //
+ // Make Nonce BLOCKLEN bytes by appending zeroes if needed
+ //
+ $nonce = str_pad($nonce, 16, "\0");
+
+ //
+ // Generate subkey, encipher and extract indexed substring
+ //
+ $kp = $this->kdf(0, 16);
+ $c = new AES('ctr');
+ $c->disablePadding();
+ $c->setKey($kp);
+ $c->setIV($nonce);
+ $t = $c->encrypt("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
+
+ // we could use ord() but per https://paragonie.com/blog/2016/06/constant-time-encoding-boring-cryptography-rfc-4648-and-you
+ // unpack() doesn't leak timing info
+ return $taglen <= 8 ?
+ substr($t, unpack('C', $index)[1] * $taglen, $taglen) :
+ substr($t, 0, $taglen);
+ }
+
+ /**
+ * UHASH Algorithm
+ *
+ * @param string $m string of length less than 2^67 bits.
+ * @param int $taglen the integer 4, 8, 12 or 16.
+ * @return string string of length taglen bytes.
+ */
+ private function uhash($m, $taglen)
+ {
+ //
+ // One internal iteration per 4 bytes of output
+ //
+ $iters = $taglen >> 2;
+
+ //
+ // Define total key needed for all iterations using KDF.
+ // L1Key reuses most key material between iterations.
+ //
+ //$L1Key = $this->kdf(1, 1024 + ($iters - 1) * 16);
+ $L1Key = $this->kdf(1, (1024 + ($iters - 1)) * 16);
+ $L2Key = $this->kdf(2, $iters * 24);
+ $L3Key1 = $this->kdf(3, $iters * 64);
+ $L3Key2 = $this->kdf(4, $iters * 4);
+
+ //
+ // For each iteration, extract key and do three-layer hash.
+ // If bytelength(M) <= 1024, then skip L2-HASH.
+ //
+ $y = '';
+ for ($i = 0; $i < $iters; $i++) {
+ $L1Key_i = substr($L1Key, $i * 16, 1024);
+ $L2Key_i = substr($L2Key, $i * 24, 24);
+ $L3Key1_i = substr($L3Key1, $i * 64, 64);
+ $L3Key2_i = substr($L3Key2, $i * 4, 4);
+
+ $a = self::L1Hash($L1Key_i, $m);
+ $b = strlen($m) <= 1024 ? "\0\0\0\0\0\0\0\0$a" : self::L2Hash($L2Key_i, $a);
+ $c = self::L3Hash($L3Key1_i, $L3Key2_i, $b);
+ $y .= $c;
+ }
+
+ return $y;
+ }
+
+ /**
+ * L1-HASH Algorithm
+ *
+ * The first-layer hash breaks the message into 1024-byte chunks and
+ * hashes each with a function called NH. Concatenating the results
+ * forms a string, which is up to 128 times shorter than the original.
+ *
+ * @param string $k string of length 1024 bytes.
+ * @param string $m string of length less than 2^67 bits.
+ * @return string string of length (8 * ceil(bitlength(M)/8192)) bytes.
+ */
+ private static function L1Hash($k, $m)
+ {
+ //
+ // Break M into 1024 byte chunks (final chunk may be shorter)
+ //
+ $m = str_split($m, 1024);
+
+ //
+ // For each chunk, except the last: endian-adjust, NH hash
+ // and add bit-length. Use results to build Y.
+ //
+ $length = 1024 * 8;
+ $y = '';
+
+ for ($i = 0; $i < count($m) - 1; $i++) {
+ $m[$i] = pack('N*', ...unpack('V*', $m[$i])); // ENDIAN-SWAP
+ $y .= PHP_INT_SIZE == 8 ?
+ static::nh64($k, $m[$i], $length) :
+ static::nh32($k, $m[$i], $length);
+ }
+
+ //
+ // For the last chunk: pad to 32-byte boundary, endian-adjust,
+ // NH hash and add bit-length. Concatenate the result to Y.
+ //
+ $length = count($m) ? strlen($m[$i]) : 0;
+ $pad = 32 - ($length % 32);
+ $pad = max(32, $length + $pad % 32);
+ $m[$i] = str_pad(isset($m[$i]) ? $m[$i] : '', $pad, "\0"); // zeropad
+ $m[$i] = pack('N*', ...unpack('V*', $m[$i])); // ENDIAN-SWAP
+
+ $y .= PHP_INT_SIZE == 8 ?
+ static::nh64($k, $m[$i], $length * 8) :
+ static::nh32($k, $m[$i], $length * 8);
+
+ return $y;
+ }
+
+ /**
+ * 32-bit safe 64-bit Multiply with 2x 32-bit ints
+ *
+ * @param int $x
+ * @param int $y
+ * @return string $x * $y
+ */
+ private static function mul32_64($x, $y)
+ {
+ // see mul64() for a more detailed explanation of how this works
+
+ $x1 = ($x >> 16) & 0xFFFF;
+ $x0 = $x & 0xFFFF;
+
+ $y1 = ($y >> 16) & 0xFFFF;
+ $y0 = $y & 0xFFFF;
+
+ // the following 3x lines will possibly yield floats
+ $z2 = $x1 * $y1;
+ $z0 = $x0 * $y0;
+ $z1 = $x1 * $y0 + $x0 * $y1;
+
+ $a = intval(fmod($z0, 65536));
+ $b = intval($z0 / 65536) + intval(fmod($z1, 65536));
+ $c = intval($z1 / 65536) + intval(fmod($z2, 65536)) + intval($b / 65536);
+ $b = intval(fmod($b, 65536));
+ $d = intval($z2 / 65536) + intval($c / 65536);
+ $c = intval(fmod($c, 65536));
+ $d = intval(fmod($d, 65536));
+
+ return pack('n4', $d, $c, $b, $a);
+ }
+
+ /**
+ * 32-bit safe 64-bit Addition with 2x 64-bit strings
+ *
+ * @param int $x
+ * @param int $y
+ * @return int $x * $y
+ */
+ private static function add32_64($x, $y)
+ {
+ list(, $x1, $x2, $x3, $x4) = unpack('n4', $x);
+ list(, $y1, $y2, $y3, $y4) = unpack('n4', $y);
+ $a = $x4 + $y4;
+ $b = $x3 + $y3 + ($a >> 16);
+ $c = $x2 + $y2 + ($b >> 16);
+ $d = $x1 + $y1 + ($c >> 16);
+ return pack('n4', $d, $c, $b, $a);
+ }
+
+ /**
+ * 32-bit safe 32-bit Addition with 2x 32-bit strings
+ *
+ * @param int $x
+ * @param int $y
+ * @return int $x * $y
+ */
+ private static function add32($x, $y)
+ {
+ // see add64() for a more detailed explanation of how this works
+
+ $x1 = $x & 0xFFFF;
+ $x2 = ($x >> 16) & 0xFFFF;
+ $y1 = $y & 0xFFFF;
+ $y2 = ($y >> 16) & 0xFFFF;
+
+ $a = $x1 + $y1;
+ $b = ($x2 + $y2 + ($a >> 16)) << 16;
+ $a &= 0xFFFF;
+
+ return $a | $b;
+ }
+
+ /**
+ * NH Algorithm / 32-bit safe
+ *
+ * @param string $k string of length 1024 bytes.
+ * @param string $m string with length divisible by 32 bytes.
+ * @return string string of length 8 bytes.
+ */
+ private static function nh32($k, $m, $length)
+ {
+ //
+ // Break M and K into 4-byte chunks
+ //
+ $k = unpack('N*', $k);
+ $m = unpack('N*', $m);
+ $t = count($m);
+
+ //
+ // Perform NH hash on the chunks, pairing words for multiplication
+ // which are 4 apart to accommodate vector-parallelism.
+ //
+ $i = 1;
+ $y = "\0\0\0\0\0\0\0\0";
+ while ($i <= $t) {
+ $temp = self::add32($m[$i], $k[$i]);
+ $temp2 = self::add32($m[$i + 4], $k[$i + 4]);
+ $y = self::add32_64($y, self::mul32_64($temp, $temp2));
+
+ $temp = self::add32($m[$i + 1], $k[$i + 1]);
+ $temp2 = self::add32($m[$i + 5], $k[$i + 5]);
+ $y = self::add32_64($y, self::mul32_64($temp, $temp2));
+
+ $temp = self::add32($m[$i + 2], $k[$i + 2]);
+ $temp2 = self::add32($m[$i + 6], $k[$i + 6]);
+ $y = self::add32_64($y, self::mul32_64($temp, $temp2));
+
+ $temp = self::add32($m[$i + 3], $k[$i + 3]);
+ $temp2 = self::add32($m[$i + 7], $k[$i + 7]);
+ $y = self::add32_64($y, self::mul32_64($temp, $temp2));
+
+ $i += 8;
+ }
+
+ return self::add32_64($y, pack('N2', 0, $length));
+ }
+
+ /**
+ * 64-bit Multiply with 2x 32-bit ints
+ *
+ * @param int $x
+ * @param int $y
+ * @return int $x * $y
+ */
+ private static function mul64($x, $y)
+ {
+ // since PHP doesn't implement unsigned integers we'll implement them with signed integers
+ // to do this we'll use karatsuba multiplication
+
+ $x1 = $x >> 16;
+ $x0 = $x & 0xFFFF;
+
+ $y1 = $y >> 16;
+ $y0 = $y & 0xFFFF;
+
+ $z2 = $x1 * $y1; // up to 32 bits long
+ $z0 = $x0 * $y0; // up to 32 bits long
+ $z1 = $x1 * $y0 + $x0 * $y1; // up to 33 bit long
+ // normally karatsuba multiplication calculates $z1 thusly:
+ //$z1 = ($x1 + $x0) * ($y0 + $y1) - $z2 - $z0;
+ // the idea being to eliminate one extra multiplication. for arbitrary precision math that makes sense
+ // but not for this purpose
+
+ // at this point karatsuba would normally return this:
+ //return ($z2 << 64) + ($z1 << 32) + $z0;
+ // the problem is that the output could be out of range for signed 64-bit ints,
+ // which would cause PHP to switch to floats, which would risk losing the lower few bits
+ // as such we'll OR 4x 16-bit blocks together like so:
+ /*
+ ........ | ........ | ........ | ........
+ upper $z2 | lower $z2 | lower $z1 | lower $z0
+ | +upper $z1 | +upper $z0 |
+ + $carry | + $carry | |
+ */
+ // technically upper $z1 is 17 bit - not 16 - but the most significant digit of that will
+ // just get added to $carry
+
+ $a = $z0 & 0xFFFF;
+ $b = ($z0 >> 16) + ($z1 & 0xFFFF);
+ $c = ($z1 >> 16) + ($z2 & 0xFFFF) + ($b >> 16);
+ $b = ($b & 0xFFFF) << 16;
+ $d = ($z2 >> 16) + ($c >> 16);
+ $c = ($c & 0xFFFF) << 32;
+ $d = ($d & 0xFFFF) << 48;
+
+ return $a | $b | $c | $d;
+ }
+
+ /**
+ * 64-bit Addition with 2x 64-bit ints
+ *
+ * @param int $x
+ * @param int $y
+ * @return int $x + $y
+ */
+ private static function add64($x, $y)
+ {
+ // doing $x + $y risks returning a result that's out of range for signed 64-bit ints
+ // in that event PHP would convert the result to a float and precision would be lost
+ // so we'll just add 2x 32-bit ints together like so:
+ /*
+ ........ | ........
+ upper $x | lower $x
+ +upper $y |+lower $y
+ + $carry |
+ */
+ $x1 = $x & 0xFFFFFFFF;
+ $x2 = ($x >> 32) & 0xFFFFFFFF;
+ $y1 = $y & 0xFFFFFFFF;
+ $y2 = ($y >> 32) & 0xFFFFFFFF;
+
+ $a = $x1 + $y1;
+ $b = ($x2 + $y2 + ($a >> 32)) << 32;
+ $a &= 0xFFFFFFFF;
+
+ return $a | $b;
+ }
+
+ /**
+ * NH Algorithm / 64-bit safe
+ *
+ * @param string $k string of length 1024 bytes.
+ * @param string $m string with length divisible by 32 bytes.
+ * @return string string of length 8 bytes.
+ */
+ private static function nh64($k, $m, $length)
+ {
+ //
+ // Break M and K into 4-byte chunks
+ //
+ $k = unpack('N*', $k);
+ $m = unpack('N*', $m);
+ $t = count($m);
+
+ //
+ // Perform NH hash on the chunks, pairing words for multiplication
+ // which are 4 apart to accommodate vector-parallelism.
+ //
+ $i = 1;
+ $y = 0;
+ while ($i <= $t) {
+ $temp = ($m[$i] + $k[$i]) & 0xFFFFFFFF;
+ $temp2 = ($m[$i + 4] + $k[$i + 4]) & 0xFFFFFFFF;
+ $y = self::add64($y, self::mul64($temp, $temp2));
+
+ $temp = ($m[$i + 1] + $k[$i + 1]) & 0xFFFFFFFF;
+ $temp2 = ($m[$i + 5] + $k[$i + 5]) & 0xFFFFFFFF;
+ $y = self::add64($y, self::mul64($temp, $temp2));
+
+ $temp = ($m[$i + 2] + $k[$i + 2]) & 0xFFFFFFFF;
+ $temp2 = ($m[$i + 6] + $k[$i + 6]) & 0xFFFFFFFF;
+ $y = self::add64($y, self::mul64($temp, $temp2));
+
+ $temp = ($m[$i + 3] + $k[$i + 3]) & 0xFFFFFFFF;
+ $temp2 = ($m[$i + 7] + $k[$i + 7]) & 0xFFFFFFFF;
+ $y = self::add64($y, self::mul64($temp, $temp2));
+
+ $i += 8;
+ }
+
+ return pack('J', self::add64($y, $length));
+ }
+
+ /**
+ * L2-HASH: Second-Layer Hash
+ *
+ * The second-layer rehashes the L1-HASH output using a polynomial hash
+ * called POLY. If the L1-HASH output is long, then POLY is called once
+ * on a prefix of the L1-HASH output and called using different settings
+ * on the remainder. (This two-step hashing of the L1-HASH output is
+ * needed only if the message length is greater than 16 megabytes.)
+ * Careful implementation of POLY is necessary to avoid a possible
+ * timing attack (see Section 6.6 for more information).
+ *
+ * @param string $k string of length 24 bytes.
+ * @param string $m string of length less than 2^64 bytes.
+ * @return string string of length 16 bytes.
+ */
+ private static function L2Hash($k, $m)
+ {
+ //
+ // Extract keys and restrict to special key-sets
+ //
+ $k64 = $k & "\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF";
+ $k64 = new BigInteger($k64, 256);
+ $k128 = substr($k, 8) & "\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF";
+ $k128 = new BigInteger($k128, 256);
+
+ //
+ // If M is no more than 2^17 bytes, hash under 64-bit prime,
+ // otherwise, hash first 2^17 bytes under 64-bit prime and
+ // remainder under 128-bit prime.
+ //
+ if (strlen($m) <= 0x20000) { // 2^14 64-bit words
+ $y = self::poly(64, self::$maxwordrange64, $k64, $m);
+ } else {
+ $m_1 = substr($m, 0, 0x20000); // 1 << 17
+ $m_2 = substr($m, 0x20000) . "\x80";
+ $length = strlen($m_2);
+ $pad = 16 - ($length % 16);
+ $pad %= 16;
+ $m_2 = str_pad($m_2, $length + $pad, "\0"); // zeropad
+ $y = self::poly(64, self::$maxwordrange64, $k64, $m_1);
+ $y = str_pad($y, 16, "\0", STR_PAD_LEFT);
+ $y = self::poly(128, self::$maxwordrange128, $k128, $y . $m_2);
+ }
+
+ return str_pad($y, 16, "\0", STR_PAD_LEFT);
+ }
+
+ /**
+ * POLY Algorithm
+ *
+ * @param int $wordbits the integer 64 or 128.
+ * @param BigInteger $maxwordrange positive integer less than 2^wordbits.
+ * @param BigInteger $k integer in the range 0 ... prime(wordbits) - 1.
+ * @param string $m string with length divisible by (wordbits / 8) bytes.
+ * @return integer in the range 0 ... prime(wordbits) - 1.
+ */
+ private static function poly($wordbits, $maxwordrange, $k, $m)
+ {
+ //
+ // Define constants used for fixing out-of-range words
+ //
+ $wordbytes = $wordbits >> 3;
+ if ($wordbits == 128) {
+ $factory = self::$factory128;
+ $offset = self::$offset128;
+ $marker = self::$marker128;
+ } else {
+ $factory = self::$factory64;
+ $offset = self::$offset64;
+ $marker = self::$marker64;
+ }
+
+ $k = $factory->newInteger($k);
+
+ //
+ // Break M into chunks of length wordbytes bytes
+ //
+ $m_i = str_split($m, $wordbytes);
+
+ //
+ // Each input word m is compared with maxwordrange. If not smaller
+ // then 'marker' and (m - offset), both in range, are hashed.
+ //
+ $y = $factory->newInteger(new BigInteger(1));
+ foreach ($m_i as $m) {
+ $m = $factory->newInteger(new BigInteger($m, 256));
+ if ($m->compare($maxwordrange) >= 0) {
+ $y = $k->multiply($y)->add($marker);
+ $y = $k->multiply($y)->add($m->subtract($offset));
+ } else {
+ $y = $k->multiply($y)->add($m);
+ }
+ }
+
+ return $y->toBytes();
+ }
+
+ /**
+ * L3-HASH: Third-Layer Hash
+ *
+ * The output from L2-HASH is 16 bytes long. This final hash function
+ * hashes the 16-byte string to a fixed length of 4 bytes.
+ *
+ * @param string $k1 string of length 64 bytes.
+ * @param string $k2 string of length 4 bytes.
+ * @param string $m string of length 16 bytes.
+ * @return string string of length 4 bytes.
+ */
+ private static function L3Hash($k1, $k2, $m)
+ {
+ $factory = self::$factory36;
+
+ $y = $factory->newInteger(new BigInteger());
+ for ($i = 0; $i < 8; $i++) {
+ $m_i = $factory->newInteger(new BigInteger(substr($m, 2 * $i, 2), 256));
+ $k_i = $factory->newInteger(new BigInteger(substr($k1, 8 * $i, 8), 256));
+ $y = $y->add($m_i->multiply($k_i));
+ }
+ $y = str_pad(substr($y->toBytes(), -4), 4, "\0", STR_PAD_LEFT);
+ $y = $y ^ $k2;
+
+ return $y;
+ }
+
+ /**
+ * Compute the Hash / HMAC / UMAC.
+ *
+ * @param string $text
+ * @return string
+ */
+ public function hash($text)
+ {
+ $algo = $this->algo;
+ // https://www.rfc-editor.org/rfc/rfc4493.html
+ // https://en.wikipedia.org/wiki/One-key_MAC
+ if ($algo == 'aes_cmac') {
+ $constZero = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+ if ($this->recomputeAESKey) {
+ if (!is_string($this->key)) {
+ throw new InsufficientSetupException('No key has been set');
+ }
+ if (strlen($this->key) != 16) {
+ throw new \LengthException('Key must be 16 bytes long');
+ }
+ // Algorithm Generate_Subkey
+ $constRb = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x87";
+ $this->c = new AES('ecb');
+ $this->c->setKey($this->key);
+ $this->c->disablePadding();
+ $l = $this->c->encrypt($constZero);
+ $msb = ($l & "\x80") == "\x80";
+ $l = new BigInteger($l, 256);
+ $l->setPrecision(128);
+ $l = $l->bitwise_leftShift(1)->toBytes();
+ // make it constant time
+ $k1 = $msb ? $l ^ $constRb : $l | $constZero;
+
+ $msb = ($k1 & "\x80") == "\x80";
+ $k2 = new BigInteger($k1, 256);
+ $k2->setPrecision(128);
+ $k2 = $k2->bitwise_leftShift(1)->toBytes();
+ // make it constant time
+ $k2 = $msb ? $k2 ^ $constRb : $k2 | $constZero;
+
+ $this->k1 = $k1;
+ $this->k2 = $k2;
+ }
+
+ $len = strlen($text);
+ $const_Bsize = 16;
+ $M = strlen($text) ? str_split($text, $const_Bsize) : [''];
+
+ // Step 2
+ $n = ceil($len / $const_Bsize);
+ // Step 3
+ if ($n == 0) {
+ $n = 1;
+ $flag = false;
+ } else {
+ $flag = $len % $const_Bsize == 0;
+ }
+ // Step 4
+ $M_last = $flag ?
+ $M[$n - 1] ^ $k1 :
+ self::OMAC_padding($M[$n - 1], $const_Bsize) ^ $k2;
+ // Step 5
+ $x = $constZero;
+ // Step 6
+ $c = &$this->c;
+ for ($i = 0; $i < $n - 1; $i++) {
+ $y = $x ^ $M[$i];
+ $x = $c->encrypt($y);
+ }
+ $y = $M_last ^ $x;
+ return $c->encrypt($y);
+ }
+ if ($algo == 'umac') {
+ if ($this->recomputeAESKey) {
+ if (!is_string($this->nonce)) {
+ throw new InsufficientSetupException('No nonce has been set');
+ }
+ if (!is_string($this->key)) {
+ throw new InsufficientSetupException('No key has been set');
+ }
+ if (strlen($this->key) != 16) {
+ throw new \LengthException('Key must be 16 bytes long');
+ }
+
+ if (!isset(self::$maxwordrange64)) {
+ $one = new BigInteger(1);
+
+ $prime36 = new BigInteger("\x00\x00\x00\x0F\xFF\xFF\xFF\xFB", 256);
+ self::$factory36 = new PrimeField($prime36);
+
+ $prime64 = new BigInteger("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC5", 256);
+ self::$factory64 = new PrimeField($prime64);
+
+ $prime128 = new BigInteger("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x61", 256);
+ self::$factory128 = new PrimeField($prime128);
+
+ self::$offset64 = new BigInteger("\1\0\0\0\0\0\0\0\0", 256);
+ self::$offset64 = self::$factory64->newInteger(self::$offset64->subtract($prime64));
+ self::$offset128 = new BigInteger("\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 256);
+ self::$offset128 = self::$factory128->newInteger(self::$offset128->subtract($prime128));
+
+ self::$marker64 = self::$factory64->newInteger($prime64->subtract($one));
+ self::$marker128 = self::$factory128->newInteger($prime128->subtract($one));
+
+ $maxwordrange64 = $one->bitwise_leftShift(64)->subtract($one->bitwise_leftShift(32));
+ self::$maxwordrange64 = self::$factory64->newInteger($maxwordrange64);
+
+ $maxwordrange128 = $one->bitwise_leftShift(128)->subtract($one->bitwise_leftShift(96));
+ self::$maxwordrange128 = self::$factory128->newInteger($maxwordrange128);
+ }
+
+ $this->c = new AES('ctr');
+ $this->c->disablePadding();
+ $this->c->setKey($this->key);
+
+ $this->pad = $this->pdf();
+
+ $this->recomputeAESKey = false;
+ }
+
+ $hashedmessage = $this->uhash($text, $this->length);
+ return $hashedmessage ^ $this->pad;
+ }
+
+ if (is_array($algo)) {
+ if (empty($this->key) || !is_string($this->key)) {
+ return substr($algo($text, ...array_values($this->parameters)), 0, $this->length);
+ }
+
+ // SHA3 HMACs are discussed at https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=30
+
+ $key = str_pad($this->computedKey, $b, chr(0));
+ $temp = $this->ipad ^ $key;
+ $temp .= $text;
+ $temp = substr($algo($temp, ...array_values($this->parameters)), 0, $this->length);
+ $output = $this->opad ^ $key;
+ $output .= $temp;
+ $output = $algo($output, ...array_values($this->parameters));
+
+ return substr($output, 0, $this->length);
+ }
+
+ $output = !empty($this->key) || is_string($this->key) ?
+ hash_hmac($algo, $text, $this->computedKey, true) :
+ hash($algo, $text, true);
+
+ return strlen($output) > $this->length
+ ? substr($output, 0, $this->length)
+ : $output;
+ }
+
+ /**
+ * Returns the hash length (in bits)
+ *
+ * @return int
+ */
+ public function getLength()
+ {
+ return $this->length << 3;
+ }
+
+ /**
+ * Returns the hash length (in bytes)
+ *
+ * @return int
+ */
+ public function getLengthInBytes()
+ {
+ return $this->length;
+ }
+
+ /**
+ * Returns the block length (in bits)
+ *
+ * @return int
+ */
+ public function getBlockLength()
+ {
+ return $this->blockSize;
+ }
+
+ /**
+ * Returns the block length (in bytes)
+ *
+ * @return int
+ */
+ public function getBlockLengthInBytes()
+ {
+ return $this->blockSize >> 3;
+ }
+
+ /**
+ * Pads SHA3 based on the mode
+ *
+ * @param int $padLength
+ * @param int $padType
+ * @return string
+ */
+ private static function sha3_pad($padLength, $padType)
+ {
+ switch ($padType) {
+ case self::PADDING_KECCAK:
+ $temp = chr(0x01) . str_repeat("\0", $padLength - 1);
+ $temp[$padLength - 1] = $temp[$padLength - 1] | chr(0x80);
+ return $temp;
+ case self::PADDING_SHAKE:
+ $temp = chr(0x1F) . str_repeat("\0", $padLength - 1);
+ $temp[$padLength - 1] = $temp[$padLength - 1] | chr(0x80);
+ return $temp;
+ //case self::PADDING_SHA3:
+ default:
+ // from https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=36
+ return $padLength == 1 ? chr(0x86) : chr(0x06) . str_repeat("\0", $padLength - 2) . chr(0x80);
+ }
+ }
+
+ /**
+ * Pure-PHP 32-bit implementation of SHA3
+ *
+ * Whereas BigInteger.php's 32-bit engine works on PHP 64-bit this 32-bit implementation
+ * of SHA3 will *not* work on PHP 64-bit. This is because this implementation
+ * employees bitwise NOTs and bitwise left shifts. And the round constants only work
+ * on 32-bit PHP. eg. dechex(-2147483648) returns 80000000 on 32-bit PHP and
+ * FFFFFFFF80000000 on 64-bit PHP. Sure, we could do bitwise ANDs but that would slow
+ * things down.
+ *
+ * SHA512 requires BigInteger to simulate 64-bit unsigned integers because SHA2 employees
+ * addition whereas SHA3 just employees bitwise operators. PHP64 only supports signed
+ * 64-bit integers, which complicates addition, whereas that limitation isn't an issue
+ * for SHA3.
+ *
+ * In https://ws680.nist.gov/publication/get_pdf.cfm?pub_id=919061#page=16 KECCAK[C] is
+ * defined as "the KECCAK instance with KECCAK-f[1600] as the underlying permutation and
+ * capacity c". This is relevant because, altho the KECCAK standard defines a mode
+ * (KECCAK-f[800]) designed for 32-bit machines that mode is incompatible with SHA3
+ *
+ * @param string $p
+ * @param int $c
+ * @param int $r
+ * @param int $d
+ * @param int $padType
+ */
+ private static function sha3_32($p, $c, $r, $d, $padType)
+ {
+ $block_size = $r >> 3;
+ $padLength = $block_size - (strlen($p) % $block_size);
+ $num_ints = $block_size >> 2;
+
+ $p .= static::sha3_pad($padLength, $padType);
+
+ $n = strlen($p) / $r; // number of blocks
+
+ $s = [
+ [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
+ [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
+ [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
+ [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
+ [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
+ ];
+
+ $p = str_split($p, $block_size);
+
+ foreach ($p as $pi) {
+ $pi = unpack('V*', $pi);
+ $x = $y = 0;
+ for ($i = 1; $i <= $num_ints; $i += 2) {
+ $s[$x][$y][0] ^= $pi[$i + 1];
+ $s[$x][$y][1] ^= $pi[$i];
+ if (++$y == 5) {
+ $y = 0;
+ $x++;
+ }
+ }
+ static::processSHA3Block32($s);
+ }
+
+ $z = '';
+ $i = $j = 0;
+ while (strlen($z) < $d) {
+ $z .= pack('V2', $s[$i][$j][1], $s[$i][$j++][0]);
+ if ($j == 5) {
+ $j = 0;
+ $i++;
+ if ($i == 5) {
+ $i = 0;
+ static::processSHA3Block32($s);
+ }
+ }
+ }
+
+ return $z;
+ }
+
+ /**
+ * 32-bit block processing method for SHA3
+ *
+ * @param array $s
+ */
+ private static function processSHA3Block32(&$s)
+ {
+ static $rotationOffsets = [
+ [ 0, 1, 62, 28, 27],
+ [36, 44, 6, 55, 20],
+ [ 3, 10, 43, 25, 39],
+ [41, 45, 15, 21, 8],
+ [18, 2, 61, 56, 14]
+ ];
+
+ // the standards give these constants in hexadecimal notation. it's tempting to want to use
+ // that same notation, here, however, we can't, because 0x80000000, on PHP32, is a positive
+ // float - not the negative int that we need to be in PHP32. so we use -2147483648 instead
+ static $roundConstants = [
+ [0, 1],
+ [0, 32898],
+ [-2147483648, 32906],
+ [-2147483648, -2147450880],
+ [0, 32907],
+ [0, -2147483647],
+ [-2147483648, -2147450751],
+ [-2147483648, 32777],
+ [0, 138],
+ [0, 136],
+ [0, -2147450871],
+ [0, -2147483638],
+ [0, -2147450741],
+ [-2147483648, 139],
+ [-2147483648, 32905],
+ [-2147483648, 32771],
+ [-2147483648, 32770],
+ [-2147483648, 128],
+ [0, 32778],
+ [-2147483648, -2147483638],
+ [-2147483648, -2147450751],
+ [-2147483648, 32896],
+ [0, -2147483647],
+ [-2147483648, -2147450872]
+ ];
+
+ for ($round = 0; $round < 24; $round++) {
+ // theta step
+ $parity = $rotated = [];
+ for ($i = 0; $i < 5; $i++) {
+ $parity[] = [
+ $s[0][$i][0] ^ $s[1][$i][0] ^ $s[2][$i][0] ^ $s[3][$i][0] ^ $s[4][$i][0],
+ $s[0][$i][1] ^ $s[1][$i][1] ^ $s[2][$i][1] ^ $s[3][$i][1] ^ $s[4][$i][1]
+ ];
+ $rotated[] = static::rotateLeft32($parity[$i], 1);
+ }
+
+ $temp = [
+ [$parity[4][0] ^ $rotated[1][0], $parity[4][1] ^ $rotated[1][1]],
+ [$parity[0][0] ^ $rotated[2][0], $parity[0][1] ^ $rotated[2][1]],
+ [$parity[1][0] ^ $rotated[3][0], $parity[1][1] ^ $rotated[3][1]],
+ [$parity[2][0] ^ $rotated[4][0], $parity[2][1] ^ $rotated[4][1]],
+ [$parity[3][0] ^ $rotated[0][0], $parity[3][1] ^ $rotated[0][1]]
+ ];
+ for ($i = 0; $i < 5; $i++) {
+ for ($j = 0; $j < 5; $j++) {
+ $s[$i][$j][0] ^= $temp[$j][0];
+ $s[$i][$j][1] ^= $temp[$j][1];
+ }
+ }
+
+ $st = $s;
+
+ // rho and pi steps
+ for ($i = 0; $i < 5; $i++) {
+ for ($j = 0; $j < 5; $j++) {
+ $st[(2 * $i + 3 * $j) % 5][$j] = static::rotateLeft32($s[$j][$i], $rotationOffsets[$j][$i]);
+ }
+ }
+
+ // chi step
+ for ($i = 0; $i < 5; $i++) {
+ $s[$i][0] = [
+ $st[$i][0][0] ^ (~$st[$i][1][0] & $st[$i][2][0]),
+ $st[$i][0][1] ^ (~$st[$i][1][1] & $st[$i][2][1])
+ ];
+ $s[$i][1] = [
+ $st[$i][1][0] ^ (~$st[$i][2][0] & $st[$i][3][0]),
+ $st[$i][1][1] ^ (~$st[$i][2][1] & $st[$i][3][1])
+ ];
+ $s[$i][2] = [
+ $st[$i][2][0] ^ (~$st[$i][3][0] & $st[$i][4][0]),
+ $st[$i][2][1] ^ (~$st[$i][3][1] & $st[$i][4][1])
+ ];
+ $s[$i][3] = [
+ $st[$i][3][0] ^ (~$st[$i][4][0] & $st[$i][0][0]),
+ $st[$i][3][1] ^ (~$st[$i][4][1] & $st[$i][0][1])
+ ];
+ $s[$i][4] = [
+ $st[$i][4][0] ^ (~$st[$i][0][0] & $st[$i][1][0]),
+ $st[$i][4][1] ^ (~$st[$i][0][1] & $st[$i][1][1])
+ ];
+ }
+
+ // iota step
+ $s[0][0][0] ^= $roundConstants[$round][0];
+ $s[0][0][1] ^= $roundConstants[$round][1];
+ }
+ }
+
+ /**
+ * Rotate 32-bit int
+ *
+ * @param array $x
+ * @param int $shift
+ */
+ private static function rotateLeft32($x, $shift)
+ {
+ if ($shift < 32) {
+ list($hi, $lo) = $x;
+ } else {
+ $shift -= 32;
+ list($lo, $hi) = $x;
+ }
+
+ $mask = -1 ^ (-1 << $shift);
+ return [
+ ($hi << $shift) | (($lo >> (32 - $shift)) & $mask),
+ ($lo << $shift) | (($hi >> (32 - $shift)) & $mask)
+ ];
+ }
+
+ /**
+ * Pure-PHP 64-bit implementation of SHA3
+ *
+ * @param string $p
+ * @param int $c
+ * @param int $r
+ * @param int $d
+ * @param int $padType
+ */
+ private static function sha3_64($p, $c, $r, $d, $padType)
+ {
+ $block_size = $r >> 3;
+ $padLength = $block_size - (strlen($p) % $block_size);
+ $num_ints = $block_size >> 2;
+
+ $p .= static::sha3_pad($padLength, $padType);
+
+ $n = strlen($p) / $r; // number of blocks
+
+ $s = [
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0]
+ ];
+
+ $p = str_split($p, $block_size);
+
+ foreach ($p as $pi) {
+ $pi = unpack('P*', $pi);
+ $x = $y = 0;
+ foreach ($pi as $subpi) {
+ $s[$x][$y++] ^= $subpi;
+ if ($y == 5) {
+ $y = 0;
+ $x++;
+ }
+ }
+ static::processSHA3Block64($s);
+ }
+
+ $z = '';
+ $i = $j = 0;
+ while (strlen($z) < $d) {
+ $z .= pack('P', $s[$i][$j++]);
+ if ($j == 5) {
+ $j = 0;
+ $i++;
+ if ($i == 5) {
+ $i = 0;
+ static::processSHA3Block64($s);
+ }
+ }
+ }
+
+ return $z;
+ }
+
+ /**
+ * 64-bit block processing method for SHA3
+ *
+ * @param array $s
+ */
+ private static function processSHA3Block64(&$s)
+ {
+ static $rotationOffsets = [
+ [ 0, 1, 62, 28, 27],
+ [36, 44, 6, 55, 20],
+ [ 3, 10, 43, 25, 39],
+ [41, 45, 15, 21, 8],
+ [18, 2, 61, 56, 14]
+ ];
+
+ static $roundConstants = [
+ 1,
+ 32898,
+ -9223372036854742902,
+ -9223372034707259392,
+ 32907,
+ 2147483649,
+ -9223372034707259263,
+ -9223372036854743031,
+ 138,
+ 136,
+ 2147516425,
+ 2147483658,
+ 2147516555,
+ -9223372036854775669,
+ -9223372036854742903,
+ -9223372036854743037,
+ -9223372036854743038,
+ -9223372036854775680,
+ 32778,
+ -9223372034707292150,
+ -9223372034707259263,
+ -9223372036854742912,
+ 2147483649,
+ -9223372034707259384
+ ];
+
+ for ($round = 0; $round < 24; $round++) {
+ // theta step
+ $parity = [];
+ for ($i = 0; $i < 5; $i++) {
+ $parity[] = $s[0][$i] ^ $s[1][$i] ^ $s[2][$i] ^ $s[3][$i] ^ $s[4][$i];
+ }
+ $temp = [
+ $parity[4] ^ static::rotateLeft64($parity[1], 1),
+ $parity[0] ^ static::rotateLeft64($parity[2], 1),
+ $parity[1] ^ static::rotateLeft64($parity[3], 1),
+ $parity[2] ^ static::rotateLeft64($parity[4], 1),
+ $parity[3] ^ static::rotateLeft64($parity[0], 1)
+ ];
+ for ($i = 0; $i < 5; $i++) {
+ for ($j = 0; $j < 5; $j++) {
+ $s[$i][$j] ^= $temp[$j];
+ }
+ }
+
+ $st = $s;
+
+ // rho and pi steps
+ for ($i = 0; $i < 5; $i++) {
+ for ($j = 0; $j < 5; $j++) {
+ $st[(2 * $i + 3 * $j) % 5][$j] = static::rotateLeft64($s[$j][$i], $rotationOffsets[$j][$i]);
+ }
+ }
+
+ // chi step
+ for ($i = 0; $i < 5; $i++) {
+ $s[$i] = [
+ $st[$i][0] ^ (~$st[$i][1] & $st[$i][2]),
+ $st[$i][1] ^ (~$st[$i][2] & $st[$i][3]),
+ $st[$i][2] ^ (~$st[$i][3] & $st[$i][4]),
+ $st[$i][3] ^ (~$st[$i][4] & $st[$i][0]),
+ $st[$i][4] ^ (~$st[$i][0] & $st[$i][1])
+ ];
+ }
+
+ // iota step
+ $s[0][0] ^= $roundConstants[$round];
+ }
+ }
+
+ /**
+ * Left rotate 64-bit int
+ *
+ * @param int $x
+ * @param int $shift
+ */
+ private static function rotateLeft64($x, $shift)
+ {
+ $mask = -1 ^ (-1 << $shift);
+ return ($x << $shift) | (($x >> (64 - $shift)) & $mask);
+ }
+
+ /**
+ * Right rotate 64-bit int
+ *
+ * @param int $x
+ * @param int $shift
+ */
+ private static function rotateRight64($x, $shift)
+ {
+ $mask = -1 ^ (-1 << (64 - $shift));
+ return (($x >> $shift) & $mask) | ($x << (64 - $shift));
+ }
+
+ /**
+ * Pure-PHP implementation of SHA512
+ *
+ * @param string $m
+ * @param array $hash
+ * @return string
+ */
+ private static function sha512($m, $hash)
+ {
+ static $k;
+
+ if (!isset($k)) {
+ // Initialize table of round constants
+ // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409)
+ $k = [
+ '428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc',
+ '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118',
+ 'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2',
+ '72be5d74f27b896f', '80deb1fe3b1696b1', '9bdc06a725c71235', 'c19bf174cf692694',
+ 'e49b69c19ef14ad2', 'efbe4786384f25e3', '0fc19dc68b8cd5b5', '240ca1cc77ac9c65',
+ '2de92c6f592b0275', '4a7484aa6ea6e483', '5cb0a9dcbd41fbd4', '76f988da831153b5',
+ '983e5152ee66dfab', 'a831c66d2db43210', 'b00327c898fb213f', 'bf597fc7beef0ee4',
+ 'c6e00bf33da88fc2', 'd5a79147930aa725', '06ca6351e003826f', '142929670a0e6e70',
+ '27b70a8546d22ffc', '2e1b21385c26c926', '4d2c6dfc5ac42aed', '53380d139d95b3df',
+ '650a73548baf63de', '766a0abb3c77b2a8', '81c2c92e47edaee6', '92722c851482353b',
+ 'a2bfe8a14cf10364', 'a81a664bbc423001', 'c24b8b70d0f89791', 'c76c51a30654be30',
+ 'd192e819d6ef5218', 'd69906245565a910', 'f40e35855771202a', '106aa07032bbd1b8',
+ '19a4c116b8d2d0c8', '1e376c085141ab53', '2748774cdf8eeb99', '34b0bcb5e19b48a8',
+ '391c0cb3c5c95a63', '4ed8aa4ae3418acb', '5b9cca4f7763e373', '682e6ff3d6b2b8a3',
+ '748f82ee5defb2fc', '78a5636f43172f60', '84c87814a1f0ab72', '8cc702081a6439ec',
+ '90befffa23631e28', 'a4506cebde82bde9', 'bef9a3f7b2c67915', 'c67178f2e372532b',
+ 'ca273eceea26619c', 'd186b8c721c0c207', 'eada7dd6cde0eb1e', 'f57d4f7fee6ed178',
+ '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b',
+ '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c',
+ '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817'
+ ];
+
+ for ($i = 0; $i < 80; $i++) {
+ $k[$i] = new BigInteger($k[$i], 16);
+ }
+ }
+
+ // Pre-processing
+ $length = strlen($m);
+ // to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128
+ $m .= str_repeat(chr(0), 128 - (($length + 16) & 0x7F));
+ $m[$length] = chr(0x80);
+ // we don't support hashing strings 512MB long
+ $m .= pack('N4', 0, 0, 0, $length << 3);
+
+ // Process the message in successive 1024-bit chunks
+ $chunks = str_split($m, 128);
+ foreach ($chunks as $chunk) {
+ $w = [];
+ for ($i = 0; $i < 16; $i++) {
+ $temp = new BigInteger(Strings::shift($chunk, 8), 256);
+ $temp->setPrecision(64);
+ $w[] = $temp;
+ }
+
+ // Extend the sixteen 32-bit words into eighty 32-bit words
+ for ($i = 16; $i < 80; $i++) {
+ $temp = [
+ $w[$i - 15]->bitwise_rightRotate(1),
+ $w[$i - 15]->bitwise_rightRotate(8),
+ $w[$i - 15]->bitwise_rightShift(7)
+ ];
+ $s0 = $temp[0]->bitwise_xor($temp[1]);
+ $s0 = $s0->bitwise_xor($temp[2]);
+ $temp = [
+ $w[$i - 2]->bitwise_rightRotate(19),
+ $w[$i - 2]->bitwise_rightRotate(61),
+ $w[$i - 2]->bitwise_rightShift(6)
+ ];
+ $s1 = $temp[0]->bitwise_xor($temp[1]);
+ $s1 = $s1->bitwise_xor($temp[2]);
+ $w[$i] = clone $w[$i - 16];
+ $w[$i] = $w[$i]->add($s0);
+ $w[$i] = $w[$i]->add($w[$i - 7]);
+ $w[$i] = $w[$i]->add($s1);
+ }
+
+ // Initialize hash value for this chunk
+ $a = clone $hash[0];
+ $b = clone $hash[1];
+ $c = clone $hash[2];
+ $d = clone $hash[3];
+ $e = clone $hash[4];
+ $f = clone $hash[5];
+ $g = clone $hash[6];
+ $h = clone $hash[7];
+
+ // Main loop
+ for ($i = 0; $i < 80; $i++) {
+ $temp = [
+ $a->bitwise_rightRotate(28),
+ $a->bitwise_rightRotate(34),
+ $a->bitwise_rightRotate(39)
+ ];
+ $s0 = $temp[0]->bitwise_xor($temp[1]);
+ $s0 = $s0->bitwise_xor($temp[2]);
+ $temp = [
+ $a->bitwise_and($b),
+ $a->bitwise_and($c),
+ $b->bitwise_and($c)
+ ];
+ $maj = $temp[0]->bitwise_xor($temp[1]);
+ $maj = $maj->bitwise_xor($temp[2]);
+ $t2 = $s0->add($maj);
+
+ $temp = [
+ $e->bitwise_rightRotate(14),
+ $e->bitwise_rightRotate(18),
+ $e->bitwise_rightRotate(41)
+ ];
+ $s1 = $temp[0]->bitwise_xor($temp[1]);
+ $s1 = $s1->bitwise_xor($temp[2]);
+ $temp = [
+ $e->bitwise_and($f),
+ $g->bitwise_and($e->bitwise_not())
+ ];
+ $ch = $temp[0]->bitwise_xor($temp[1]);
+ $t1 = $h->add($s1);
+ $t1 = $t1->add($ch);
+ $t1 = $t1->add($k[$i]);
+ $t1 = $t1->add($w[$i]);
+
+ $h = clone $g;
+ $g = clone $f;
+ $f = clone $e;
+ $e = $d->add($t1);
+ $d = clone $c;
+ $c = clone $b;
+ $b = clone $a;
+ $a = $t1->add($t2);
+ }
+
+ // Add this chunk's hash to result so far
+ $hash = [
+ $hash[0]->add($a),
+ $hash[1]->add($b),
+ $hash[2]->add($c),
+ $hash[3]->add($d),
+ $hash[4]->add($e),
+ $hash[5]->add($f),
+ $hash[6]->add($g),
+ $hash[7]->add($h)
+ ];
+ }
+
+ // Produce the final hash value (big-endian)
+ // (\phpseclib3\Crypt\Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here)
+ $temp = $hash[0]->toBytes() . $hash[1]->toBytes() . $hash[2]->toBytes() . $hash[3]->toBytes() .
+ $hash[4]->toBytes() . $hash[5]->toBytes() . $hash[6]->toBytes() . $hash[7]->toBytes();
+
+ return $temp;
+ }
+
+ /**
+ * Pure-PHP implementation of SHA512
+ *
+ * @param string $m
+ * @param array $hash
+ * @return string
+ */
+ private static function sha512_64($m, $hash)
+ {
+ static $k;
+
+ if (!isset($k)) {
+ // Initialize table of round constants
+ // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409)
+ $k = [
+ '428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc',
+ '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118',
+ 'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2',
+ '72be5d74f27b896f', '80deb1fe3b1696b1', '9bdc06a725c71235', 'c19bf174cf692694',
+ 'e49b69c19ef14ad2', 'efbe4786384f25e3', '0fc19dc68b8cd5b5', '240ca1cc77ac9c65',
+ '2de92c6f592b0275', '4a7484aa6ea6e483', '5cb0a9dcbd41fbd4', '76f988da831153b5',
+ '983e5152ee66dfab', 'a831c66d2db43210', 'b00327c898fb213f', 'bf597fc7beef0ee4',
+ 'c6e00bf33da88fc2', 'd5a79147930aa725', '06ca6351e003826f', '142929670a0e6e70',
+ '27b70a8546d22ffc', '2e1b21385c26c926', '4d2c6dfc5ac42aed', '53380d139d95b3df',
+ '650a73548baf63de', '766a0abb3c77b2a8', '81c2c92e47edaee6', '92722c851482353b',
+ 'a2bfe8a14cf10364', 'a81a664bbc423001', 'c24b8b70d0f89791', 'c76c51a30654be30',
+ 'd192e819d6ef5218', 'd69906245565a910', 'f40e35855771202a', '106aa07032bbd1b8',
+ '19a4c116b8d2d0c8', '1e376c085141ab53', '2748774cdf8eeb99', '34b0bcb5e19b48a8',
+ '391c0cb3c5c95a63', '4ed8aa4ae3418acb', '5b9cca4f7763e373', '682e6ff3d6b2b8a3',
+ '748f82ee5defb2fc', '78a5636f43172f60', '84c87814a1f0ab72', '8cc702081a6439ec',
+ '90befffa23631e28', 'a4506cebde82bde9', 'bef9a3f7b2c67915', 'c67178f2e372532b',
+ 'ca273eceea26619c', 'd186b8c721c0c207', 'eada7dd6cde0eb1e', 'f57d4f7fee6ed178',
+ '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b',
+ '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c',
+ '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817'
+ ];
+
+ for ($i = 0; $i < 80; $i++) {
+ list(, $k[$i]) = unpack('J', pack('H*', $k[$i]));
+ }
+ }
+
+ // Pre-processing
+ $length = strlen($m);
+ // to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128
+ $m .= str_repeat(chr(0), 128 - (($length + 16) & 0x7F));
+ $m[$length] = chr(0x80);
+ // we don't support hashing strings 512MB long
+ $m .= pack('N4', 0, 0, 0, $length << 3);
+
+ // Process the message in successive 1024-bit chunks
+ $chunks = str_split($m, 128);
+ foreach ($chunks as $chunk) {
+ $w = [];
+ for ($i = 0; $i < 16; $i++) {
+ list(, $w[]) = unpack('J', Strings::shift($chunk, 8));
+ }
+
+ // Extend the sixteen 32-bit words into eighty 32-bit words
+ for ($i = 16; $i < 80; $i++) {
+ $temp = [
+ self::rotateRight64($w[$i - 15], 1),
+ self::rotateRight64($w[$i - 15], 8),
+ ($w[$i - 15] >> 7) & 0x01FFFFFFFFFFFFFF,
+ ];
+ $s0 = $temp[0] ^ $temp[1] ^ $temp[2];
+ $temp = [
+ self::rotateRight64($w[$i - 2], 19),
+ self::rotateRight64($w[$i - 2], 61),
+ ($w[$i - 2] >> 6) & 0x03FFFFFFFFFFFFFF,
+ ];
+ $s1 = $temp[0] ^ $temp[1] ^ $temp[2];
+
+ $w[$i] = $w[$i - 16];
+ $w[$i] = self::add64($w[$i], $s0);
+ $w[$i] = self::add64($w[$i], $w[$i - 7]);
+ $w[$i] = self::add64($w[$i], $s1);
+ }
+
+ // Initialize hash value for this chunk
+ list($a, $b, $c, $d, $e, $f, $g, $h) = $hash;
+
+ // Main loop
+ for ($i = 0; $i < 80; $i++) {
+ $temp = [
+ self::rotateRight64($a, 28),
+ self::rotateRight64($a, 34),
+ self::rotateRight64($a, 39),
+ ];
+ $s0 = $temp[0] ^ $temp[1] ^ $temp[2];
+ $temp = [$a & $b, $a & $c, $b & $c];
+ $maj = $temp[0] ^ $temp[1] ^ $temp[2];
+ $t2 = self::add64($s0, $maj);
+
+ $temp = [
+ self::rotateRight64($e, 14),
+ self::rotateRight64($e, 18),
+ self::rotateRight64($e, 41),
+ ];
+ $s1 = $temp[0] ^ $temp[1] ^ $temp[2];
+ $ch = ($e & $f) ^ ($g & ~$e);
+ $t1 = self::add64($h, $s1);
+ $t1 = self::add64($t1, $ch);
+ $t1 = self::add64($t1, $k[$i]);
+ $t1 = self::add64($t1, $w[$i]);
+
+ $h = $g;
+ $g = $f;
+ $f = $e;
+ $e = self::add64($d, $t1);
+ $d = $c;
+ $c = $b;
+ $b = $a;
+ $a = self::add64($t1, $t2);
+ }
+
+ // Add this chunk's hash to result so far
+ $hash = [
+ self::add64($hash[0], $a),
+ self::add64($hash[1], $b),
+ self::add64($hash[2], $c),
+ self::add64($hash[3], $d),
+ self::add64($hash[4], $e),
+ self::add64($hash[5], $f),
+ self::add64($hash[6], $g),
+ self::add64($hash[7], $h),
+ ];
+ }
+
+ // Produce the final hash value (big-endian)
+ // (\phpseclib3\Crypt\Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here)
+ return pack('J*', ...$hash);
+ }
+
+ /**
+ * OMAC Padding
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc4493.html#section-2.4
+ */
+ private static function OMAC_padding($m, $length)
+ {
+ $count = $length - strlen($m) - 1;
+ return "$m\x80" . str_repeat("\0", $count);
+ }
+
+ /**
+ * __toString() magic method
+ */
+ public function __toString()
+ {
+ return $this->getHash();
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/PublicKeyLoader.php b/Sources/Phpseclib/Crypt/PublicKeyLoader.php
new file mode 100644
index 0000000000..36264080c2
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/PublicKeyLoader.php
@@ -0,0 +1,112 @@
+
+ * @copyright 2009 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\AsymmetricKey;
+use phpseclib3\Crypt\Common\PrivateKey;
+use phpseclib3\Crypt\Common\PublicKey;
+use phpseclib3\Exception\NoKeyLoadedException;
+use phpseclib3\File\X509;
+
+/**
+ * PublicKeyLoader
+ *
+ * @author Jim Wigginton
+ */
+abstract class PublicKeyLoader
+{
+ /**
+ * Loads a public or private key
+ *
+ * @return AsymmetricKey
+ * @param string|array $key
+ * @param string $password optional
+ * @throws NoKeyLoadedException if key is not valid
+ */
+ public static function load($key, $password = false)
+ {
+ try {
+ return EC::load($key, $password);
+ } catch (NoKeyLoadedException $e) {
+ }
+
+ try {
+ return RSA::load($key, $password);
+ } catch (NoKeyLoadedException $e) {
+ }
+
+ try {
+ return DSA::load($key, $password);
+ } catch (NoKeyLoadedException $e) {
+ }
+
+ try {
+ $x509 = new X509();
+ $x509->loadX509($key);
+ $key = $x509->getPublicKey();
+ if ($key) {
+ return $key;
+ }
+ } catch (\Exception $e) {
+ }
+
+ throw new NoKeyLoadedException('Unable to read key');
+ }
+
+ /**
+ * Loads a private key
+ *
+ * @return PrivateKey
+ * @param string|array $key
+ * @param string $password optional
+ */
+ public static function loadPrivateKey($key, $password = false)
+ {
+ $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;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RC2.php b/Sources/Phpseclib/Crypt/RC2.php
new file mode 100644
index 0000000000..175c52e7bf
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RC2.php
@@ -0,0 +1,640 @@
+
+ * setKey('abcdefgh');
+ *
+ * $plaintext = str_repeat('a', 1024);
+ *
+ * echo $rc2->decrypt($rc2->encrypt($plaintext));
+ * ?>
+ *
+ *
+ * @author Patrick Monnerat
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Crypt\Common\BlockCipher;
+use phpseclib3\Exception\BadModeException;
+
+/**
+ * Pure-PHP implementation of RC2.
+ *
+ */
+class RC2 extends BlockCipher
+{
+ /**
+ * Block Length of the cipher
+ *
+ * @see Common\SymmetricKey::block_size
+ * @var int
+ */
+ protected $block_size = 8;
+
+ /**
+ * The Key
+ *
+ * @see Common\SymmetricKey::key
+ * @see self::setKey()
+ * @var string
+ */
+ protected $key;
+
+ /**
+ * The Original (unpadded) Key
+ *
+ * @see Common\SymmetricKey::key
+ * @see self::setKey()
+ * @see self::encrypt()
+ * @see self::decrypt()
+ * @var string
+ */
+ private $orig_key;
+
+ /**
+ * Key Length (in bytes)
+ *
+ * @see \phpseclib3\Crypt\RC2::setKeyLength()
+ * @var int
+ */
+ protected $key_length = 16; // = 128 bits
+
+ /**
+ * The mcrypt specific name of the cipher
+ *
+ * @see Common\SymmetricKey::cipher_name_mcrypt
+ * @var string
+ */
+ protected $cipher_name_mcrypt = 'rc2';
+
+ /**
+ * Optimizing value while CFB-encrypting
+ *
+ * @see Common\SymmetricKey::cfb_init_len
+ * @var int
+ */
+ protected $cfb_init_len = 500;
+
+ /**
+ * The key length in bits.
+ *
+ * {@internal Should be in range [1..1024].}
+ *
+ * {@internal Changing this value after setting the key has no effect.}
+ *
+ * @see self::setKeyLength()
+ * @see self::setKey()
+ * @var int
+ */
+ private $default_key_length = 1024;
+
+ /**
+ * The key length in bits.
+ *
+ * {@internal Should be in range [1..1024].}
+ *
+ * @see self::isValidEnine()
+ * @see self::setKey()
+ * @var int
+ */
+ private $current_key_length;
+
+ /**
+ * The Key Schedule
+ *
+ * @see self::setupKey()
+ * @var array
+ */
+ private $keys;
+
+ /**
+ * Key expansion randomization table.
+ * Twice the same 256-value sequence to save a modulus in key expansion.
+ *
+ * @see self::setKey()
+ * @var array
+ */
+ private static $pitable = [
+ 0xD9, 0x78, 0xF9, 0xC4, 0x19, 0xDD, 0xB5, 0xED,
+ 0x28, 0xE9, 0xFD, 0x79, 0x4A, 0xA0, 0xD8, 0x9D,
+ 0xC6, 0x7E, 0x37, 0x83, 0x2B, 0x76, 0x53, 0x8E,
+ 0x62, 0x4C, 0x64, 0x88, 0x44, 0x8B, 0xFB, 0xA2,
+ 0x17, 0x9A, 0x59, 0xF5, 0x87, 0xB3, 0x4F, 0x13,
+ 0x61, 0x45, 0x6D, 0x8D, 0x09, 0x81, 0x7D, 0x32,
+ 0xBD, 0x8F, 0x40, 0xEB, 0x86, 0xB7, 0x7B, 0x0B,
+ 0xF0, 0x95, 0x21, 0x22, 0x5C, 0x6B, 0x4E, 0x82,
+ 0x54, 0xD6, 0x65, 0x93, 0xCE, 0x60, 0xB2, 0x1C,
+ 0x73, 0x56, 0xC0, 0x14, 0xA7, 0x8C, 0xF1, 0xDC,
+ 0x12, 0x75, 0xCA, 0x1F, 0x3B, 0xBE, 0xE4, 0xD1,
+ 0x42, 0x3D, 0xD4, 0x30, 0xA3, 0x3C, 0xB6, 0x26,
+ 0x6F, 0xBF, 0x0E, 0xDA, 0x46, 0x69, 0x07, 0x57,
+ 0x27, 0xF2, 0x1D, 0x9B, 0xBC, 0x94, 0x43, 0x03,
+ 0xF8, 0x11, 0xC7, 0xF6, 0x90, 0xEF, 0x3E, 0xE7,
+ 0x06, 0xC3, 0xD5, 0x2F, 0xC8, 0x66, 0x1E, 0xD7,
+ 0x08, 0xE8, 0xEA, 0xDE, 0x80, 0x52, 0xEE, 0xF7,
+ 0x84, 0xAA, 0x72, 0xAC, 0x35, 0x4D, 0x6A, 0x2A,
+ 0x96, 0x1A, 0xD2, 0x71, 0x5A, 0x15, 0x49, 0x74,
+ 0x4B, 0x9F, 0xD0, 0x5E, 0x04, 0x18, 0xA4, 0xEC,
+ 0xC2, 0xE0, 0x41, 0x6E, 0x0F, 0x51, 0xCB, 0xCC,
+ 0x24, 0x91, 0xAF, 0x50, 0xA1, 0xF4, 0x70, 0x39,
+ 0x99, 0x7C, 0x3A, 0x85, 0x23, 0xB8, 0xB4, 0x7A,
+ 0xFC, 0x02, 0x36, 0x5B, 0x25, 0x55, 0x97, 0x31,
+ 0x2D, 0x5D, 0xFA, 0x98, 0xE3, 0x8A, 0x92, 0xAE,
+ 0x05, 0xDF, 0x29, 0x10, 0x67, 0x6C, 0xBA, 0xC9,
+ 0xD3, 0x00, 0xE6, 0xCF, 0xE1, 0x9E, 0xA8, 0x2C,
+ 0x63, 0x16, 0x01, 0x3F, 0x58, 0xE2, 0x89, 0xA9,
+ 0x0D, 0x38, 0x34, 0x1B, 0xAB, 0x33, 0xFF, 0xB0,
+ 0xBB, 0x48, 0x0C, 0x5F, 0xB9, 0xB1, 0xCD, 0x2E,
+ 0xC5, 0xF3, 0xDB, 0x47, 0xE5, 0xA5, 0x9C, 0x77,
+ 0x0A, 0xA6, 0x20, 0x68, 0xFE, 0x7F, 0xC1, 0xAD,
+ 0xD9, 0x78, 0xF9, 0xC4, 0x19, 0xDD, 0xB5, 0xED,
+ 0x28, 0xE9, 0xFD, 0x79, 0x4A, 0xA0, 0xD8, 0x9D,
+ 0xC6, 0x7E, 0x37, 0x83, 0x2B, 0x76, 0x53, 0x8E,
+ 0x62, 0x4C, 0x64, 0x88, 0x44, 0x8B, 0xFB, 0xA2,
+ 0x17, 0x9A, 0x59, 0xF5, 0x87, 0xB3, 0x4F, 0x13,
+ 0x61, 0x45, 0x6D, 0x8D, 0x09, 0x81, 0x7D, 0x32,
+ 0xBD, 0x8F, 0x40, 0xEB, 0x86, 0xB7, 0x7B, 0x0B,
+ 0xF0, 0x95, 0x21, 0x22, 0x5C, 0x6B, 0x4E, 0x82,
+ 0x54, 0xD6, 0x65, 0x93, 0xCE, 0x60, 0xB2, 0x1C,
+ 0x73, 0x56, 0xC0, 0x14, 0xA7, 0x8C, 0xF1, 0xDC,
+ 0x12, 0x75, 0xCA, 0x1F, 0x3B, 0xBE, 0xE4, 0xD1,
+ 0x42, 0x3D, 0xD4, 0x30, 0xA3, 0x3C, 0xB6, 0x26,
+ 0x6F, 0xBF, 0x0E, 0xDA, 0x46, 0x69, 0x07, 0x57,
+ 0x27, 0xF2, 0x1D, 0x9B, 0xBC, 0x94, 0x43, 0x03,
+ 0xF8, 0x11, 0xC7, 0xF6, 0x90, 0xEF, 0x3E, 0xE7,
+ 0x06, 0xC3, 0xD5, 0x2F, 0xC8, 0x66, 0x1E, 0xD7,
+ 0x08, 0xE8, 0xEA, 0xDE, 0x80, 0x52, 0xEE, 0xF7,
+ 0x84, 0xAA, 0x72, 0xAC, 0x35, 0x4D, 0x6A, 0x2A,
+ 0x96, 0x1A, 0xD2, 0x71, 0x5A, 0x15, 0x49, 0x74,
+ 0x4B, 0x9F, 0xD0, 0x5E, 0x04, 0x18, 0xA4, 0xEC,
+ 0xC2, 0xE0, 0x41, 0x6E, 0x0F, 0x51, 0xCB, 0xCC,
+ 0x24, 0x91, 0xAF, 0x50, 0xA1, 0xF4, 0x70, 0x39,
+ 0x99, 0x7C, 0x3A, 0x85, 0x23, 0xB8, 0xB4, 0x7A,
+ 0xFC, 0x02, 0x36, 0x5B, 0x25, 0x55, 0x97, 0x31,
+ 0x2D, 0x5D, 0xFA, 0x98, 0xE3, 0x8A, 0x92, 0xAE,
+ 0x05, 0xDF, 0x29, 0x10, 0x67, 0x6C, 0xBA, 0xC9,
+ 0xD3, 0x00, 0xE6, 0xCF, 0xE1, 0x9E, 0xA8, 0x2C,
+ 0x63, 0x16, 0x01, 0x3F, 0x58, 0xE2, 0x89, 0xA9,
+ 0x0D, 0x38, 0x34, 0x1B, 0xAB, 0x33, 0xFF, 0xB0,
+ 0xBB, 0x48, 0x0C, 0x5F, 0xB9, 0xB1, 0xCD, 0x2E,
+ 0xC5, 0xF3, 0xDB, 0x47, 0xE5, 0xA5, 0x9C, 0x77,
+ 0x0A, 0xA6, 0x20, 0x68, 0xFE, 0x7F, 0xC1, 0xAD
+ ];
+
+ /**
+ * Inverse key expansion randomization table.
+ *
+ * @see self::setKey()
+ * @var array
+ */
+ private static $invpitable = [
+ 0xD1, 0xDA, 0xB9, 0x6F, 0x9C, 0xC8, 0x78, 0x66,
+ 0x80, 0x2C, 0xF8, 0x37, 0xEA, 0xE0, 0x62, 0xA4,
+ 0xCB, 0x71, 0x50, 0x27, 0x4B, 0x95, 0xD9, 0x20,
+ 0x9D, 0x04, 0x91, 0xE3, 0x47, 0x6A, 0x7E, 0x53,
+ 0xFA, 0x3A, 0x3B, 0xB4, 0xA8, 0xBC, 0x5F, 0x68,
+ 0x08, 0xCA, 0x8F, 0x14, 0xD7, 0xC0, 0xEF, 0x7B,
+ 0x5B, 0xBF, 0x2F, 0xE5, 0xE2, 0x8C, 0xBA, 0x12,
+ 0xE1, 0xAF, 0xB2, 0x54, 0x5D, 0x59, 0x76, 0xDB,
+ 0x32, 0xA2, 0x58, 0x6E, 0x1C, 0x29, 0x64, 0xF3,
+ 0xE9, 0x96, 0x0C, 0x98, 0x19, 0x8D, 0x3E, 0x26,
+ 0xAB, 0xA5, 0x85, 0x16, 0x40, 0xBD, 0x49, 0x67,
+ 0xDC, 0x22, 0x94, 0xBB, 0x3C, 0xC1, 0x9B, 0xEB,
+ 0x45, 0x28, 0x18, 0xD8, 0x1A, 0x42, 0x7D, 0xCC,
+ 0xFB, 0x65, 0x8E, 0x3D, 0xCD, 0x2A, 0xA3, 0x60,
+ 0xAE, 0x93, 0x8A, 0x48, 0x97, 0x51, 0x15, 0xF7,
+ 0x01, 0x0B, 0xB7, 0x36, 0xB1, 0x2E, 0x11, 0xFD,
+ 0x84, 0x2D, 0x3F, 0x13, 0x88, 0xB3, 0x34, 0x24,
+ 0x1B, 0xDE, 0xC5, 0x1D, 0x4D, 0x2B, 0x17, 0x31,
+ 0x74, 0xA9, 0xC6, 0x43, 0x6D, 0x39, 0x90, 0xBE,
+ 0xC3, 0xB0, 0x21, 0x6B, 0xF6, 0x0F, 0xD5, 0x99,
+ 0x0D, 0xAC, 0x1F, 0x5C, 0x9E, 0xF5, 0xF9, 0x4C,
+ 0xD6, 0xDF, 0x89, 0xE4, 0x8B, 0xFF, 0xC7, 0xAA,
+ 0xE7, 0xED, 0x46, 0x25, 0xB6, 0x06, 0x5E, 0x35,
+ 0xB5, 0xEC, 0xCE, 0xE8, 0x6C, 0x30, 0x55, 0x61,
+ 0x4A, 0xFE, 0xA0, 0x79, 0x03, 0xF0, 0x10, 0x72,
+ 0x7C, 0xCF, 0x52, 0xA6, 0xA7, 0xEE, 0x44, 0xD3,
+ 0x9A, 0x57, 0x92, 0xD0, 0x5A, 0x7A, 0x41, 0x7F,
+ 0x0E, 0x00, 0x63, 0xF2, 0x4F, 0x05, 0x83, 0xC9,
+ 0xA1, 0xD4, 0xDD, 0xC4, 0x56, 0xF4, 0xD2, 0x77,
+ 0x81, 0x09, 0x82, 0x33, 0x9F, 0x07, 0x86, 0x75,
+ 0x38, 0x4E, 0x69, 0xF1, 0xAD, 0x23, 0x73, 0x87,
+ 0x70, 0x02, 0xC2, 0x1E, 0xB8, 0x0A, 0xFC, 0xE6
+ ];
+
+ /**
+ * 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 BadModeException('Block ciphers cannot be ran in stream mode');
+ }
+ }
+
+ /**
+ * Test for engine validity
+ *
+ * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
+ *
+ * @see Common\SymmetricKey::__construct()
+ * @param int $engine
+ * @return bool
+ */
+ protected function isValidEngineHelper($engine)
+ {
+ switch ($engine) {
+ case self::ENGINE_OPENSSL:
+ if ($this->current_key_length != 128 || strlen($this->orig_key) < 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 (defined('OPENSSL_VERSION_TEXT') && version_compare(preg_replace('#OpenSSL (\d+\.\d+\.\d+) .*#', '$1', OPENSSL_VERSION_TEXT), '3.0.1', '>=')) {
+ return false;
+ }
+ $this->cipher_name_openssl_ecb = 'rc2-ecb';
+ $this->cipher_name_openssl = 'rc2-' . $this->openssl_translate_mode();
+ }
+
+ return parent::isValidEngineHelper($engine);
+ }
+
+ /**
+ * Sets the key length.
+ *
+ * Valid key lengths are 8 to 1024.
+ * Calling this function after setting the key has no effect until the next
+ * \phpseclib3\Crypt\RC2::setKey() call.
+ *
+ * @param int $length in bits
+ * @throws \LengthException if the key length isn't supported
+ */
+ public function setKeyLength($length)
+ {
+ if ($length < 8 || $length > 1024) {
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys between 1 and 1024 bits, inclusive, are supported');
+ }
+
+ $this->default_key_length = $this->current_key_length = $length;
+ $this->explicit_key_length = $length >> 3;
+ }
+
+ /**
+ * Returns the current key length
+ *
+ * @return int
+ */
+ public function getKeyLength()
+ {
+ return $this->current_key_length;
+ }
+
+ /**
+ * Sets the key.
+ *
+ * Keys can be of any length. RC2, itself, uses 8 to 1024 bit keys (eg.
+ * strlen($key) <= 128), however, we only use the first 128 bytes if $key
+ * has more then 128 bytes in it, and set $key to a single null byte if
+ * it is empty.
+ *
+ * @see Common\SymmetricKey::setKey()
+ * @param string $key
+ * @param int|boolean $t1 optional Effective key length in bits.
+ * @throws \LengthException if the key length isn't supported
+ */
+ public function setKey($key, $t1 = false)
+ {
+ $this->orig_key = $key;
+
+ if ($t1 === false) {
+ $t1 = $this->default_key_length;
+ }
+
+ if ($t1 < 1 || $t1 > 1024) {
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys between 1 and 1024 bits, inclusive, are supported');
+ }
+
+ $this->current_key_length = $t1;
+ if (strlen($key) < 1 || strlen($key) > 128) {
+ throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes between 8 and 1024 bits, inclusive, are supported');
+ }
+
+ $t = strlen($key);
+
+ // The mcrypt RC2 implementation only supports effective key length
+ // of 1024 bits. It is however possible to handle effective key
+ // lengths in range 1..1024 by expanding the key and applying
+ // inverse pitable mapping to the first byte before submitting it
+ // to mcrypt.
+
+ // Key expansion.
+ $l = array_values(unpack('C*', $key));
+ $t8 = ($t1 + 7) >> 3;
+ $tm = 0xFF >> (8 * $t8 - $t1);
+
+ // Expand key.
+ $pitable = self::$pitable;
+ for ($i = $t; $i < 128; $i++) {
+ $l[$i] = $pitable[$l[$i - 1] + $l[$i - $t]];
+ }
+ $i = 128 - $t8;
+ $l[$i] = $pitable[$l[$i] & $tm];
+ while ($i--) {
+ $l[$i] = $pitable[$l[$i + 1] ^ $l[$i + $t8]];
+ }
+
+ // Prepare the key for mcrypt.
+ $l[0] = self::$invpitable[$l[0]];
+ array_unshift($l, 'C*');
+
+ $this->key = pack(...$l);
+ $this->key_length = strlen($this->key);
+ $this->changed = $this->nonIVChanged = true;
+ $this->setEngine();
+ }
+
+ /**
+ * Encrypts a message.
+ *
+ * Mostly a wrapper for \phpseclib3\Crypt\Common\SymmetricKey::encrypt, with some additional OpenSSL handling code
+ *
+ * @see self::decrypt()
+ * @param string $plaintext
+ * @return string $ciphertext
+ */
+ public function encrypt($plaintext)
+ {
+ if ($this->engine == self::ENGINE_OPENSSL) {
+ $temp = $this->key;
+ $this->key = $this->orig_key;
+ $result = parent::encrypt($plaintext);
+ $this->key = $temp;
+ return $result;
+ }
+
+ return parent::encrypt($plaintext);
+ }
+
+ /**
+ * Decrypts a message.
+ *
+ * Mostly a wrapper for \phpseclib3\Crypt\Common\SymmetricKey::decrypt, with some additional OpenSSL handling code
+ *
+ * @see self::encrypt()
+ * @param string $ciphertext
+ * @return string $plaintext
+ */
+ public function decrypt($ciphertext)
+ {
+ if ($this->engine == self::ENGINE_OPENSSL) {
+ $temp = $this->key;
+ $this->key = $this->orig_key;
+ $result = parent::decrypt($ciphertext);
+ $this->key = $temp;
+ return $result;
+ }
+
+ return parent::decrypt($ciphertext);
+ }
+
+ /**
+ * Encrypts a block
+ *
+ * @see Common\SymmetricKey::encryptBlock()
+ * @see Common\SymmetricKey::encrypt()
+ * @param string $in
+ * @return string
+ */
+ protected function encryptBlock($in)
+ {
+ list($r0, $r1, $r2, $r3) = array_values(unpack('v*', $in));
+ $keys = $this->keys;
+ $limit = 20;
+ $actions = [$limit => 44, 44 => 64];
+ $j = 0;
+
+ for (;;) {
+ // Mixing round.
+ $r0 = (($r0 + $keys[$j++] + ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF) << 1;
+ $r0 |= $r0 >> 16;
+ $r1 = (($r1 + $keys[$j++] + ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF) << 2;
+ $r1 |= $r1 >> 16;
+ $r2 = (($r2 + $keys[$j++] + ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF) << 3;
+ $r2 |= $r2 >> 16;
+ $r3 = (($r3 + $keys[$j++] + ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF) << 5;
+ $r3 |= $r3 >> 16;
+
+ if ($j === $limit) {
+ if ($limit === 64) {
+ break;
+ }
+
+ // Mashing round.
+ $r0 += $keys[$r3 & 0x3F];
+ $r1 += $keys[$r0 & 0x3F];
+ $r2 += $keys[$r1 & 0x3F];
+ $r3 += $keys[$r2 & 0x3F];
+ $limit = $actions[$limit];
+ }
+ }
+
+ return pack('vvvv', $r0, $r1, $r2, $r3);
+ }
+
+ /**
+ * Decrypts a block
+ *
+ * @see Common\SymmetricKey::decryptBlock()
+ * @see Common\SymmetricKey::decrypt()
+ * @param string $in
+ * @return string
+ */
+ protected function decryptBlock($in)
+ {
+ list($r0, $r1, $r2, $r3) = array_values(unpack('v*', $in));
+ $keys = $this->keys;
+ $limit = 44;
+ $actions = [$limit => 20, 20 => 0];
+ $j = 64;
+
+ for (;;) {
+ // R-mixing round.
+ $r3 = ($r3 | ($r3 << 16)) >> 5;
+ $r3 = ($r3 - $keys[--$j] - ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF;
+ $r2 = ($r2 | ($r2 << 16)) >> 3;
+ $r2 = ($r2 - $keys[--$j] - ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF;
+ $r1 = ($r1 | ($r1 << 16)) >> 2;
+ $r1 = ($r1 - $keys[--$j] - ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF;
+ $r0 = ($r0 | ($r0 << 16)) >> 1;
+ $r0 = ($r0 - $keys[--$j] - ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF;
+
+ if ($j === $limit) {
+ if ($limit === 0) {
+ break;
+ }
+
+ // R-mashing round.
+ $r3 = ($r3 - $keys[$r2 & 0x3F]) & 0xFFFF;
+ $r2 = ($r2 - $keys[$r1 & 0x3F]) & 0xFFFF;
+ $r1 = ($r1 - $keys[$r0 & 0x3F]) & 0xFFFF;
+ $r0 = ($r0 - $keys[$r3 & 0x3F]) & 0xFFFF;
+ $limit = $actions[$limit];
+ }
+ }
+
+ return pack('vvvv', $r0, $r1, $r2, $r3);
+ }
+
+ /**
+ * Creates the key schedule
+ *
+ * @see Common\SymmetricKey::setupKey()
+ */
+ protected function setupKey()
+ {
+ if (!isset($this->key)) {
+ $this->setKey('');
+ }
+
+ // Key has already been expanded in \phpseclib3\Crypt\RC2::setKey():
+ // Only the first value must be altered.
+ $l = unpack('Ca/Cb/v*', $this->key);
+ array_unshift($l, self::$pitable[$l['a']] | ($l['b'] << 8));
+ unset($l['a']);
+ unset($l['b']);
+ $this->keys = $l;
+ }
+
+ /**
+ * Setup the performance-optimized function for de/encrypt()
+ *
+ * @see Common\SymmetricKey::setupInlineCrypt()
+ */
+ protected function setupInlineCrypt()
+ {
+ // Init code for both, encrypt and decrypt.
+ $init_crypt = '$keys = $this->keys;';
+
+ $keys = $this->keys;
+
+ // $in is the current 8 bytes block which has to be en/decrypt
+ $encrypt_block = $decrypt_block = '
+ $in = unpack("v4", $in);
+ $r0 = $in[1];
+ $r1 = $in[2];
+ $r2 = $in[3];
+ $r3 = $in[4];
+ ';
+
+ // Create code for encryption.
+ $limit = 20;
+ $actions = [$limit => 44, 44 => 64];
+ $j = 0;
+
+ for (;;) {
+ // Mixing round.
+ $encrypt_block .= '
+ $r0 = (($r0 + ' . $keys[$j++] . ' +
+ ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF) << 1;
+ $r0 |= $r0 >> 16;
+ $r1 = (($r1 + ' . $keys[$j++] . ' +
+ ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF) << 2;
+ $r1 |= $r1 >> 16;
+ $r2 = (($r2 + ' . $keys[$j++] . ' +
+ ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF) << 3;
+ $r2 |= $r2 >> 16;
+ $r3 = (($r3 + ' . $keys[$j++] . ' +
+ ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF) << 5;
+ $r3 |= $r3 >> 16;';
+
+ if ($j === $limit) {
+ if ($limit === 64) {
+ break;
+ }
+
+ // Mashing round.
+ $encrypt_block .= '
+ $r0 += $keys[$r3 & 0x3F];
+ $r1 += $keys[$r0 & 0x3F];
+ $r2 += $keys[$r1 & 0x3F];
+ $r3 += $keys[$r2 & 0x3F];';
+ $limit = $actions[$limit];
+ }
+ }
+
+ $encrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);';
+
+ // Create code for decryption.
+ $limit = 44;
+ $actions = [$limit => 20, 20 => 0];
+ $j = 64;
+
+ for (;;) {
+ // R-mixing round.
+ $decrypt_block .= '
+ $r3 = ($r3 | ($r3 << 16)) >> 5;
+ $r3 = ($r3 - ' . $keys[--$j] . ' -
+ ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF;
+ $r2 = ($r2 | ($r2 << 16)) >> 3;
+ $r2 = ($r2 - ' . $keys[--$j] . ' -
+ ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF;
+ $r1 = ($r1 | ($r1 << 16)) >> 2;
+ $r1 = ($r1 - ' . $keys[--$j] . ' -
+ ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF;
+ $r0 = ($r0 | ($r0 << 16)) >> 1;
+ $r0 = ($r0 - ' . $keys[--$j] . ' -
+ ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF;';
+
+ if ($j === $limit) {
+ if ($limit === 0) {
+ break;
+ }
+
+ // R-mashing round.
+ $decrypt_block .= '
+ $r3 = ($r3 - $keys[$r2 & 0x3F]) & 0xFFFF;
+ $r2 = ($r2 - $keys[$r1 & 0x3F]) & 0xFFFF;
+ $r1 = ($r1 - $keys[$r0 & 0x3F]) & 0xFFFF;
+ $r0 = ($r0 - $keys[$r3 & 0x3F]) & 0xFFFF;';
+ $limit = $actions[$limit];
+ }
+ }
+
+ $decrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);';
+
+ // Creates the inline-crypt function
+ $this->inline_crypt = $this->createInlineCryptFunction(
+ [
+ 'init_crypt' => $init_crypt,
+ 'encrypt_block' => $encrypt_block,
+ 'decrypt_block' => $decrypt_block
+ ]
+ );
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RC4.php b/Sources/Phpseclib/Crypt/RC4.php
new file mode 100644
index 0000000000..98cf011658
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RC4.php
@@ -0,0 +1,280 @@
+
+ * setKey('abcdefgh');
+ *
+ * $size = 10 * 1024;
+ * $plaintext = '';
+ * for ($i = 0; $i < $size; $i++) {
+ * $plaintext.= 'a';
+ * }
+ *
+ * echo $rc4->decrypt($rc4->encrypt($plaintext));
+ * ?>
+ *
+ *
+ * @author Jim Wigginton
+ * @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\StreamCipher;
+
+/**
+ * Pure-PHP implementation of RC4.
+ *
+ * @author Jim Wigginton
+ */
+class RC4 extends StreamCipher
+{
+ /**
+ * @see \phpseclib3\Crypt\RC4::_crypt()
+ */
+ const ENCRYPT = 0;
+
+ /**
+ * @see \phpseclib3\Crypt\RC4::_crypt()
+ */
+ const DECRYPT = 1;
+
+ /**
+ * Key Length (in bytes)
+ *
+ * @see \phpseclib3\Crypt\RC4::setKeyLength()
+ * @var int
+ */
+ protected $key_length = 128; // = 1024 bits
+
+ /**
+ * The mcrypt specific name of the cipher
+ *
+ * @see Common\SymmetricKey::cipher_name_mcrypt
+ * @var string
+ */
+ protected $cipher_name_mcrypt = 'arcfour';
+
+ /**
+ * The Key
+ *
+ * @see self::setKey()
+ * @var string
+ */
+ protected $key;
+
+ /**
+ * The Key Stream for decryption and encryption
+ *
+ * @see self::setKey()
+ * @var array
+ */
+ private $stream;
+
+ /**
+ * Test for engine validity
+ *
+ * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
+ *
+ * @see Common\SymmetricKey::__construct()
+ * @param int $engine
+ * @return bool
+ */
+ protected function isValidEngineHelper($engine)
+ {
+ if ($engine == self::ENGINE_OPENSSL) {
+ if ($this->continuousBuffer) {
+ 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 (defined('OPENSSL_VERSION_TEXT') && version_compare(preg_replace('#OpenSSL (\d+\.\d+\.\d+) .*#', '$1', OPENSSL_VERSION_TEXT), '3.0.1', '>=')) {
+ return false;
+ }
+ $this->cipher_name_openssl = 'rc4-40';
+ }
+
+ return parent::isValidEngineHelper($engine);
+ }
+
+ /**
+ * Sets the key length
+ *
+ * Keys can be between 1 and 256 bytes long.
+ *
+ * @param int $length
+ * @throws \LengthException if the key length is invalid
+ */
+ public function setKeyLength($length)
+ {
+ if ($length < 8 || $length > 2048) {
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys between 1 and 256 bytes are supported');
+ }
+
+ $this->key_length = $length >> 3;
+
+ parent::setKeyLength($length);
+ }
+
+ /**
+ * Sets the key length
+ *
+ * Keys can be between 1 and 256 bytes long.
+ *
+ * @param string $key
+ */
+ public function setKey($key)
+ {
+ $length = strlen($key);
+ if ($length < 1 || $length > 256) {
+ throw new \LengthException('Key size of ' . $length . ' bytes is not supported by RC4. Keys must be between 1 and 256 bytes long');
+ }
+
+ parent::setKey($key);
+ }
+
+ /**
+ * Encrypts a message.
+ *
+ * @see Common\SymmetricKey::decrypt()
+ * @see self::crypt()
+ * @param string $plaintext
+ * @return string $ciphertext
+ */
+ public function encrypt($plaintext)
+ {
+ if ($this->engine != self::ENGINE_INTERNAL) {
+ return parent::encrypt($plaintext);
+ }
+ return $this->crypt($plaintext, self::ENCRYPT);
+ }
+
+ /**
+ * Decrypts a message.
+ *
+ * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
+ * At least if the continuous buffer is disabled.
+ *
+ * @see Common\SymmetricKey::encrypt()
+ * @see self::crypt()
+ * @param string $ciphertext
+ * @return string $plaintext
+ */
+ public function decrypt($ciphertext)
+ {
+ if ($this->engine != self::ENGINE_INTERNAL) {
+ return parent::decrypt($ciphertext);
+ }
+ return $this->crypt($ciphertext, self::DECRYPT);
+ }
+
+ /**
+ * Encrypts a block
+ *
+ * @param string $in
+ */
+ protected function encryptBlock($in)
+ {
+ // RC4 does not utilize this method
+ }
+
+ /**
+ * Decrypts a block
+ *
+ * @param string $in
+ */
+ protected function decryptBlock($in)
+ {
+ // RC4 does not utilize this method
+ }
+
+ /**
+ * Setup the key (expansion)
+ *
+ * @see Common\SymmetricKey::_setupKey()
+ */
+ protected function setupKey()
+ {
+ $key = $this->key;
+ $keyLength = strlen($key);
+ $keyStream = range(0, 255);
+ $j = 0;
+ for ($i = 0; $i < 256; $i++) {
+ $j = ($j + $keyStream[$i] + ord($key[$i % $keyLength])) & 255;
+ $temp = $keyStream[$i];
+ $keyStream[$i] = $keyStream[$j];
+ $keyStream[$j] = $temp;
+ }
+
+ $this->stream = [];
+ $this->stream[self::DECRYPT] = $this->stream[self::ENCRYPT] = [
+ 0, // index $i
+ 0, // index $j
+ $keyStream
+ ];
+ }
+
+ /**
+ * Encrypts or decrypts a message.
+ *
+ * @see self::encrypt()
+ * @see self::decrypt()
+ * @param string $text
+ * @param int $mode
+ * @return string $text
+ */
+ private function crypt($text, $mode)
+ {
+ if ($this->changed) {
+ $this->setup();
+ }
+
+ $stream = &$this->stream[$mode];
+ if ($this->continuousBuffer) {
+ $i = &$stream[0];
+ $j = &$stream[1];
+ $keyStream = &$stream[2];
+ } else {
+ $i = $stream[0];
+ $j = $stream[1];
+ $keyStream = $stream[2];
+ }
+
+ $len = strlen($text);
+ for ($k = 0; $k < $len; ++$k) {
+ $i = ($i + 1) & 255;
+ $ksi = $keyStream[$i];
+ $j = ($j + $ksi) & 255;
+ $ksj = $keyStream[$j];
+
+ $keyStream[$i] = $ksj;
+ $keyStream[$j] = $ksi;
+ $text[$k] = $text[$k] ^ chr($keyStream[($ksj + $ksi) & 255]);
+ }
+
+ return $text;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA.php b/Sources/Phpseclib/Crypt/RSA.php
new file mode 100644
index 0000000000..0a11957b05
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA.php
@@ -0,0 +1,933 @@
+
+ * getPublicKey();
+ *
+ * $plaintext = 'terrafrost';
+ *
+ * $ciphertext = $public->encrypt($plaintext);
+ *
+ * echo $private->decrypt($ciphertext);
+ * ?>
+ *
+ *
+ * Here's an example of how to create signatures and verify signatures with this library:
+ *
+ * getPublicKey();
+ *
+ * $plaintext = 'terrafrost';
+ *
+ * $signature = $private->sign($plaintext);
+ *
+ * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
+ * ?>
+ *
+ *
+ * One thing to consider when using this: so phpseclib uses PSS mode by default.
+ * Technically, id-RSASSA-PSS has a different key format than rsaEncryption. So
+ * should phpseclib save to the id-RSASSA-PSS format by default or the
+ * rsaEncryption format? For stand-alone keys I figure rsaEncryption is better
+ * because SSH doesn't use PSS and idk how many SSH servers would be able to
+ * decode an id-RSASSA-PSS key. For X.509 certificates the id-RSASSA-PSS
+ * format is used by default (unless you change it up to use PKCS1 instead)
+ *
+ * @author Jim Wigginton
+ * @copyright 2009 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\AsymmetricKey;
+use phpseclib3\Crypt\RSA\Formats\Keys\PSS;
+use phpseclib3\Crypt\RSA\PrivateKey;
+use phpseclib3\Crypt\RSA\PublicKey;
+use phpseclib3\Exception\InconsistentSetupException;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Pure-PHP PKCS#1 compliant implementation of RSA.
+ *
+ * @author Jim Wigginton
+ */
+abstract class RSA extends AsymmetricKey
+{
+ /**
+ * Algorithm Name
+ *
+ * @var string
+ */
+ const ALGORITHM = 'RSA';
+
+ /**
+ * Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding}
+ * (OAEP) for encryption / decryption.
+ *
+ * Uses sha256 by default
+ *
+ * @see self::setHash()
+ * @see self::setMGFHash()
+ * @see self::encrypt()
+ * @see self::decrypt()
+ */
+ const ENCRYPTION_OAEP = 1;
+
+ /**
+ * Use PKCS#1 padding.
+ *
+ * Although self::PADDING_OAEP / self::PADDING_PSS offers more security, including PKCS#1 padding is necessary for purposes of backwards
+ * compatibility with protocols (like SSH-1) written before OAEP's introduction.
+ *
+ * @see self::encrypt()
+ * @see self::decrypt()
+ */
+ const ENCRYPTION_PKCS1 = 2;
+
+ /**
+ * Do not use any padding
+ *
+ * Although this method is not recommended it can none-the-less sometimes be useful if you're trying to decrypt some legacy
+ * stuff, if you're trying to diagnose why an encrypted message isn't decrypting, etc.
+ *
+ * @see self::encrypt()
+ * @see self::decrypt()
+ */
+ const ENCRYPTION_NONE = 4;
+
+ /**
+ * Use the Probabilistic Signature Scheme for signing
+ *
+ * Uses sha256 and 0 as the salt length
+ *
+ * @see self::setSaltLength()
+ * @see self::setMGFHash()
+ * @see self::setHash()
+ * @see self::sign()
+ * @see self::verify()
+ * @see self::setHash()
+ */
+ const SIGNATURE_PSS = 16;
+
+ /**
+ * Use a relaxed version of PKCS#1 padding for signature verification
+ *
+ * @see self::sign()
+ * @see self::verify()
+ * @see self::setHash()
+ */
+ const SIGNATURE_RELAXED_PKCS1 = 32;
+
+ /**
+ * Use PKCS#1 padding for signature verification
+ *
+ * @see self::sign()
+ * @see self::verify()
+ * @see self::setHash()
+ */
+ const SIGNATURE_PKCS1 = 64;
+
+ /**
+ * Encryption padding mode
+ *
+ * @var int
+ */
+ protected $encryptionPadding = self::ENCRYPTION_OAEP;
+
+ /**
+ * Signature padding mode
+ *
+ * @var int
+ */
+ protected $signaturePadding = self::SIGNATURE_PSS;
+
+ /**
+ * Length of hash function output
+ *
+ * @var int
+ */
+ protected $hLen;
+
+ /**
+ * Length of salt
+ *
+ * @var int
+ */
+ protected $sLen;
+
+ /**
+ * Label
+ *
+ * @var string
+ */
+ protected $label = '';
+
+ /**
+ * Hash function for the Mask Generation Function
+ *
+ * @var Hash
+ */
+ protected $mgfHash;
+
+ /**
+ * Length of MGF hash function output
+ *
+ * @var int
+ */
+ protected $mgfHLen;
+
+ /**
+ * Modulus (ie. n)
+ *
+ * @var Math\BigInteger
+ */
+ protected $modulus;
+
+ /**
+ * Modulus length
+ *
+ * @var Math\BigInteger
+ */
+ protected $k;
+
+ /**
+ * Exponent (ie. e or d)
+ *
+ * @var Math\BigInteger
+ */
+ protected $exponent;
+
+ /**
+ * Default public exponent
+ *
+ * @var int
+ * @link http://en.wikipedia.org/wiki/65537_%28number%29
+ */
+ private static $defaultExponent = 65537;
+
+ /**
+ * Enable Blinding?
+ *
+ * @var bool
+ */
+ protected static $enableBlinding = true;
+
+ /**
+ * OpenSSL configuration file name.
+ *
+ * @see self::createKey()
+ * @var ?string
+ */
+ protected static $configFile;
+
+ /**
+ * Smallest Prime
+ *
+ * Per , this number ought not result in primes smaller
+ * than 256 bits. As a consequence if the key you're trying to create is 1024 bits and you've set smallestPrime
+ * to 384 bits then you're going to get a 384 bit prime and a 640 bit prime (384 + 1024 % 384). At least if
+ * engine is set to self::ENGINE_INTERNAL. If Engine is set to self::ENGINE_OPENSSL then smallest Prime is
+ * ignored (ie. multi-prime RSA support is more intended as a way to speed up RSA key generation when there's
+ * a chance neither gmp nor OpenSSL are installed)
+ *
+ * @var int
+ */
+ private static $smallestPrime = 4096;
+
+ /**
+ * Public Exponent
+ *
+ * @var Math\BigInteger
+ */
+ protected $publicExponent;
+
+ /**
+ * Sets the public exponent for key generation
+ *
+ * This will be 65537 unless changed.
+ *
+ * @param int $val
+ */
+ public static function setExponent($val)
+ {
+ self::$defaultExponent = $val;
+ }
+
+ /**
+ * Sets the smallest prime number in bits. Used for key generation
+ *
+ * This will be 4096 unless changed.
+ *
+ * @param int $val
+ */
+ public static function setSmallestPrime($val)
+ {
+ self::$smallestPrime = $val;
+ }
+
+ /**
+ * Sets the OpenSSL config file path
+ *
+ * Set to the empty string to use the default config file
+ *
+ * @param string $val
+ */
+ public static function setOpenSSLConfigPath($val)
+ {
+ self::$configFile = $val;
+ }
+
+ /**
+ * Create a private key
+ *
+ * The public key can be extracted from the private key
+ *
+ * @return PrivateKey
+ * @param int $bits
+ */
+ public static function createKey($bits = 2048)
+ {
+ self::initialize_static_variables();
+
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
+ }
+
+ $regSize = $bits >> 1; // divide by two to see how many bits P and Q would be
+ if ($regSize > self::$smallestPrime) {
+ $num_primes = floor($bits / self::$smallestPrime);
+ $regSize = self::$smallestPrime;
+ } else {
+ $num_primes = 2;
+ }
+
+ if ($num_primes == 2 && $bits >= 384 && self::$defaultExponent == 65537) {
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+
+ // OpenSSL uses 65537 as the exponent and requires RSA keys be 384 bits minimum
+ if (self::$engines['OpenSSL']) {
+ $config = [];
+ if (self::$configFile) {
+ $config['config'] = self::$configFile;
+ }
+ $rsa = openssl_pkey_new(['private_key_bits' => $bits] + $config);
+ openssl_pkey_export($rsa, $privatekeystr, null, $config);
+
+ // clear the buffer of error strings stemming from a minimalistic openssl.cnf
+ // https://github.com/php/php-src/issues/11054 talks about other errors this'll pick up
+ while (openssl_error_string() !== false) {
+ }
+
+ return RSA::load($privatekeystr);
+ }
+ }
+
+ static $e;
+ if (!isset($e)) {
+ $e = new BigInteger(self::$defaultExponent);
+ }
+
+ $n = clone self::$one;
+ $exponents = $coefficients = $primes = [];
+ $lcm = [
+ 'top' => clone self::$one,
+ 'bottom' => false
+ ];
+
+ do {
+ for ($i = 1; $i <= $num_primes; $i++) {
+ if ($i != $num_primes) {
+ $primes[$i] = BigInteger::randomPrime($regSize);
+ } else {
+ $minMax = BigInteger::minMaxBits($bits);
+ $min = $minMax['min'];
+ $max = $minMax['max'];
+ list($min) = $min->divide($n);
+ $min = $min->add(self::$one);
+ list($max) = $max->divide($n);
+ $primes[$i] = BigInteger::randomRangePrime($min, $max);
+ }
+
+ // the first coefficient is calculated differently from the rest
+ // ie. instead of being $primes[1]->modInverse($primes[2]), it's $primes[2]->modInverse($primes[1])
+ if ($i > 2) {
+ $coefficients[$i] = $n->modInverse($primes[$i]);
+ }
+
+ $n = $n->multiply($primes[$i]);
+
+ $temp = $primes[$i]->subtract(self::$one);
+
+ // textbook RSA implementations use Euler's totient function instead of the least common multiple.
+ // see http://en.wikipedia.org/wiki/Euler%27s_totient_function
+ $lcm['top'] = $lcm['top']->multiply($temp);
+ $lcm['bottom'] = $lcm['bottom'] === false ? $temp : $lcm['bottom']->gcd($temp);
+ }
+
+ list($temp) = $lcm['top']->divide($lcm['bottom']);
+ $gcd = $temp->gcd($e);
+ $i0 = 1;
+ } while (!$gcd->equals(self::$one));
+
+ $coefficients[2] = $primes[2]->modInverse($primes[1]);
+
+ $d = $e->modInverse($temp);
+
+ foreach ($primes as $i => $prime) {
+ $temp = $prime->subtract(self::$one);
+ $exponents[$i] = $e->modInverse($temp);
+ }
+
+ // from :
+ // RSAPrivateKey ::= SEQUENCE {
+ // version Version,
+ // modulus INTEGER, -- n
+ // publicExponent INTEGER, -- e
+ // privateExponent INTEGER, -- d
+ // prime1 INTEGER, -- p
+ // prime2 INTEGER, -- q
+ // exponent1 INTEGER, -- d mod (p-1)
+ // exponent2 INTEGER, -- d mod (q-1)
+ // coefficient INTEGER, -- (inverse of q) mod p
+ // otherPrimeInfos OtherPrimeInfos OPTIONAL
+ // }
+ $privatekey = new PrivateKey();
+ $privatekey->modulus = $n;
+ $privatekey->k = $bits >> 3;
+ $privatekey->publicExponent = $e;
+ $privatekey->exponent = $d;
+ $privatekey->primes = $primes;
+ $privatekey->exponents = $exponents;
+ $privatekey->coefficients = $coefficients;
+
+ /*
+ $publickey = new PublicKey;
+ $publickey->modulus = $n;
+ $publickey->k = $bits >> 3;
+ $publickey->exponent = $e;
+ $publickey->publicExponent = $e;
+ $publickey->isPublic = true;
+ */
+
+ return $privatekey;
+ }
+
+ /**
+ * OnLoad Handler
+ *
+ * @return bool
+ */
+ protected static function onLoad(array $components)
+ {
+ $key = $components['isPublicKey'] ?
+ new PublicKey() :
+ new PrivateKey();
+
+ $key->modulus = $components['modulus'];
+ $key->publicExponent = $components['publicExponent'];
+ $key->k = $key->modulus->getLengthInBytes();
+
+ if ($components['isPublicKey'] || !isset($components['privateExponent'])) {
+ $key->exponent = $key->publicExponent;
+ } else {
+ $key->privateExponent = $components['privateExponent'];
+ $key->exponent = $key->privateExponent;
+ $key->primes = $components['primes'];
+ $key->exponents = $components['exponents'];
+ $key->coefficients = $components['coefficients'];
+ }
+
+ if ($components['format'] == PSS::class) {
+ // in the X509 world RSA keys are assumed to use PKCS1 padding by default. only if the key is
+ // explicitly a PSS key is the use of PSS assumed. phpseclib does not work like this. phpseclib
+ // uses PSS padding by default. it assumes the more secure method by default and altho it provides
+ // for the less secure PKCS1 method you have to go out of your way to use it. this is consistent
+ // with the latest trends in crypto. libsodium (NaCl) is actually a little more extreme in that
+ // not only does it defaults to the most secure methods - it doesn't even let you choose less
+ // secure methods
+ //$key = $key->withPadding(self::SIGNATURE_PSS);
+ if (isset($components['hash'])) {
+ $key = $key->withHash($components['hash']);
+ }
+ if (isset($components['MGFHash'])) {
+ $key = $key->withMGFHash($components['MGFHash']);
+ }
+ if (isset($components['saltLength'])) {
+ $key = $key->withSaltLength($components['saltLength']);
+ }
+ }
+
+ return $key;
+ }
+
+ /**
+ * Initialize static variables
+ */
+ protected static function initialize_static_variables()
+ {
+ if (!isset(self::$configFile)) {
+ self::$configFile = dirname(__FILE__) . '/../openssl.cnf';
+ }
+
+ parent::initialize_static_variables();
+ }
+
+ /**
+ * Constructor
+ *
+ * PublicKey and PrivateKey objects can only be created from abstract RSA class
+ */
+ protected function __construct()
+ {
+ parent::__construct();
+
+ $this->hLen = $this->hash->getLengthInBytes();
+ $this->mgfHash = new Hash('sha256');
+ $this->mgfHLen = $this->mgfHash->getLengthInBytes();
+ }
+
+ /**
+ * Integer-to-Octet-String primitive
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-4.1 RFC3447#section-4.1}.
+ *
+ * @param bool|Math\BigInteger $x
+ * @param int $xLen
+ * @return bool|string
+ */
+ protected function i2osp($x, $xLen)
+ {
+ if ($x === false) {
+ return false;
+ }
+ $x = $x->toBytes();
+ if (strlen($x) > $xLen) {
+ throw new \OutOfRangeException('Resultant string length out of range');
+ }
+ return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
+ }
+
+ /**
+ * Octet-String-to-Integer primitive
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-4.2 RFC3447#section-4.2}.
+ *
+ * @param string $x
+ * @return Math\BigInteger
+ */
+ protected function os2ip($x)
+ {
+ return new BigInteger($x, 256);
+ }
+
+ /**
+ * EMSA-PKCS1-V1_5-ENCODE
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-9.2 RFC3447#section-9.2}.
+ *
+ * @param string $m
+ * @param int $emLen
+ * @throws \LengthException if the intended encoded message length is too short
+ * @return string
+ */
+ protected function emsa_pkcs1_v1_5_encode($m, $emLen)
+ {
+ $h = $this->hash->hash($m);
+
+ // see http://tools.ietf.org/html/rfc3447#page-43
+ switch ($this->hash->getHash()) {
+ case 'md2':
+ $t = "\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x02\x05\x00\x04\x10";
+ break;
+ case 'md5':
+ $t = "\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10";
+ break;
+ case 'sha1':
+ $t = "\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14";
+ break;
+ case 'sha256':
+ $t = "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20";
+ break;
+ case 'sha384':
+ $t = "\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30";
+ break;
+ case 'sha512':
+ $t = "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40";
+ break;
+ // from https://www.emc.com/collateral/white-papers/h11300-pkcs-1v2-2-rsa-cryptography-standard-wp.pdf#page=40
+ case 'sha224':
+ $t = "\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c";
+ break;
+ case 'sha512/224':
+ $t = "\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x05\x05\x00\x04\x1c";
+ break;
+ case 'sha512/256':
+ $t = "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x06\x05\x00\x04\x20";
+ }
+ $t .= $h;
+ $tLen = strlen($t);
+
+ if ($emLen < $tLen + 11) {
+ throw new \LengthException('Intended encoded message length too short');
+ }
+
+ $ps = str_repeat(chr(0xFF), $emLen - $tLen - 3);
+
+ $em = "\0\1$ps\0$t";
+
+ return $em;
+ }
+
+ /**
+ * EMSA-PKCS1-V1_5-ENCODE (without NULL)
+ *
+ * Quoting https://tools.ietf.org/html/rfc8017#page-65,
+ *
+ * "The parameters field associated with id-sha1, id-sha224, id-sha256,
+ * id-sha384, id-sha512, id-sha512/224, and id-sha512/256 should
+ * generally be omitted, but if present, it shall have a value of type
+ * NULL"
+ *
+ * @param string $m
+ * @param int $emLen
+ * @return string
+ */
+ protected function emsa_pkcs1_v1_5_encode_without_null($m, $emLen)
+ {
+ $h = $this->hash->hash($m);
+
+ // see http://tools.ietf.org/html/rfc3447#page-43
+ switch ($this->hash->getHash()) {
+ case 'sha1':
+ $t = "\x30\x1f\x30\x07\x06\x05\x2b\x0e\x03\x02\x1a\x04\x14";
+ break;
+ case 'sha256':
+ $t = "\x30\x2f\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x04\x20";
+ break;
+ case 'sha384':
+ $t = "\x30\x3f\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x04\x30";
+ break;
+ case 'sha512':
+ $t = "\x30\x4f\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x04\x40";
+ break;
+ // from https://www.emc.com/collateral/white-papers/h11300-pkcs-1v2-2-rsa-cryptography-standard-wp.pdf#page=40
+ case 'sha224':
+ $t = "\x30\x2b\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x04\x1c";
+ break;
+ case 'sha512/224':
+ $t = "\x30\x2b\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x05\x04\x1c";
+ break;
+ case 'sha512/256':
+ $t = "\x30\x2f\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x06\x04\x20";
+ break;
+ default:
+ throw new UnsupportedAlgorithmException('md2 and md5 require NULLs');
+ }
+ $t .= $h;
+ $tLen = strlen($t);
+
+ if ($emLen < $tLen + 11) {
+ throw new \LengthException('Intended encoded message length too short');
+ }
+
+ $ps = str_repeat(chr(0xFF), $emLen - $tLen - 3);
+
+ $em = "\0\1$ps\0$t";
+
+ return $em;
+ }
+
+ /**
+ * MGF1
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#appendix-B.2.1 RFC3447#appendix-B.2.1}.
+ *
+ * @param string $mgfSeed
+ * @param int $maskLen
+ * @return string
+ */
+ protected function mgf1($mgfSeed, $maskLen)
+ {
+ // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output.
+
+ $t = '';
+ $count = ceil($maskLen / $this->mgfHLen);
+ for ($i = 0; $i < $count; $i++) {
+ $c = pack('N', $i);
+ $t .= $this->mgfHash->hash($mgfSeed . $c);
+ }
+
+ return substr($t, 0, $maskLen);
+ }
+
+ /**
+ * Returns the key size
+ *
+ * More specifically, this returns the size of the modulo in bits.
+ *
+ * @return int
+ */
+ public function getLength()
+ {
+ return !isset($this->modulus) ? 0 : $this->modulus->getLength();
+ }
+
+ /**
+ * Determines which hashing function should be used
+ *
+ * Used with signature production / verification and (if the encryption mode is self::PADDING_OAEP) encryption and
+ * decryption.
+ *
+ * @param string $hash
+ */
+ public function withHash($hash)
+ {
+ $new = clone $this;
+
+ // Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example.
+ switch (strtolower($hash)) {
+ case 'md2':
+ case 'md5':
+ case 'sha1':
+ case 'sha256':
+ case 'sha384':
+ case 'sha512':
+ case 'sha224':
+ case 'sha512/224':
+ case 'sha512/256':
+ $new->hash = new Hash($hash);
+ break;
+ default:
+ throw new UnsupportedAlgorithmException(
+ 'The only supported hash algorithms are: md2, md5, sha1, sha256, sha384, sha512, sha224, sha512/224, sha512/256'
+ );
+ }
+ $new->hLen = $new->hash->getLengthInBytes();
+
+ return $new;
+ }
+
+ /**
+ * Determines which hashing function should be used for the mask generation function
+ *
+ * The mask generation function is used by self::PADDING_OAEP and self::PADDING_PSS and although it's
+ * best if Hash and MGFHash are set to the same thing this is not a requirement.
+ *
+ * @param string $hash
+ */
+ public function withMGFHash($hash)
+ {
+ $new = clone $this;
+
+ // Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example.
+ switch (strtolower($hash)) {
+ case 'md2':
+ case 'md5':
+ case 'sha1':
+ case 'sha256':
+ case 'sha384':
+ case 'sha512':
+ case 'sha224':
+ case 'sha512/224':
+ case 'sha512/256':
+ $new->mgfHash = new Hash($hash);
+ break;
+ default:
+ throw new UnsupportedAlgorithmException(
+ 'The only supported hash algorithms are: md2, md5, sha1, sha256, sha384, sha512, sha224, sha512/224, sha512/256'
+ );
+ }
+ $new->mgfHLen = $new->mgfHash->getLengthInBytes();
+
+ return $new;
+ }
+
+ /**
+ * Returns the MGF hash algorithm currently being used
+ *
+ */
+ public function getMGFHash()
+ {
+ return clone $this->mgfHash;
+ }
+
+ /**
+ * Determines the salt length
+ *
+ * Used by RSA::PADDING_PSS
+ *
+ * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}:
+ *
+ * Typical salt lengths in octets are hLen (the length of the output
+ * of the hash function Hash) and 0.
+ *
+ * @param int $sLen
+ */
+ public function withSaltLength($sLen)
+ {
+ $new = clone $this;
+ $new->sLen = $sLen;
+ return $new;
+ }
+
+ /**
+ * Returns the salt length currently being used
+ *
+ */
+ public function getSaltLength()
+ {
+ return $this->sLen !== null ? $this->sLen : $this->hLen;
+ }
+
+ /**
+ * Determines the label
+ *
+ * Used by RSA::PADDING_OAEP
+ *
+ * To quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}:
+ *
+ * Both the encryption and the decryption operations of RSAES-OAEP take
+ * the value of a label L as input. In this version of PKCS #1, L is
+ * the empty string; other uses of the label are outside the scope of
+ * this document.
+ *
+ * @param string $label
+ */
+ public function withLabel($label)
+ {
+ $new = clone $this;
+ $new->label = $label;
+ return $new;
+ }
+
+ /**
+ * Returns the label currently being used
+ *
+ */
+ public function getLabel()
+ {
+ return $this->label;
+ }
+
+ /**
+ * Determines the padding modes
+ *
+ * Example: $key->withPadding(RSA::ENCRYPTION_PKCS1 | RSA::SIGNATURE_PKCS1);
+ *
+ * @param int $padding
+ */
+ public function withPadding($padding)
+ {
+ $masks = [
+ self::ENCRYPTION_OAEP,
+ self::ENCRYPTION_PKCS1,
+ self::ENCRYPTION_NONE
+ ];
+ $encryptedCount = 0;
+ $selected = 0;
+ foreach ($masks as $mask) {
+ if ($padding & $mask) {
+ $selected = $mask;
+ $encryptedCount++;
+ }
+ }
+ if ($encryptedCount > 1) {
+ throw new InconsistentSetupException('Multiple encryption padding modes have been selected; at most only one should be selected');
+ }
+ $encryptionPadding = $selected;
+
+ $masks = [
+ self::SIGNATURE_PSS,
+ self::SIGNATURE_RELAXED_PKCS1,
+ self::SIGNATURE_PKCS1
+ ];
+ $signatureCount = 0;
+ $selected = 0;
+ foreach ($masks as $mask) {
+ if ($padding & $mask) {
+ $selected = $mask;
+ $signatureCount++;
+ }
+ }
+ if ($signatureCount > 1) {
+ throw new InconsistentSetupException('Multiple signature padding modes have been selected; at most only one should be selected');
+ }
+ $signaturePadding = $selected;
+
+ $new = clone $this;
+ if ($encryptedCount) {
+ $new->encryptionPadding = $encryptionPadding;
+ }
+ if ($signatureCount) {
+ $new->signaturePadding = $signaturePadding;
+ }
+ return $new;
+ }
+
+ /**
+ * Returns the padding currently being used
+ *
+ */
+ public function getPadding()
+ {
+ return $this->signaturePadding | $this->encryptionPadding;
+ }
+
+ /**
+ * Returns the current engine being used
+ *
+ * OpenSSL is only used in this class (and it's subclasses) for key generation
+ * Even then it depends on the parameters you're using. It's not used for
+ * multi-prime RSA nor is it used if the key length is outside of the range
+ * supported by OpenSSL
+ *
+ * @see self::useInternalEngine()
+ * @see self::useBestEngine()
+ * @return string
+ */
+ public function getEngine()
+ {
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+ return self::$engines['OpenSSL'] && self::$defaultExponent == 65537 ?
+ 'OpenSSL' :
+ 'PHP';
+ }
+
+ /**
+ * Enable RSA Blinding
+ *
+ */
+ public static function enableBlinding()
+ {
+ static::$enableBlinding = true;
+ }
+
+ /**
+ * Disable RSA Blinding
+ *
+ */
+ public static function disableBlinding()
+ {
+ static::$enableBlinding = false;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA/Formats/Keys/JWK.php b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/JWK.php
new file mode 100644
index 0000000000..6dcf1cb03d
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/JWK.php
@@ -0,0 +1,142 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\JWK as Progenitor;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * JWK Formatted RSA Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class JWK extends Progenitor
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ if ($key->kty != 'RSA') {
+ throw new \RuntimeException('Only RSA JWK keys are supported');
+ }
+
+ $count = $publicCount = 0;
+ $vars = ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi'];
+ foreach ($vars as $var) {
+ if (!isset($key->$var) || !is_string($key->$var)) {
+ continue;
+ }
+ $count++;
+ $value = new BigInteger(Strings::base64url_decode($key->$var), 256);
+ switch ($var) {
+ case 'n':
+ $publicCount++;
+ $components['modulus'] = $value;
+ break;
+ case 'e':
+ $publicCount++;
+ $components['publicExponent'] = $value;
+ break;
+ case 'd':
+ $components['privateExponent'] = $value;
+ break;
+ case 'p':
+ $components['primes'][1] = $value;
+ break;
+ case 'q':
+ $components['primes'][2] = $value;
+ break;
+ case 'dp':
+ $components['exponents'][1] = $value;
+ break;
+ case 'dq':
+ $components['exponents'][2] = $value;
+ break;
+ case 'qi':
+ $components['coefficients'][2] = $value;
+ }
+ }
+
+ if ($count == count($vars)) {
+ return $components + ['isPublicKey' => false];
+ }
+
+ if ($count == 2 && $publicCount == 2) {
+ return $components + ['isPublicKey' => true];
+ }
+
+ throw new \UnexpectedValueException('Key does not have an appropriate number of RSA parameters');
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ if (count($primes) != 2) {
+ throw new \InvalidArgumentException('JWK does not support multi-prime RSA keys');
+ }
+
+ $key = [
+ 'kty' => 'RSA',
+ 'n' => Strings::base64url_encode($n->toBytes()),
+ 'e' => Strings::base64url_encode($e->toBytes()),
+ 'd' => Strings::base64url_encode($d->toBytes()),
+ 'p' => Strings::base64url_encode($primes[1]->toBytes()),
+ 'q' => Strings::base64url_encode($primes[2]->toBytes()),
+ 'dp' => Strings::base64url_encode($exponents[1]->toBytes()),
+ 'dq' => Strings::base64url_encode($exponents[2]->toBytes()),
+ 'qi' => Strings::base64url_encode($coefficients[2]->toBytes())
+ ];
+
+ return self::wrapKey($key, $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = [])
+ {
+ $key = [
+ 'kty' => 'RSA',
+ 'n' => Strings::base64url_encode($n->toBytes()),
+ 'e' => Strings::base64url_encode($e->toBytes())
+ ];
+
+ return self::wrapKey($key, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php
new file mode 100644
index 0000000000..035fc8c383
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php
@@ -0,0 +1,224 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Microsoft BLOB Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class MSBLOB
+{
+ /**
+ * Public/Private Key Pair
+ *
+ */
+ const PRIVATEKEYBLOB = 0x7;
+ /**
+ * Public Key
+ *
+ */
+ const PUBLICKEYBLOB = 0x6;
+ /**
+ * Public Key
+ *
+ */
+ const PUBLICKEYBLOBEX = 0xA;
+ /**
+ * RSA public key exchange algorithm
+ *
+ */
+ const CALG_RSA_KEYX = 0x0000A400;
+ /**
+ * RSA public key exchange algorithm
+ *
+ */
+ const CALG_RSA_SIGN = 0x00002400;
+ /**
+ * Public Key
+ *
+ */
+ const RSA1 = 0x31415352;
+ /**
+ * Private Key
+ *
+ */
+ const RSA2 = 0x32415352;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @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 = Strings::base64_decode($key);
+
+ if (!is_string($key)) {
+ throw new \UnexpectedValueException('Base64 decoding produced an error');
+ }
+ if (strlen($key) < 20) {
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ // PUBLICKEYSTRUC publickeystruc
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387453(v=vs.85).aspx
+ $unpacked = unpack('atype/aversion/vreserved/Valgo', Strings::shift($key, 8));
+ $type = $unpacked['type'];
+ $version = $unpacked['version'];
+ $reserved = $unpacked['reserved'];
+ $algo = $unpacked['algo'];
+ switch (ord($type)) {
+ case self::PUBLICKEYBLOB:
+ case self::PUBLICKEYBLOBEX:
+ $publickey = true;
+ break;
+ case self::PRIVATEKEYBLOB:
+ $publickey = false;
+ break;
+ default:
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ $components = ['isPublicKey' => $publickey];
+
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375549(v=vs.85).aspx
+ switch ($algo) {
+ case self::CALG_RSA_KEYX:
+ case self::CALG_RSA_SIGN:
+ break;
+ default:
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ // RSAPUBKEY rsapubkey
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387685(v=vs.85).aspx
+ // could do V for pubexp but that's unsigned 32-bit whereas some PHP installs only do signed 32-bit
+ $unpacked = unpack('Vmagic/Vbitlen/a4pubexp', Strings::shift($key, 12));
+ $magic = $unpacked['magic'];
+ $bitlen = $unpacked['bitlen'];
+ $pubexp = $unpacked['pubexp'];
+ switch ($magic) {
+ case self::RSA2:
+ $components['isPublicKey'] = false;
+ // fall-through
+ case self::RSA1:
+ break;
+ default:
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ $baseLength = $bitlen / 16;
+ if (strlen($key) != 2 * $baseLength && strlen($key) != 9 * $baseLength) {
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(strrev($pubexp), 256);
+ // BYTE modulus[rsapubkey.bitlen/8]
+ $components['modulus'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256);
+
+ if ($publickey) {
+ return $components;
+ }
+
+ $components['isPublicKey'] = false;
+
+ // BYTE prime1[rsapubkey.bitlen/16]
+ $components['primes'] = [1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)];
+ // BYTE prime2[rsapubkey.bitlen/16]
+ $components['primes'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256);
+ // BYTE exponent1[rsapubkey.bitlen/16]
+ $components['exponents'] = [1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)];
+ // BYTE exponent2[rsapubkey.bitlen/16]
+ $components['exponents'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256);
+ // BYTE coefficient[rsapubkey.bitlen/16]
+ $components['coefficients'] = [2 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)];
+ if (isset($components['privateExponent'])) {
+ $components['publicExponent'] = $components['privateExponent'];
+ }
+ // BYTE privateExponent[rsapubkey.bitlen/8]
+ $components['privateExponent'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256);
+
+ return $components;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '')
+ {
+ if (count($primes) != 2) {
+ throw new \InvalidArgumentException('MSBLOB does not support multi-prime RSA keys');
+ }
+
+ if (!empty($password) && is_string($password)) {
+ throw new UnsupportedFormatException('MSBLOB private keys do not support encryption');
+ }
+
+ $n = strrev($n->toBytes());
+ $e = str_pad(strrev($e->toBytes()), 4, "\0");
+ $key = pack('aavV', chr(self::PRIVATEKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX);
+ $key .= pack('VVa*', self::RSA2, 8 * strlen($n), $e);
+ $key .= $n;
+ $key .= strrev($primes[1]->toBytes());
+ $key .= strrev($primes[2]->toBytes());
+ $key .= strrev($exponents[1]->toBytes());
+ $key .= strrev($exponents[2]->toBytes());
+ $key .= strrev($coefficients[2]->toBytes());
+ $key .= strrev($d->toBytes());
+
+ return Strings::base64_encode($key);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ $n = strrev($n->toBytes());
+ $e = str_pad(strrev($e->toBytes()), 4, "\0");
+ $key = pack('aavV', chr(self::PUBLICKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX);
+ $key .= pack('VVa*', self::RSA1, 8 * strlen($n), $e);
+ $key .= $n;
+
+ return Strings::base64_encode($key);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php
new file mode 100644
index 0000000000..ca74ea481a
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php
@@ -0,0 +1,132 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * OpenSSH Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class OpenSSH extends Progenitor
+{
+ /**
+ * Supported Key Types
+ *
+ * @var array
+ */
+ protected static $types = ['ssh-rsa'];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ static $one;
+ if (!isset($one)) {
+ $one = new BigInteger(1);
+ }
+
+ $parsed = parent::load($key, $password);
+
+ if (isset($parsed['paddedKey'])) {
+ list($type) = Strings::unpackSSH2('s', $parsed['paddedKey']);
+ if ($type != $parsed['type']) {
+ throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
+ }
+
+ $primes = $coefficients = [];
+
+ list(
+ $modulus,
+ $publicExponent,
+ $privateExponent,
+ $coefficients[2],
+ $primes[1],
+ $primes[2],
+ $comment,
+ ) = Strings::unpackSSH2('i6s', $parsed['paddedKey']);
+
+ $temp = $primes[1]->subtract($one);
+ $exponents = [1 => $publicExponent->modInverse($temp)];
+ $temp = $primes[2]->subtract($one);
+ $exponents[] = $publicExponent->modInverse($temp);
+
+ $isPublicKey = false;
+
+ return compact('publicExponent', 'modulus', 'privateExponent', 'primes', 'coefficients', 'exponents', 'comment', 'isPublicKey');
+ }
+
+ list($publicExponent, $modulus) = Strings::unpackSSH2('ii', $parsed['publicKey']);
+
+ return [
+ 'isPublicKey' => true,
+ 'modulus' => $modulus,
+ 'publicExponent' => $publicExponent,
+ 'comment' => $parsed['comment']
+ ];
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = [])
+ {
+ $RSAPublicKey = Strings::packSSH2('sii', 'ssh-rsa', $e, $n);
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $RSAPublicKey;
+ }
+
+ $comment = isset($options['comment']) ? $options['comment'] : self::$comment;
+ $RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $comment;
+
+ return $RSAPublicKey;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ $publicKey = self::savePublicKey($n, $e, ['binary' => true]);
+ $privateKey = Strings::packSSH2('si6', 'ssh-rsa', $n, $e, $d, $coefficients[2], $primes[1], $primes[2]);
+
+ return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php
new file mode 100644
index 0000000000..68d92701ed
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php
@@ -0,0 +1,187 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#1 Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class PKCS1 extends Progenitor
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @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, 'PUBLIC') !== false) {
+ $components = ['isPublicKey' => true];
+ } elseif (strpos($key, 'PRIVATE') !== false) {
+ $components = ['isPublicKey' => false];
+ } else {
+ $components = [];
+ }
+
+ $key = parent::load($key, $password);
+
+ $decoded = ASN1::decodeBER($key);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\RSAPrivateKey::MAP);
+ if (is_array($key)) {
+ $components += [
+ 'modulus' => $key['modulus'],
+ 'publicExponent' => $key['publicExponent'],
+ 'privateExponent' => $key['privateExponent'],
+ 'primes' => [1 => $key['prime1'], $key['prime2']],
+ 'exponents' => [1 => $key['exponent1'], $key['exponent2']],
+ 'coefficients' => [2 => $key['coefficient']]
+ ];
+ if ($key['version'] == 'multi') {
+ foreach ($key['otherPrimeInfos'] as $primeInfo) {
+ $components['primes'][] = $primeInfo['prime'];
+ $components['exponents'][] = $primeInfo['exponent'];
+ $components['coefficients'][] = $primeInfo['coefficient'];
+ }
+ }
+ if (!isset($components['isPublicKey'])) {
+ $components['isPublicKey'] = false;
+ }
+ return $components;
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\RSAPublicKey::MAP);
+
+ if (!is_array($key)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping');
+ }
+
+ if (!isset($components['isPublicKey'])) {
+ $components['isPublicKey'] = true;
+ }
+
+ $components = $components + $key;
+ foreach ($components as &$val) {
+ if ($val instanceof BigInteger) {
+ $val = self::makePositive($val);
+ }
+ if (is_array($val)) {
+ foreach ($val as &$subval) {
+ if ($subval instanceof BigInteger) {
+ $subval = self::makePositive($subval);
+ }
+ }
+ }
+ }
+
+ return $components + $key;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ $num_primes = count($primes);
+ $key = [
+ 'version' => $num_primes == 2 ? 'two-prime' : 'multi',
+ 'modulus' => $n,
+ 'publicExponent' => $e,
+ 'privateExponent' => $d,
+ 'prime1' => $primes[1],
+ 'prime2' => $primes[2],
+ 'exponent1' => $exponents[1],
+ 'exponent2' => $exponents[2],
+ 'coefficient' => $coefficients[2]
+ ];
+ for ($i = 3; $i <= $num_primes; $i++) {
+ $key['otherPrimeInfos'][] = [
+ 'prime' => $primes[$i],
+ 'exponent' => $exponents[$i],
+ 'coefficient' => $coefficients[$i]
+ ];
+ }
+
+ $key = ASN1::encodeDER($key, Maps\RSAPrivateKey::MAP);
+
+ return self::wrapPrivateKey($key, 'RSA', $password, $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ $key = [
+ 'modulus' => $n,
+ 'publicExponent' => $e
+ ];
+
+ $key = ASN1::encodeDER($key, Maps\RSAPublicKey::MAP);
+
+ return self::wrapPublicKey($key, 'RSA');
+ }
+
+ /**
+ * Negative numbers make no sense in RSA so convert them to positive
+ *
+ * @param BigInteger $x
+ * @return string
+ */
+ private static function makePositive(BigInteger $x)
+ {
+ return $x->isNegative() ?
+ new BigInteger($x->toBytes(true), 256) :
+ $x;
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php
new file mode 100644
index 0000000000..30f63ff979
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php
@@ -0,0 +1,122 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#8 Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class PKCS8 extends Progenitor
+{
+ /**
+ * OID Name
+ *
+ * @var string
+ */
+ const OID_NAME = 'rsaEncryption';
+
+ /**
+ * OID Value
+ *
+ * @var string
+ */
+ const OID_VALUE = '1.2.840.113549.1.1.1';
+
+ /**
+ * Child OIDs loaded
+ *
+ * @var bool
+ */
+ protected static $childOIDsLoaded = false;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ if (isset($key['privateKey'])) {
+ $components['isPublicKey'] = false;
+ $type = 'private';
+ } else {
+ $components['isPublicKey'] = true;
+ $type = 'public';
+ }
+
+ $result = $components + PKCS1::load($key[$type . 'Key']);
+
+ if (isset($key['meta'])) {
+ $result['meta'] = $key['meta'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ $key = PKCS1::savePrivateKey($n, $e, $d, $primes, $exponents, $coefficients);
+ $key = ASN1::extractBER($key);
+ return self::wrapPrivateKey($key, [], null, $password, null, '', $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = [])
+ {
+ $key = PKCS1::savePublicKey($n, $e);
+ $key = ASN1::extractBER($key);
+ return self::wrapPublicKey($key, null, null, $options);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA/Formats/Keys/PSS.php b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/PSS.php
new file mode 100644
index 0000000000..bf51bcf769
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/PSS.php
@@ -0,0 +1,238 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#8 Formatted RSA-PSS Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class PSS extends Progenitor
+{
+ /**
+ * OID Name
+ *
+ * @var string
+ */
+ const OID_NAME = 'id-RSASSA-PSS';
+
+ /**
+ * OID Value
+ *
+ * @var string
+ */
+ const OID_VALUE = '1.2.840.113549.1.1.10';
+
+ /**
+ * OIDs loaded
+ *
+ * @var bool
+ */
+ private static $oidsLoaded = false;
+
+ /**
+ * Child OIDs loaded
+ *
+ * @var bool
+ */
+ protected static $childOIDsLoaded = false;
+
+ /**
+ * Initialize static variables
+ */
+ private static function initialize_static_variables()
+ {
+ if (!self::$oidsLoaded) {
+ ASN1::loadOIDs([
+ 'md2' => '1.2.840.113549.2.2',
+ 'md4' => '1.2.840.113549.2.4',
+ 'md5' => '1.2.840.113549.2.5',
+ 'id-sha1' => '1.3.14.3.2.26',
+ 'id-sha256' => '2.16.840.1.101.3.4.2.1',
+ 'id-sha384' => '2.16.840.1.101.3.4.2.2',
+ 'id-sha512' => '2.16.840.1.101.3.4.2.3',
+ 'id-sha224' => '2.16.840.1.101.3.4.2.4',
+ 'id-sha512/224' => '2.16.840.1.101.3.4.2.5',
+ 'id-sha512/256' => '2.16.840.1.101.3.4.2.6',
+
+ 'id-mgf1' => '1.2.840.113549.1.1.8'
+ ]);
+ self::$oidsLoaded = true;
+ }
+ }
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ self::initialize_static_variables();
+
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ $components = ['isPublicKey' => strpos($key, 'PUBLIC') !== false];
+
+ $key = parent::load($key, $password);
+
+ $type = isset($key['privateKey']) ? 'private' : 'public';
+
+ $result = $components + PKCS1::load($key[$type . 'Key']);
+
+ if (isset($key[$type . 'KeyAlgorithm']['parameters'])) {
+ $decoded = ASN1::decodeBER($key[$type . 'KeyAlgorithm']['parameters']);
+ if ($decoded === false) {
+ throw new \UnexpectedValueException('Unable to decode parameters');
+ }
+ $params = ASN1::asn1map($decoded[0], Maps\RSASSA_PSS_params::MAP);
+ } else {
+ $params = [];
+ }
+
+ if (isset($params['maskGenAlgorithm']['parameters'])) {
+ $decoded = ASN1::decodeBER($params['maskGenAlgorithm']['parameters']);
+ if ($decoded === false) {
+ throw new \UnexpectedValueException('Unable to decode parameters');
+ }
+ $params['maskGenAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], Maps\HashAlgorithm::MAP);
+ } else {
+ $params['maskGenAlgorithm'] = [
+ 'algorithm' => 'id-mgf1',
+ 'parameters' => ['algorithm' => 'id-sha1']
+ ];
+ }
+
+ if (!isset($params['hashAlgorithm']['algorithm'])) {
+ $params['hashAlgorithm']['algorithm'] = 'id-sha1';
+ }
+
+ $result['hash'] = str_replace('id-', '', $params['hashAlgorithm']['algorithm']);
+ $result['MGFHash'] = str_replace('id-', '', $params['maskGenAlgorithm']['parameters']['algorithm']);
+ if (isset($params['saltLength'])) {
+ $result['saltLength'] = (int) $params['saltLength']->toString();
+ }
+
+ if (isset($key['meta'])) {
+ $result['meta'] = $key['meta'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ self::initialize_static_variables();
+
+ $key = PKCS1::savePrivateKey($n, $e, $d, $primes, $exponents, $coefficients);
+ $key = ASN1::extractBER($key);
+ $params = self::savePSSParams($options);
+ return self::wrapPrivateKey($key, [], $params, $password, null, '', $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = [])
+ {
+ self::initialize_static_variables();
+
+ $key = PKCS1::savePublicKey($n, $e);
+ $key = ASN1::extractBER($key);
+ $params = self::savePSSParams($options);
+ return self::wrapPublicKey($key, $params);
+ }
+
+ /**
+ * Encodes PSS parameters
+ *
+ * @param array $options
+ * @return string
+ */
+ public static function savePSSParams(array $options)
+ {
+ /*
+ The trailerField field is an integer. It provides
+ compatibility with IEEE Std 1363a-2004 [P1363A]. The value
+ MUST be 1, which represents the trailer field with hexadecimal
+ value 0xBC. Other trailer fields, including the trailer field
+ composed of HashID concatenated with 0xCC that is specified in
+ IEEE Std 1363a, are not supported. Implementations that
+ perform signature generation MUST omit the trailerField field,
+ indicating that the default trailer field value was used.
+ Implementations that perform signature validation MUST
+ recognize both a present trailerField field with value 1 and an
+ absent trailerField field.
+
+ source: https://tools.ietf.org/html/rfc4055#page-9
+ */
+ $params = [
+ 'trailerField' => new BigInteger(1)
+ ];
+ if (isset($options['hash'])) {
+ $params['hashAlgorithm']['algorithm'] = 'id-' . $options['hash'];
+ }
+ if (isset($options['MGFHash'])) {
+ $temp = ['algorithm' => 'id-' . $options['MGFHash']];
+ $temp = ASN1::encodeDER($temp, Maps\HashAlgorithm::MAP);
+ $params['maskGenAlgorithm'] = [
+ 'algorithm' => 'id-mgf1',
+ 'parameters' => new ASN1\Element($temp)
+ ];
+ }
+ if (isset($options['saltLength'])) {
+ $params['saltLength'] = new BigInteger($options['saltLength']);
+ }
+
+ return new ASN1\Element(ASN1::encodeDER($params, Maps\RSASSA_PSS_params::MAP));
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php
new file mode 100644
index 0000000000..8416758c2d
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php
@@ -0,0 +1,124 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PuTTY as Progenitor;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PuTTY Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class PuTTY extends Progenitor
+{
+ /**
+ * Public Handler
+ *
+ * @var string
+ */
+ const PUBLIC_HANDLER = 'phpseclib3\Crypt\RSA\Formats\Keys\OpenSSH';
+
+ /**
+ * Algorithm Identifier
+ *
+ * @var array
+ */
+ protected static $types = ['ssh-rsa'];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ static $one;
+ if (!isset($one)) {
+ $one = new BigInteger(1);
+ }
+
+ $components = parent::load($key, $password);
+ if (!isset($components['private'])) {
+ return $components;
+ }
+ $type = $components['type'];
+ $comment = $components['comment'];
+ $public = $components['public'];
+ $private = $components['private'];
+ unset($components['public'], $components['private']);
+
+ $isPublicKey = false;
+
+ $result = Strings::unpackSSH2('ii', $public);
+ if ($result === false) {
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+ list($publicExponent, $modulus) = $result;
+
+ $result = Strings::unpackSSH2('iiii', $private);
+ if ($result === false) {
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+ $primes = $coefficients = [];
+ list($privateExponent, $primes[1], $primes[2], $coefficients[2]) = $result;
+
+ $temp = $primes[1]->subtract($one);
+ $exponents = [1 => $publicExponent->modInverse($temp)];
+ $temp = $primes[2]->subtract($one);
+ $exponents[] = $publicExponent->modInverse($temp);
+
+ return compact('publicExponent', 'modulus', 'privateExponent', 'primes', 'coefficients', 'exponents', 'comment', 'isPublicKey');
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ if (count($primes) != 2) {
+ throw new \InvalidArgumentException('PuTTY does not support multi-prime RSA keys');
+ }
+
+ $public = Strings::packSSH2('ii', $e, $n);
+ $private = Strings::packSSH2('iiii', $d, $primes[1], $primes[2], $coefficients[2]);
+
+ return self::wrapPrivateKey($public, $private, 'ssh-rsa', $password, $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ return self::wrapPublicKey(Strings::packSSH2('ii', $e, $n), 'ssh-rsa');
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA/Formats/Keys/Raw.php b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/Raw.php
new file mode 100644
index 0000000000..55c7ccd7aa
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/Raw.php
@@ -0,0 +1,184 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Raw RSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class Raw
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ if (!is_array($key)) {
+ throw new \UnexpectedValueException('Key should be a array - not a ' . gettype($key));
+ }
+
+ $key = array_change_key_case($key, CASE_LOWER);
+
+ $components = ['isPublicKey' => false];
+
+ foreach (['e', 'exponent', 'publicexponent', 0, 'privateexponent', 'd'] as $index) {
+ if (isset($key[$index])) {
+ $components['publicExponent'] = $key[$index];
+ break;
+ }
+ }
+
+ foreach (['n', 'modulo', 'modulus', 1] as $index) {
+ if (isset($key[$index])) {
+ $components['modulus'] = $key[$index];
+ break;
+ }
+ }
+
+ if (!isset($components['publicExponent']) || !isset($components['modulus'])) {
+ throw new \UnexpectedValueException('Modulus / exponent not present');
+ }
+
+ if (isset($key['primes'])) {
+ $components['primes'] = $key['primes'];
+ } elseif (isset($key['p']) && isset($key['q'])) {
+ $indices = [
+ ['p', 'q'],
+ ['prime1', 'prime2']
+ ];
+ foreach ($indices as $index) {
+ list($i0, $i1) = $index;
+ if (isset($key[$i0]) && isset($key[$i1])) {
+ $components['primes'] = [1 => $key[$i0], $key[$i1]];
+ }
+ }
+ }
+
+ if (isset($key['exponents'])) {
+ $components['exponents'] = $key['exponents'];
+ } else {
+ $indices = [
+ ['dp', 'dq'],
+ ['exponent1', 'exponent2']
+ ];
+ foreach ($indices as $index) {
+ list($i0, $i1) = $index;
+ if (isset($key[$i0]) && isset($key[$i1])) {
+ $components['exponents'] = [1 => $key[$i0], $key[$i1]];
+ }
+ }
+ }
+
+ if (isset($key['coefficients'])) {
+ $components['coefficients'] = $key['coefficients'];
+ } else {
+ foreach (['inverseq', 'q\'', 'coefficient'] as $index) {
+ if (isset($key[$index])) {
+ $components['coefficients'] = [2 => $key[$index]];
+ }
+ }
+ }
+
+ if (!isset($components['primes'])) {
+ $components['isPublicKey'] = true;
+ return $components;
+ }
+
+ if (!isset($components['exponents'])) {
+ $one = new BigInteger(1);
+ $temp = $components['primes'][1]->subtract($one);
+ $exponents = [1 => $components['publicExponent']->modInverse($temp)];
+ $temp = $components['primes'][2]->subtract($one);
+ $exponents[] = $components['publicExponent']->modInverse($temp);
+ $components['exponents'] = $exponents;
+ }
+
+ if (!isset($components['coefficients'])) {
+ $components['coefficients'] = [2 => $components['primes'][2]->modInverse($components['primes'][1])];
+ }
+
+ foreach (['privateexponent', 'd'] as $index) {
+ if (isset($key[$index])) {
+ $components['privateExponent'] = $key[$index];
+ break;
+ }
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return array
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ if (!empty($password) && is_string($password)) {
+ throw new UnsupportedFormatException('Raw private keys do not support encryption');
+ }
+
+ return [
+ 'e' => clone $e,
+ 'n' => clone $n,
+ 'd' => clone $d,
+ 'primes' => array_map(function ($var) {
+ return clone $var;
+ }, $primes),
+ 'exponents' => array_map(function ($var) {
+ return clone $var;
+ }, $exponents),
+ 'coefficients' => array_map(function ($var) {
+ return clone $var;
+ }, $coefficients)
+ ];
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @return array
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ return ['e' => clone $e, 'n' => clone $n];
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA/Formats/Keys/XML.php b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/XML.php
new file mode 100644
index 0000000000..d569dea6da
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/XML.php
@@ -0,0 +1,171 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Exception\BadConfigurationException;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * XML Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+abstract class XML
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @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 (!class_exists('DOMDocument')) {
+ throw new BadConfigurationException('The dom extension is not setup correctly on this system');
+ }
+
+ $components = [
+ 'isPublicKey' => false,
+ 'primes' => [],
+ 'exponents' => [],
+ 'coefficients' => []
+ ];
+
+ $use_errors = libxml_use_internal_errors(true);
+
+ $dom = new \DOMDocument();
+ if (substr($key, 0, 5) != '' . $key . '';
+ }
+ if (!$dom->loadXML($key)) {
+ libxml_use_internal_errors($use_errors);
+ throw new \UnexpectedValueException('Key does not appear to contain XML');
+ }
+ $xpath = new \DOMXPath($dom);
+ $keys = ['modulus', 'exponent', 'p', 'q', 'dp', 'dq', 'inverseq', 'd'];
+ foreach ($keys as $key) {
+ // $dom->getElementsByTagName($key) is case-sensitive
+ $temp = $xpath->query("//*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$key']");
+ if (!$temp->length) {
+ continue;
+ }
+ $value = new BigInteger(Strings::base64_decode($temp->item(0)->nodeValue), 256);
+ switch ($key) {
+ case 'modulus':
+ $components['modulus'] = $value;
+ break;
+ case 'exponent':
+ $components['publicExponent'] = $value;
+ break;
+ case 'p':
+ $components['primes'][1] = $value;
+ break;
+ case 'q':
+ $components['primes'][2] = $value;
+ break;
+ case 'dp':
+ $components['exponents'][1] = $value;
+ break;
+ case 'dq':
+ $components['exponents'][2] = $value;
+ break;
+ case 'inverseq':
+ $components['coefficients'][2] = $value;
+ break;
+ case 'd':
+ $components['privateExponent'] = $value;
+ }
+ }
+
+ libxml_use_internal_errors($use_errors);
+
+ foreach ($components as $key => $value) {
+ if (is_array($value) && !count($value)) {
+ unset($components[$key]);
+ }
+ }
+
+ if (isset($components['modulus']) && isset($components['publicExponent'])) {
+ if (count($components) == 3) {
+ $components['isPublicKey'] = true;
+ }
+ return $components;
+ }
+
+ throw new \UnexpectedValueException('Modulus / exponent not present');
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '')
+ {
+ if (count($primes) != 2) {
+ throw new \InvalidArgumentException('XML does not support multi-prime RSA keys');
+ }
+
+ if (!empty($password) && is_string($password)) {
+ throw new UnsupportedFormatException('XML private keys do not support encryption');
+ }
+
+ return "\r\n" .
+ ' ' . Strings::base64_encode($n->toBytes()) . "\r\n" .
+ ' ' . Strings::base64_encode($e->toBytes()) . "\r\n" .
+ ' ' . Strings::base64_encode($primes[1]->toBytes()) . "
\r\n" .
+ ' ' . Strings::base64_encode($primes[2]->toBytes()) . "
\r\n" .
+ ' ' . Strings::base64_encode($exponents[1]->toBytes()) . "\r\n" .
+ ' ' . Strings::base64_encode($exponents[2]->toBytes()) . "\r\n" .
+ ' ' . Strings::base64_encode($coefficients[2]->toBytes()) . "\r\n" .
+ ' ' . Strings::base64_encode($d->toBytes()) . "\r\n" .
+ '';
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ return "\r\n" .
+ ' ' . Strings::base64_encode($n->toBytes()) . "\r\n" .
+ ' ' . Strings::base64_encode($e->toBytes()) . "\r\n" .
+ '';
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA/Formats/Keys/index.php b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA/Formats/Keys/index.php
@@ -0,0 +1,8 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA;
+
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Crypt\RSA;
+use phpseclib3\Crypt\RSA\Formats\Keys\PSS;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Raw RSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+final class PrivateKey extends RSA implements Common\PrivateKey
+{
+ use Common\Traits\PasswordProtected;
+
+ /**
+ * Primes for Chinese Remainder Theorem (ie. p and q)
+ *
+ * @var array
+ */
+ protected $primes;
+
+ /**
+ * Exponents for Chinese Remainder Theorem (ie. dP and dQ)
+ *
+ * @var array
+ */
+ protected $exponents;
+
+ /**
+ * Coefficients for Chinese Remainder Theorem (ie. qInv)
+ *
+ * @var array
+ */
+ protected $coefficients;
+
+ /**
+ * Private Exponent
+ *
+ * @var BigInteger
+ */
+ protected $privateExponent;
+
+ /**
+ * RSADP
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}.
+ *
+ * @return bool|BigInteger
+ */
+ private function rsadp(BigInteger $c)
+ {
+ if ($c->compare(self::$zero) < 0 || $c->compare($this->modulus) > 0) {
+ throw new \OutOfRangeException('Ciphertext representative out of range');
+ }
+ return $this->exponentiate($c);
+ }
+
+ /**
+ * RSASP1
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}.
+ *
+ * @return bool|BigInteger
+ */
+ private function rsasp1(BigInteger $m)
+ {
+ if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) {
+ throw new \OutOfRangeException('Signature representative out of range');
+ }
+ return $this->exponentiate($m);
+ }
+
+ /**
+ * Exponentiate
+ *
+ * @param BigInteger $x
+ * @return BigInteger
+ */
+ protected function exponentiate(BigInteger $x)
+ {
+ switch (true) {
+ case empty($this->primes):
+ case $this->primes[1]->equals(self::$zero):
+ case empty($this->coefficients):
+ case $this->coefficients[2]->equals(self::$zero):
+ case empty($this->exponents):
+ case $this->exponents[1]->equals(self::$zero):
+ return $x->modPow($this->exponent, $this->modulus);
+ }
+
+ $num_primes = count($this->primes);
+
+ if (!static::$enableBlinding) {
+ $m_i = [
+ 1 => $x->modPow($this->exponents[1], $this->primes[1]),
+ 2 => $x->modPow($this->exponents[2], $this->primes[2])
+ ];
+ $h = $m_i[1]->subtract($m_i[2]);
+ $h = $h->multiply($this->coefficients[2]);
+ list(, $h) = $h->divide($this->primes[1]);
+ $m = $m_i[2]->add($h->multiply($this->primes[2]));
+
+ $r = $this->primes[1];
+ for ($i = 3; $i <= $num_primes; $i++) {
+ $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]);
+
+ $r = $r->multiply($this->primes[$i - 1]);
+
+ $h = $m_i->subtract($m);
+ $h = $h->multiply($this->coefficients[$i]);
+ list(, $h) = $h->divide($this->primes[$i]);
+
+ $m = $m->add($r->multiply($h));
+ }
+ } else {
+ $smallest = $this->primes[1];
+ for ($i = 2; $i <= $num_primes; $i++) {
+ if ($smallest->compare($this->primes[$i]) > 0) {
+ $smallest = $this->primes[$i];
+ }
+ }
+
+ $r = BigInteger::randomRange(self::$one, $smallest->subtract(self::$one));
+
+ $m_i = [
+ 1 => $this->blind($x, $r, 1),
+ 2 => $this->blind($x, $r, 2)
+ ];
+ $h = $m_i[1]->subtract($m_i[2]);
+ $h = $h->multiply($this->coefficients[2]);
+ list(, $h) = $h->divide($this->primes[1]);
+ $m = $m_i[2]->add($h->multiply($this->primes[2]));
+
+ $r = $this->primes[1];
+ for ($i = 3; $i <= $num_primes; $i++) {
+ $m_i = $this->blind($x, $r, $i);
+
+ $r = $r->multiply($this->primes[$i - 1]);
+
+ $h = $m_i->subtract($m);
+ $h = $h->multiply($this->coefficients[$i]);
+ list(, $h) = $h->divide($this->primes[$i]);
+
+ $m = $m->add($r->multiply($h));
+ }
+ }
+
+ return $m;
+ }
+
+ /**
+ * Performs RSA Blinding
+ *
+ * Protects against timing attacks by employing RSA Blinding.
+ * Returns $x->modPow($this->exponents[$i], $this->primes[$i])
+ *
+ * @param BigInteger $x
+ * @param BigInteger $r
+ * @param int $i
+ * @return BigInteger
+ */
+ private function blind(BigInteger $x, BigInteger $r, $i)
+ {
+ $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i]));
+ $x = $x->modPow($this->exponents[$i], $this->primes[$i]);
+
+ $r = $r->modInverse($this->primes[$i]);
+ $x = $x->multiply($r);
+ list(, $x) = $x->divide($this->primes[$i]);
+
+ return $x;
+ }
+
+ /**
+ * EMSA-PSS-ENCODE
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}.
+ *
+ * @return string
+ * @param string $m
+ * @throws \RuntimeException on encoding error
+ * @param int $emBits
+ */
+ private function emsa_pss_encode($m, $emBits)
+ {
+ // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
+ // be output.
+
+ $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8)
+ $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
+
+ $mHash = $this->hash->hash($m);
+ if ($emLen < $this->hLen + $sLen + 2) {
+ throw new \LengthException('RSA modulus too short');
+ }
+
+ $salt = Random::string($sLen);
+ $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
+ $h = $this->hash->hash($m2);
+ $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2);
+ $db = $ps . chr(1) . $salt;
+ $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1); // ie. stlren($db)
+ $maskedDB = $db ^ $dbMask;
+ $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0];
+ $em = $maskedDB . $h . chr(0xBC);
+
+ return $em;
+ }
+
+ /**
+ * RSASSA-PSS-SIGN
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}.
+ *
+ * @param string $m
+ * @return bool|string
+ */
+ private function rsassa_pss_sign($m)
+ {
+ // EMSA-PSS encoding
+
+ $em = $this->emsa_pss_encode($m, 8 * $this->k - 1);
+
+ // RSA signature
+
+ $m = $this->os2ip($em);
+ $s = $this->rsasp1($m);
+ $s = $this->i2osp($s, $this->k);
+
+ // Output the signature S
+
+ return $s;
+ }
+
+ /**
+ * RSASSA-PKCS1-V1_5-SIGN
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}.
+ *
+ * @param string $m
+ * @throws \LengthException if the RSA modulus is too short
+ * @return bool|string
+ */
+ private function rsassa_pkcs1_v1_5_sign($m)
+ {
+ // EMSA-PKCS1-v1_5 encoding
+
+ // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus
+ // too short" and stop.
+ try {
+ $em = $this->emsa_pkcs1_v1_5_encode($m, $this->k);
+ } catch (\LengthException $e) {
+ throw new \LengthException('RSA modulus too short');
+ }
+
+ // RSA signature
+
+ $m = $this->os2ip($em);
+ $s = $this->rsasp1($m);
+ $s = $this->i2osp($s, $this->k);
+
+ // Output the signature S
+
+ return $s;
+ }
+
+ /**
+ * Create a signature
+ *
+ * @see self::verify()
+ * @param string $message
+ * @return string
+ */
+ public function sign($message)
+ {
+ switch ($this->signaturePadding) {
+ case self::SIGNATURE_PKCS1:
+ case self::SIGNATURE_RELAXED_PKCS1:
+ return $this->rsassa_pkcs1_v1_5_sign($message);
+ //case self::SIGNATURE_PSS:
+ default:
+ return $this->rsassa_pss_sign($message);
+ }
+ }
+
+ /**
+ * RSAES-PKCS1-V1_5-DECRYPT
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}.
+ *
+ * @param string $c
+ * @return bool|string
+ */
+ private function rsaes_pkcs1_v1_5_decrypt($c)
+ {
+ // Length checking
+
+ if (strlen($c) != $this->k) { // or if k < 11
+ throw new \LengthException('Ciphertext representative too long');
+ }
+
+ // RSA decryption
+
+ $c = $this->os2ip($c);
+ $m = $this->rsadp($c);
+ $em = $this->i2osp($m, $this->k);
+
+ // EME-PKCS1-v1_5 decoding
+
+ if (ord($em[0]) != 0 || ord($em[1]) > 2) {
+ throw new \RuntimeException('Decryption error');
+ }
+
+ $ps = substr($em, 2, strpos($em, chr(0), 2) - 2);
+ $m = substr($em, strlen($ps) + 3);
+
+ if (strlen($ps) < 8) {
+ throw new \RuntimeException('Decryption error');
+ }
+
+ // Output M
+
+ return $m;
+ }
+
+ /**
+ * RSAES-OAEP-DECRYPT
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error
+ * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2:
+ *
+ * Note. Care must be taken to ensure that an opponent cannot
+ * distinguish the different error conditions in Step 3.g, whether by
+ * error message or timing, or, more generally, learn partial
+ * information about the encoded message EM. Otherwise an opponent may
+ * be able to obtain useful information about the decryption of the
+ * ciphertext C, leading to a chosen-ciphertext attack such as the one
+ * observed by Manger [36].
+ *
+ * @param string $c
+ * @return bool|string
+ */
+ private function rsaes_oaep_decrypt($c)
+ {
+ // Length checking
+
+ // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
+ // be output.
+
+ if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) {
+ throw new \LengthException('Ciphertext representative too long');
+ }
+
+ // RSA decryption
+
+ $c = $this->os2ip($c);
+ $m = $this->rsadp($c);
+ $em = $this->i2osp($m, $this->k);
+
+ // EME-OAEP decoding
+
+ $lHash = $this->hash->hash($this->label);
+ $y = ord($em[0]);
+ $maskedSeed = substr($em, 1, $this->hLen);
+ $maskedDB = substr($em, $this->hLen + 1);
+ $seedMask = $this->mgf1($maskedDB, $this->hLen);
+ $seed = $maskedSeed ^ $seedMask;
+ $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1);
+ $db = $maskedDB ^ $dbMask;
+ $lHash2 = substr($db, 0, $this->hLen);
+ $m = substr($db, $this->hLen);
+ $hashesMatch = hash_equals($lHash, $lHash2);
+ $leadingZeros = 1;
+ $patternMatch = 0;
+ $offset = 0;
+ for ($i = 0; $i < strlen($m); $i++) {
+ $patternMatch |= $leadingZeros & ($m[$i] === "\1");
+ $leadingZeros &= $m[$i] === "\0";
+ $offset += $patternMatch ? 0 : 1;
+ }
+
+ // we do | instead of || to avoid https://en.wikipedia.org/wiki/Short-circuit_evaluation
+ // to protect against timing attacks
+ if (!$hashesMatch | !$patternMatch) {
+ throw new \RuntimeException('Decryption error');
+ }
+
+ // Output the message M
+
+ return substr($m, $offset + 1);
+ }
+
+ /**
+ * Raw Encryption / Decryption
+ *
+ * Doesn't use padding and is not recommended.
+ *
+ * @param string $m
+ * @return bool|string
+ * @throws \LengthException if strlen($m) > $this->k
+ */
+ private function raw_encrypt($m)
+ {
+ if (strlen($m) > $this->k) {
+ throw new \LengthException('Ciphertext representative too long');
+ }
+
+ $temp = $this->os2ip($m);
+ $temp = $this->rsadp($temp);
+ return $this->i2osp($temp, $this->k);
+ }
+
+ /**
+ * Decryption
+ *
+ * @see self::encrypt()
+ * @param string $ciphertext
+ * @return bool|string
+ */
+ public function decrypt($ciphertext)
+ {
+ switch ($this->encryptionPadding) {
+ case self::ENCRYPTION_NONE:
+ return $this->raw_encrypt($ciphertext);
+ case self::ENCRYPTION_PKCS1:
+ return $this->rsaes_pkcs1_v1_5_decrypt($ciphertext);
+ //case self::ENCRYPTION_OAEP:
+ default:
+ return $this->rsaes_oaep_decrypt($ciphertext);
+ }
+ }
+
+ /**
+ * Returns the public key
+ *
+ * @return mixed
+ */
+ public function getPublicKey()
+ {
+ $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey');
+ if (empty($this->modulus) || empty($this->publicExponent)) {
+ throw new \RuntimeException('Public key components not found');
+ }
+
+ $key = $type::savePublicKey($this->modulus, $this->publicExponent);
+ return RSA::loadFormat('PKCS8', $key)
+ ->withHash($this->hash->getHash())
+ ->withMGFHash($this->mgfHash->getHash())
+ ->withSaltLength($this->sLen)
+ ->withLabel($this->label)
+ ->withPadding($this->signaturePadding | $this->encryptionPadding);
+ }
+
+ /**
+ * Returns the private key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin(
+ 'Keys',
+ $type,
+ empty($this->primes) ? 'savePublicKey' : 'savePrivateKey'
+ );
+
+ if ($type == PSS::class) {
+ if ($this->signaturePadding == self::SIGNATURE_PSS) {
+ $options += [
+ 'hash' => $this->hash->getHash(),
+ 'MGFHash' => $this->mgfHash->getHash(),
+ 'saltLength' => $this->getSaltLength()
+ ];
+ } else {
+ throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS');
+ }
+ }
+
+ if (empty($this->primes)) {
+ return $type::savePublicKey($this->modulus, $this->exponent, $options);
+ }
+
+ return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password, $options);
+
+ /*
+ $key = $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password, $options);
+ if ($key !== false || count($this->primes) == 2) {
+ return $key;
+ }
+
+ $nSize = $this->getSize() >> 1;
+
+ $primes = [1 => clone self::$one, clone self::$one];
+ $i = 1;
+ foreach ($this->primes as $prime) {
+ $primes[$i] = $primes[$i]->multiply($prime);
+ if ($primes[$i]->getLength() >= $nSize) {
+ $i++;
+ }
+ }
+
+ $exponents = [];
+ $coefficients = [2 => $primes[2]->modInverse($primes[1])];
+
+ foreach ($primes as $i => $prime) {
+ $temp = $prime->subtract(self::$one);
+ $exponents[$i] = $this->modulus->modInverse($temp);
+ }
+
+ return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $primes, $exponents, $coefficients, $this->password, $options);
+ */
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA/PublicKey.php b/Sources/Phpseclib/Crypt/RSA/PublicKey.php
new file mode 100644
index 0000000000..ff80ae79c1
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA/PublicKey.php
@@ -0,0 +1,513 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Crypt\RSA;
+use phpseclib3\Crypt\RSA\Formats\Keys\PSS;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps\DigestInfo;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Raw RSA Key Handler
+ *
+ * @author Jim Wigginton
+ */
+final class PublicKey extends RSA implements Common\PublicKey
+{
+ use Common\Traits\Fingerprint;
+
+ /**
+ * Exponentiate
+ *
+ * @param BigInteger $x
+ * @return BigInteger
+ */
+ private function exponentiate(BigInteger $x)
+ {
+ return $x->modPow($this->exponent, $this->modulus);
+ }
+
+ /**
+ * RSAVP1
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}.
+ *
+ * @param BigInteger $s
+ * @return bool|BigInteger
+ */
+ private function rsavp1($s)
+ {
+ if ($s->compare(self::$zero) < 0 || $s->compare($this->modulus) > 0) {
+ return false;
+ }
+ return $this->exponentiate($s);
+ }
+
+ /**
+ * RSASSA-PKCS1-V1_5-VERIFY
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}.
+ *
+ * @param string $m
+ * @param string $s
+ * @throws \LengthException if the RSA modulus is too short
+ * @return bool
+ */
+ private function rsassa_pkcs1_v1_5_verify($m, $s)
+ {
+ // Length checking
+
+ if (strlen($s) != $this->k) {
+ return false;
+ }
+
+ // RSA verification
+
+ $s = $this->os2ip($s);
+ $m2 = $this->rsavp1($s);
+ if ($m2 === false) {
+ return false;
+ }
+ $em = $this->i2osp($m2, $this->k);
+ if ($em === false) {
+ return false;
+ }
+
+ // EMSA-PKCS1-v1_5 encoding
+
+ $exception = false;
+
+ // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus
+ // too short" and stop.
+ try {
+ $em2 = $this->emsa_pkcs1_v1_5_encode($m, $this->k);
+ $r1 = hash_equals($em, $em2);
+ } catch (\LengthException $e) {
+ $exception = true;
+ }
+
+ try {
+ $em3 = $this->emsa_pkcs1_v1_5_encode_without_null($m, $this->k);
+ $r2 = hash_equals($em, $em3);
+ } catch (\LengthException $e) {
+ $exception = true;
+ } catch (UnsupportedAlgorithmException $e) {
+ $r2 = false;
+ }
+
+ if ($exception) {
+ throw new \LengthException('RSA modulus too short');
+ }
+
+ // Compare
+ return $r1 || $r2;
+ }
+
+ /**
+ * RSASSA-PKCS1-V1_5-VERIFY (relaxed matching)
+ *
+ * Per {@link http://tools.ietf.org/html/rfc3447#page-43 RFC3447#page-43} PKCS1 v1.5
+ * specified the use BER encoding rather than DER encoding that PKCS1 v2.0 specified.
+ * This means that under rare conditions you can have a perfectly valid v1.5 signature
+ * that fails to validate with _rsassa_pkcs1_v1_5_verify(). PKCS1 v2.1 also recommends
+ * that if you're going to validate these types of signatures you "should indicate
+ * whether the underlying BER encoding is a DER encoding and hence whether the signature
+ * is valid with respect to the specification given in [PKCS1 v2.0+]". so if you do
+ * $rsa->getLastPadding() and get RSA::PADDING_RELAXED_PKCS1 back instead of
+ * RSA::PADDING_PKCS1... that means BER encoding was used.
+ *
+ * @param string $m
+ * @param string $s
+ * @return bool
+ */
+ private function rsassa_pkcs1_v1_5_relaxed_verify($m, $s)
+ {
+ // Length checking
+
+ if (strlen($s) != $this->k) {
+ return false;
+ }
+
+ // RSA verification
+
+ $s = $this->os2ip($s);
+ $m2 = $this->rsavp1($s);
+ if ($m2 === false) {
+ return false;
+ }
+ $em = $this->i2osp($m2, $this->k);
+ if ($em === false) {
+ return false;
+ }
+
+ if (Strings::shift($em, 2) != "\0\1") {
+ return false;
+ }
+
+ $em = ltrim($em, "\xFF");
+ if (Strings::shift($em) != "\0") {
+ return false;
+ }
+
+ $decoded = ASN1::decodeBER($em);
+ if (!is_array($decoded) || empty($decoded[0]) || strlen($em) > $decoded[0]['length']) {
+ return false;
+ }
+
+ static $oids;
+ if (!isset($oids)) {
+ $oids = [
+ 'md2' => '1.2.840.113549.2.2',
+ 'md4' => '1.2.840.113549.2.4', // from PKCS1 v1.5
+ 'md5' => '1.2.840.113549.2.5',
+ 'id-sha1' => '1.3.14.3.2.26',
+ 'id-sha256' => '2.16.840.1.101.3.4.2.1',
+ 'id-sha384' => '2.16.840.1.101.3.4.2.2',
+ 'id-sha512' => '2.16.840.1.101.3.4.2.3',
+ // from PKCS1 v2.2
+ 'id-sha224' => '2.16.840.1.101.3.4.2.4',
+ 'id-sha512/224' => '2.16.840.1.101.3.4.2.5',
+ 'id-sha512/256' => '2.16.840.1.101.3.4.2.6',
+ ];
+ ASN1::loadOIDs($oids);
+ }
+
+ $decoded = ASN1::asn1map($decoded[0], DigestInfo::MAP);
+ if (!isset($decoded) || $decoded === false) {
+ return false;
+ }
+
+ if (!isset($oids[$decoded['digestAlgorithm']['algorithm']])) {
+ return false;
+ }
+
+ if (isset($decoded['digestAlgorithm']['parameters']) && $decoded['digestAlgorithm']['parameters'] !== ['null' => '']) {
+ return false;
+ }
+
+ $hash = $decoded['digestAlgorithm']['algorithm'];
+ $hash = substr($hash, 0, 3) == 'id-' ?
+ substr($hash, 3) :
+ $hash;
+ $hash = new Hash($hash);
+ $em = $hash->hash($m);
+ $em2 = $decoded['digest'];
+
+ return hash_equals($em, $em2);
+ }
+
+ /**
+ * EMSA-PSS-VERIFY
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}.
+ *
+ * @param string $m
+ * @param string $em
+ * @param int $emBits
+ * @return string
+ */
+ private function emsa_pss_verify($m, $em, $emBits)
+ {
+ // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
+ // be output.
+
+ $emLen = ($emBits + 7) >> 3; // ie. ceil($emBits / 8);
+ $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
+
+ $mHash = $this->hash->hash($m);
+ if ($emLen < $this->hLen + $sLen + 2) {
+ return false;
+ }
+
+ if ($em[strlen($em) - 1] != chr(0xBC)) {
+ return false;
+ }
+
+ $maskedDB = substr($em, 0, -$this->hLen - 1);
+ $h = substr($em, -$this->hLen - 1, $this->hLen);
+ $temp = chr(0xFF << ($emBits & 7));
+ if ((~$maskedDB[0] & $temp) != $temp) {
+ return false;
+ }
+ $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1);
+ $db = $maskedDB ^ $dbMask;
+ $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
+ $temp = $emLen - $this->hLen - $sLen - 2;
+ if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) {
+ return false;
+ }
+ $salt = substr($db, $temp + 1); // should be $sLen long
+ $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
+ $h2 = $this->hash->hash($m2);
+ return hash_equals($h, $h2);
+ }
+
+ /**
+ * RSASSA-PSS-VERIFY
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}.
+ *
+ * @param string $m
+ * @param string $s
+ * @return bool|string
+ */
+ private function rsassa_pss_verify($m, $s)
+ {
+ // Length checking
+
+ if (strlen($s) != $this->k) {
+ return false;
+ }
+
+ // RSA verification
+
+ $modBits = strlen($this->modulus->toBits());
+
+ $s2 = $this->os2ip($s);
+ $m2 = $this->rsavp1($s2);
+ $em = $this->i2osp($m2, $this->k);
+ if ($em === false) {
+ return false;
+ }
+
+ // EMSA-PSS verification
+
+ return $this->emsa_pss_verify($m, $em, $modBits - 1);
+ }
+
+ /**
+ * Verifies a signature
+ *
+ * @see self::sign()
+ * @param string $message
+ * @param string $signature
+ * @return bool
+ */
+ public function verify($message, $signature)
+ {
+ switch ($this->signaturePadding) {
+ case self::SIGNATURE_RELAXED_PKCS1:
+ return $this->rsassa_pkcs1_v1_5_relaxed_verify($message, $signature);
+ case self::SIGNATURE_PKCS1:
+ return $this->rsassa_pkcs1_v1_5_verify($message, $signature);
+ //case self::SIGNATURE_PSS:
+ default:
+ return $this->rsassa_pss_verify($message, $signature);
+ }
+ }
+
+ /**
+ * RSAES-PKCS1-V1_5-ENCRYPT
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}.
+ *
+ * @param string $m
+ * @param bool $pkcs15_compat optional
+ * @throws \LengthException if strlen($m) > $this->k - 11
+ * @return bool|string
+ */
+ private function rsaes_pkcs1_v1_5_encrypt($m, $pkcs15_compat = false)
+ {
+ $mLen = strlen($m);
+
+ // Length checking
+
+ if ($mLen > $this->k - 11) {
+ throw new \LengthException('Message too long');
+ }
+
+ // EME-PKCS1-v1_5 encoding
+
+ $psLen = $this->k - $mLen - 3;
+ $ps = '';
+ while (strlen($ps) != $psLen) {
+ $temp = Random::string($psLen - strlen($ps));
+ $temp = str_replace("\x00", '', $temp);
+ $ps .= $temp;
+ }
+ $type = 2;
+ $em = chr(0) . chr($type) . $ps . chr(0) . $m;
+
+ // RSA encryption
+ $m = $this->os2ip($em);
+ $c = $this->rsaep($m);
+ $c = $this->i2osp($c, $this->k);
+
+ // Output the ciphertext C
+
+ return $c;
+ }
+
+ /**
+ * RSAES-OAEP-ENCRYPT
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and
+ * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}.
+ *
+ * @param string $m
+ * @throws \LengthException if strlen($m) > $this->k - 2 * $this->hLen - 2
+ * @return string
+ */
+ private function rsaes_oaep_encrypt($m)
+ {
+ $mLen = strlen($m);
+
+ // Length checking
+
+ // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
+ // be output.
+
+ if ($mLen > $this->k - 2 * $this->hLen - 2) {
+ throw new \LengthException('Message too long');
+ }
+
+ // EME-OAEP encoding
+
+ $lHash = $this->hash->hash($this->label);
+ $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2);
+ $db = $lHash . $ps . chr(1) . $m;
+ $seed = Random::string($this->hLen);
+ $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1);
+ $maskedDB = $db ^ $dbMask;
+ $seedMask = $this->mgf1($maskedDB, $this->hLen);
+ $maskedSeed = $seed ^ $seedMask;
+ $em = chr(0) . $maskedSeed . $maskedDB;
+
+ // RSA encryption
+
+ $m = $this->os2ip($em);
+ $c = $this->rsaep($m);
+ $c = $this->i2osp($c, $this->k);
+
+ // Output the ciphertext C
+
+ return $c;
+ }
+
+ /**
+ * RSAEP
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}.
+ *
+ * @param BigInteger $m
+ * @return bool|BigInteger
+ */
+ private function rsaep($m)
+ {
+ if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) {
+ throw new \OutOfRangeException('Message representative out of range');
+ }
+ return $this->exponentiate($m);
+ }
+
+ /**
+ * Raw Encryption / Decryption
+ *
+ * Doesn't use padding and is not recommended.
+ *
+ * @param string $m
+ * @return bool|string
+ * @throws \LengthException if strlen($m) > $this->k
+ */
+ private function raw_encrypt($m)
+ {
+ if (strlen($m) > $this->k) {
+ throw new \LengthException('Message too long');
+ }
+
+ $temp = $this->os2ip($m);
+ $temp = $this->rsaep($temp);
+ return $this->i2osp($temp, $this->k);
+ }
+
+ /**
+ * Encryption
+ *
+ * Both self::PADDING_OAEP and self::PADDING_PKCS1 both place limits on how long $plaintext can be.
+ * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will
+ * be concatenated together.
+ *
+ * @see self::decrypt()
+ * @param string $plaintext
+ * @return bool|string
+ * @throws \LengthException if the RSA modulus is too short
+ */
+ public function encrypt($plaintext)
+ {
+ switch ($this->encryptionPadding) {
+ case self::ENCRYPTION_NONE:
+ return $this->raw_encrypt($plaintext);
+ case self::ENCRYPTION_PKCS1:
+ return $this->rsaes_pkcs1_v1_5_encrypt($plaintext);
+ //case self::ENCRYPTION_OAEP:
+ default:
+ return $this->rsaes_oaep_encrypt($plaintext);
+ }
+ }
+
+ /**
+ * Returns the public key
+ *
+ * The public key is only returned under two circumstances - if the private key had the public key embedded within it
+ * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this
+ * function won't return it since this library, for the most part, doesn't distinguish between public and private keys.
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return mixed
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePublicKey');
+
+ if ($type == PSS::class) {
+ if ($this->signaturePadding == self::SIGNATURE_PSS) {
+ $options += [
+ 'hash' => $this->hash->getHash(),
+ 'MGFHash' => $this->mgfHash->getHash(),
+ 'saltLength' => $this->getSaltLength()
+ ];
+ } else {
+ throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS');
+ }
+ }
+
+ return $type::savePublicKey($this->modulus, $this->publicExponent, $options);
+ }
+
+ /**
+ * Converts a public key to a private key
+ *
+ * @return RSA
+ */
+ public function asPrivateKey()
+ {
+ $new = new PrivateKey();
+ $new->exponent = $this->exponent;
+ $new->modulus = $this->modulus;
+ $new->k = $this->k;
+ $new->format = $this->format;
+ return $new
+ ->withHash($this->hash->getHash())
+ ->withMGFHash($this->mgfHash->getHash())
+ ->withSaltLength($this->sLen)
+ ->withLabel($this->label)
+ ->withPadding($this->signaturePadding | $this->encryptionPadding);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/RSA/index.php b/Sources/Phpseclib/Crypt/RSA/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/RSA/index.php
@@ -0,0 +1,8 @@
+
+ *
+ *
+ *
+ * @author Jim Wigginton
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt;
+
+/**
+ * Pure-PHP Random Number Generator
+ *
+ * @author Jim Wigginton
+ */
+abstract class Random
+{
+ /**
+ * Generate a random string.
+ *
+ * Although microoptimizations are generally discouraged as they impair readability this function is ripe with
+ * microoptimizations because this function has the potential of being called a huge number of times.
+ * eg. for RSA key generation.
+ *
+ * @param int $length
+ * @throws \RuntimeException if a symmetric cipher is needed but not loaded
+ * @return string
+ */
+ public static function string($length)
+ {
+ if (!$length) {
+ return '';
+ }
+
+ try {
+ return random_bytes($length);
+ } catch (\Exception $e) {
+ // random_compat will throw an Exception, which in PHP 5 does not implement Throwable
+ } catch (\Throwable $e) {
+ // If a sufficient source of randomness is unavailable, random_bytes() will throw an
+ // object that implements the Throwable interface (Exception, TypeError, Error).
+ // We don't actually need to do anything here. The string() method should just continue
+ // as normal. Note, however, that if we don't have a sufficient source of randomness for
+ // random_bytes(), most of the other calls here will fail too, so we'll end up using
+ // the PHP implementation.
+ }
+ // at this point we have no choice but to use a pure-PHP CSPRNG
+
+ // cascade entropy across multiple PHP instances by fixing the session and collecting all
+ // environmental variables, including the previous session data and the current session
+ // data.
+ //
+ // mt_rand seeds itself by looking at the PID and the time, both of which are (relatively)
+ // easy to guess at. linux uses mouse clicks, keyboard timings, etc, as entropy sources, but
+ // PHP isn't low level to be able to use those as sources and on a web server there's not likely
+ // going to be a ton of keyboard or mouse action. web servers do have one thing that we can use
+ // however, a ton of people visiting the website. obviously you don't want to base your seeding
+ // solely on parameters a potential attacker sends but (1) not everything in $_SERVER is controlled
+ // by the user and (2) this isn't just looking at the data sent by the current user - it's based
+ // on the data sent by all users. one user requests the page and a hash of their info is saved.
+ // another user visits the page and the serialization of their data is utilized along with the
+ // server environment stuff and a hash of the previous http request data (which itself utilizes
+ // a hash of the session data before that). certainly an attacker should be assumed to have
+ // full control over his own http requests. he, however, is not going to have control over
+ // everyone's http requests.
+ static $crypto = false, $v;
+ if ($crypto === false) {
+ // save old session data
+ $old_session_id = session_id();
+ $old_use_cookies = ini_get('session.use_cookies');
+ $old_session_cache_limiter = session_cache_limiter();
+ $_OLD_SESSION = isset($_SESSION) ? $_SESSION : false;
+ if ($old_session_id != '') {
+ session_write_close();
+ }
+
+ session_id(1);
+ ini_set('session.use_cookies', 0);
+ session_cache_limiter('');
+ session_start();
+
+ $v = (isset($_SERVER) ? self::safe_serialize($_SERVER) : '') .
+ (isset($_POST) ? self::safe_serialize($_POST) : '') .
+ (isset($_GET) ? self::safe_serialize($_GET) : '') .
+ (isset($_COOKIE) ? self::safe_serialize($_COOKIE) : '') .
+ // as of PHP 8.1 $GLOBALS can't be accessed by reference, which eliminates
+ // the need for phpseclib_safe_serialize. see https://wiki.php.net/rfc/restrict_globals_usage
+ // for more info
+ (version_compare(PHP_VERSION, '8.1.0', '>=') ? serialize($GLOBALS) : self::safe_serialize($GLOBALS)) .
+ self::safe_serialize($_SESSION) .
+ self::safe_serialize($_OLD_SESSION);
+ $v = $seed = $_SESSION['seed'] = sha1($v, true);
+ if (!isset($_SESSION['count'])) {
+ $_SESSION['count'] = 0;
+ }
+ $_SESSION['count']++;
+
+ session_write_close();
+
+ // restore old session data
+ if ($old_session_id != '') {
+ session_id($old_session_id);
+ session_start();
+ ini_set('session.use_cookies', $old_use_cookies);
+ session_cache_limiter($old_session_cache_limiter);
+ } else {
+ if ($_OLD_SESSION !== false) {
+ $_SESSION = $_OLD_SESSION;
+ unset($_OLD_SESSION);
+ } else {
+ unset($_SESSION);
+ }
+ }
+
+ // in SSH2 a shared secret and an exchange hash are generated through the key exchange process.
+ // the IV client to server is the hash of that "nonce" with the letter A and for the encryption key it's the letter C.
+ // if the hash doesn't produce enough a key or an IV that's long enough concat successive hashes of the
+ // original hash and the current hash. we'll be emulating that. for more info see the following URL:
+ //
+ // http://tools.ietf.org/html/rfc4253#section-7.2
+ //
+ // see the is_string($crypto) part for an example of how to expand the keys
+ $key = sha1($seed . 'A', true);
+ $iv = sha1($seed . 'C', true);
+
+ // ciphers are used as per the nist.gov link below. also, see this link:
+ //
+ // http://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator#Designs_based_on_cryptographic_primitives
+ switch (true) {
+ case class_exists('\phpseclib3\Crypt\AES'):
+ $crypto = new AES('ctr');
+ break;
+ case class_exists('\phpseclib3\Crypt\Twofish'):
+ $crypto = new Twofish('ctr');
+ break;
+ case class_exists('\phpseclib3\Crypt\Blowfish'):
+ $crypto = new Blowfish('ctr');
+ break;
+ case class_exists('\phpseclib3\Crypt\TripleDES'):
+ $crypto = new TripleDES('ctr');
+ break;
+ case class_exists('\phpseclib3\Crypt\DES'):
+ $crypto = new DES('ctr');
+ break;
+ case class_exists('\phpseclib3\Crypt\RC4'):
+ $crypto = new RC4();
+ break;
+ default:
+ throw new \RuntimeException(__CLASS__ . ' requires at least one symmetric cipher be loaded');
+ }
+
+ $crypto->setKey(substr($key, 0, $crypto->getKeyLength() >> 3));
+ $crypto->setIV(substr($iv, 0, $crypto->getBlockLength() >> 3));
+ $crypto->enableContinuousBuffer();
+ }
+
+ //return $crypto->encrypt(str_repeat("\0", $length));
+
+ // the following is based off of ANSI X9.31:
+ //
+ // http://csrc.nist.gov/groups/STM/cavp/documents/rng/931rngext.pdf
+ //
+ // OpenSSL uses that same standard for it's random numbers:
+ //
+ // http://www.opensource.apple.com/source/OpenSSL/OpenSSL-38/openssl/fips-1.0/rand/fips_rand.c
+ // (do a search for "ANS X9.31 A.2.4")
+ $result = '';
+ while (strlen($result) < $length) {
+ $i = $crypto->encrypt(microtime()); // strlen(microtime()) == 21
+ $r = $crypto->encrypt($i ^ $v); // strlen($v) == 20
+ $v = $crypto->encrypt($r ^ $i); // strlen($r) == 20
+ $result .= $r;
+ }
+
+ return substr($result, 0, $length);
+ }
+
+ /**
+ * Safely serialize variables
+ *
+ * If a class has a private __sleep() it'll emit a warning
+ * @return mixed
+ * @param mixed $arr
+ */
+ private static function safe_serialize(&$arr)
+ {
+ if (is_object($arr)) {
+ return '';
+ }
+ if (!is_array($arr)) {
+ return serialize($arr);
+ }
+ // prevent circular array recursion
+ if (isset($arr['__phpseclib_marker'])) {
+ return '';
+ }
+ $safearr = [];
+ $arr['__phpseclib_marker'] = true;
+ foreach (array_keys($arr) as $key) {
+ // do not recurse on the '__phpseclib_marker' key itself, for smaller memory usage
+ if ($key !== '__phpseclib_marker') {
+ $safearr[$key] = self::safe_serialize($arr[$key]);
+ }
+ }
+ unset($arr['__phpseclib_marker']);
+ return serialize($safearr);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Rijndael.php b/Sources/Phpseclib/Crypt/Rijndael.php
new file mode 100644
index 0000000000..5ba7cf7fe0
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Rijndael.php
@@ -0,0 +1,1036 @@
+
+ * setKey('abcdefghijklmnop');
+ *
+ * $size = 10 * 1024;
+ * $plaintext = '';
+ * for ($i = 0; $i < $size; $i++) {
+ * $plaintext.= 'a';
+ * }
+ *
+ * echo $rijndael->decrypt($rijndael->encrypt($plaintext));
+ * ?>
+ *
+ *
+ * @author Jim Wigginton
+ * @copyright 2008 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\BlockCipher;
+use phpseclib3\Exception\BadDecryptionException;
+use phpseclib3\Exception\BadModeException;
+use phpseclib3\Exception\InconsistentSetupException;
+use phpseclib3\Exception\InsufficientSetupException;
+
+/**
+ * Pure-PHP implementation of Rijndael.
+ *
+ * @author Jim Wigginton
+ */
+class Rijndael extends BlockCipher
+{
+ /**
+ * The mcrypt specific name of the cipher
+ *
+ * Mcrypt is useable for 128/192/256-bit $block_size/$key_length. For 160/224 not.
+ * \phpseclib3\Crypt\Rijndael determines automatically whether mcrypt is useable
+ * or not for the current $block_size/$key_length.
+ * In case of, $cipher_name_mcrypt will be set dynamically at run time accordingly.
+ *
+ * @see Common\SymmetricKey::cipher_name_mcrypt
+ * @see Common\SymmetricKey::engine
+ * @see self::isValidEngine()
+ * @var string
+ */
+ protected $cipher_name_mcrypt = 'rijndael-128';
+
+ /**
+ * The Key Schedule
+ *
+ * @see self::setup()
+ * @var array
+ */
+ private $w;
+
+ /**
+ * The Inverse Key Schedule
+ *
+ * @see self::setup()
+ * @var array
+ */
+ private $dw;
+
+ /**
+ * The Block Length divided by 32
+ *
+ * {@internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size
+ * because the encryption / decryption / key schedule creation requires this number and not $block_size. We could
+ * derive this from $block_size 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 self::setBlockLength()
+ * @var int
+ */
+ private $Nb = 4;
+
+ /**
+ * 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 self::setKeyLength()
+ * @var int
+ */
+ protected $key_length = 16;
+
+ /**
+ * The Key Length divided by 32
+ *
+ * @see self::setKeyLength()
+ * @var int
+ * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4
+ */
+ private $Nk = 4;
+
+ /**
+ * The Number of Rounds
+ *
+ * {@internal The max value is 14, the min value is 10.}
+ *
+ * @var int
+ */
+ private $Nr;
+
+ /**
+ * Shift offsets
+ *
+ * @var array
+ */
+ private $c;
+
+ /**
+ * Holds the last used key- and block_size information
+ *
+ * @var array
+ */
+ private $kl;
+
+ /**
+ * 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 BadModeException('Block ciphers cannot be ran in stream mode');
+ }
+ }
+
+ /**
+ * Sets the key length.
+ *
+ * Valid key lengths are 128, 160, 192, 224, and 256.
+ *
+ * Note: phpseclib extends Rijndael (and AES) for using 160- and 224-bit keys but they are officially not defined
+ * and the most (if not all) implementations are not able using 160/224-bit keys but round/pad them up to
+ * 192/256 bits as, for example, mcrypt will do.
+ *
+ * That said, if you want be compatible with other Rijndael and AES implementations,
+ * you should not setKeyLength(160) or setKeyLength(224).
+ *
+ * Additional: In case of 160- and 224-bit keys, phpseclib will/can, for that reason, not use
+ * the mcrypt php extension, even if available.
+ * This results then in slower encryption.
+ *
+ * @throws \LengthException if the key length is invalid
+ * @param int $length
+ */
+ public function setKeyLength($length)
+ {
+ switch ($length) {
+ case 128:
+ case 160:
+ case 192:
+ case 224:
+ case 256:
+ $this->key_length = $length >> 3;
+ break;
+ default:
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes 128, 160, 192, 224 or 256 bits are supported');
+ }
+
+ parent::setKeyLength($length);
+ }
+
+ /**
+ * Sets the key.
+ *
+ * Rijndael supports five different key lengths
+ *
+ * @see setKeyLength()
+ * @param string $key
+ * @throws \LengthException if the key length isn't supported
+ */
+ public function setKey($key)
+ {
+ switch (strlen($key)) {
+ case 16:
+ case 20:
+ case 24:
+ case 28:
+ case 32:
+ break;
+ default:
+ throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 20, 24, 28 or 32 are supported');
+ }
+
+ parent::setKey($key);
+ }
+
+ /**
+ * Sets the block length
+ *
+ * Valid block lengths are 128, 160, 192, 224, and 256.
+ *
+ * @param int $length
+ */
+ public function setBlockLength($length)
+ {
+ switch ($length) {
+ case 128:
+ case 160:
+ case 192:
+ case 224:
+ case 256:
+ break;
+ default:
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes 128, 160, 192, 224 or 256 bits are supported');
+ }
+
+ $this->Nb = $length >> 5;
+ $this->block_size = $length >> 3;
+ $this->changed = $this->nonIVChanged = true;
+ $this->setEngine();
+ }
+
+ /**
+ * 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:
+ return function_exists('sodium_crypto_aead_aes256gcm_is_available') &&
+ sodium_crypto_aead_aes256gcm_is_available() &&
+ $this->mode == self::MODE_GCM &&
+ $this->key_length == 32 &&
+ $this->nonce && strlen($this->nonce) == 12 &&
+ $this->block_size == 16;
+ case self::ENGINE_OPENSSL_GCM:
+ if (!extension_loaded('openssl')) {
+ return false;
+ }
+ $methods = openssl_get_cipher_methods();
+ return $this->mode == self::MODE_GCM &&
+ version_compare(PHP_VERSION, '7.1.0', '>=') &&
+ in_array('aes-' . $this->getKeyLength() . '-gcm', $methods) &&
+ $this->block_size == 16;
+ case self::ENGINE_OPENSSL:
+ if ($this->block_size != 16) {
+ return false;
+ }
+ $this->cipher_name_openssl_ecb = 'aes-' . ($this->key_length << 3) . '-ecb';
+ $this->cipher_name_openssl = 'aes-' . ($this->key_length << 3) . '-' . $this->openssl_translate_mode();
+ break;
+ case self::ENGINE_MCRYPT:
+ $this->cipher_name_mcrypt = 'rijndael-' . ($this->block_size << 3);
+ if ($this->key_length % 8) { // is it a 160/224-bit key?
+ // mcrypt is not usable for them, only for 128/192/256-bit keys
+ return false;
+ }
+ }
+
+ return parent::isValidEngineHelper($engine);
+ }
+
+ /**
+ * Encrypts a block
+ *
+ * @param string $in
+ * @return string
+ */
+ protected function encryptBlock($in)
+ {
+ static $tables;
+ if (empty($tables)) {
+ $tables = &$this->getTables();
+ }
+ $t0 = $tables[0];
+ $t1 = $tables[1];
+ $t2 = $tables[2];
+ $t3 = $tables[3];
+ $sbox = $tables[4];
+
+ $state = [];
+ $words = unpack('N*', $in);
+
+ $c = $this->c;
+ $w = $this->w;
+ $Nb = $this->Nb;
+ $Nr = $this->Nr;
+
+ // addRoundKey
+ $wc = $Nb - 1;
+ foreach ($words as $word) {
+ $state[] = $word ^ $w[++$wc];
+ }
+
+ // fips-197.pdf#page=19, "Figure 5. Pseudo Code for the Cipher", states that this loop has four components -
+ // subBytes, shiftRows, mixColumns, and addRoundKey. fips-197.pdf#page=30, "Implementation Suggestions Regarding
+ // Various Platforms" suggests that performs enhanced implementations are described in Rijndael-ammended.pdf.
+ // Rijndael-ammended.pdf#page=20, "Implementation aspects / 32-bit processor", discusses such an optimization.
+ // Unfortunately, the description given there is not quite correct. Per aes.spec.v316.pdf#page=19 [1],
+ // equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well.
+
+ // [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf
+ $temp = [];
+ for ($round = 1; $round < $Nr; ++$round) {
+ $i = 0; // $c[0] == 0
+ $j = $c[1];
+ $k = $c[2];
+ $l = $c[3];
+
+ while ($i < $Nb) {
+ $temp[$i] = $t0[$state[$i] >> 24 & 0x000000FF] ^
+ $t1[$state[$j] >> 16 & 0x000000FF] ^
+ $t2[$state[$k] >> 8 & 0x000000FF] ^
+ $t3[$state[$l] & 0x000000FF] ^
+ $w[++$wc];
+ ++$i;
+ $j = ($j + 1) % $Nb;
+ $k = ($k + 1) % $Nb;
+ $l = ($l + 1) % $Nb;
+ }
+ $state = $temp;
+ }
+
+ // subWord
+ for ($i = 0; $i < $Nb; ++$i) {
+ $state[$i] = $sbox[$state[$i] & 0x000000FF] |
+ ($sbox[$state[$i] >> 8 & 0x000000FF] << 8) |
+ ($sbox[$state[$i] >> 16 & 0x000000FF] << 16) |
+ ($sbox[$state[$i] >> 24 & 0x000000FF] << 24);
+ }
+
+ // shiftRows + addRoundKey
+ $i = 0; // $c[0] == 0
+ $j = $c[1];
+ $k = $c[2];
+ $l = $c[3];
+ while ($i < $Nb) {
+ $temp[$i] = ($state[$i] & intval(0xFF000000)) ^
+ ($state[$j] & 0x00FF0000) ^
+ ($state[$k] & 0x0000FF00) ^
+ ($state[$l] & 0x000000FF) ^
+ $w[$i];
+ ++$i;
+ $j = ($j + 1) % $Nb;
+ $k = ($k + 1) % $Nb;
+ $l = ($l + 1) % $Nb;
+ }
+
+ return pack('N*', ...$temp);
+ }
+
+ /**
+ * Decrypts a block
+ *
+ * @param string $in
+ * @return string
+ */
+ protected function decryptBlock($in)
+ {
+ static $invtables;
+ if (empty($invtables)) {
+ $invtables = &$this->getInvTables();
+ }
+ $dt0 = $invtables[0];
+ $dt1 = $invtables[1];
+ $dt2 = $invtables[2];
+ $dt3 = $invtables[3];
+ $isbox = $invtables[4];
+
+ $state = [];
+ $words = unpack('N*', $in);
+
+ $c = $this->c;
+ $dw = $this->dw;
+ $Nb = $this->Nb;
+ $Nr = $this->Nr;
+
+ // addRoundKey
+ $wc = $Nb - 1;
+ foreach ($words as $word) {
+ $state[] = $word ^ $dw[++$wc];
+ }
+
+ $temp = [];
+ for ($round = $Nr - 1; $round > 0; --$round) {
+ $i = 0; // $c[0] == 0
+ $j = $Nb - $c[1];
+ $k = $Nb - $c[2];
+ $l = $Nb - $c[3];
+
+ while ($i < $Nb) {
+ $temp[$i] = $dt0[$state[$i] >> 24 & 0x000000FF] ^
+ $dt1[$state[$j] >> 16 & 0x000000FF] ^
+ $dt2[$state[$k] >> 8 & 0x000000FF] ^
+ $dt3[$state[$l] & 0x000000FF] ^
+ $dw[++$wc];
+ ++$i;
+ $j = ($j + 1) % $Nb;
+ $k = ($k + 1) % $Nb;
+ $l = ($l + 1) % $Nb;
+ }
+ $state = $temp;
+ }
+
+ // invShiftRows + invSubWord + addRoundKey
+ $i = 0; // $c[0] == 0
+ $j = $Nb - $c[1];
+ $k = $Nb - $c[2];
+ $l = $Nb - $c[3];
+
+ while ($i < $Nb) {
+ $word = ($state[$i] & intval(0xFF000000)) |
+ ($state[$j] & 0x00FF0000) |
+ ($state[$k] & 0x0000FF00) |
+ ($state[$l] & 0x000000FF);
+
+ $temp[$i] = $dw[$i] ^ ($isbox[$word & 0x000000FF] |
+ ($isbox[$word >> 8 & 0x000000FF] << 8) |
+ ($isbox[$word >> 16 & 0x000000FF] << 16) |
+ ($isbox[$word >> 24 & 0x000000FF] << 24));
+ ++$i;
+ $j = ($j + 1) % $Nb;
+ $k = ($k + 1) % $Nb;
+ $l = ($l + 1) % $Nb;
+ }
+
+ return pack('N*', ...$temp);
+ }
+
+ /**
+ * 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;
+ }
+
+ parent::setup();
+
+ if (is_string($this->iv) && strlen($this->iv) != $this->block_size) {
+ throw new InconsistentSetupException('The IV length (' . strlen($this->iv) . ') does not match the block size (' . $this->block_size . ')');
+ }
+ }
+
+ /**
+ * Setup the key (expansion)
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::setupKey()
+ */
+ protected function setupKey()
+ {
+ // Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field.
+ // See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse
+ static $rcon;
+
+ if (!isset($rcon)) {
+ $rcon = [0,
+ 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
+ 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000,
+ 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000,
+ 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000,
+ 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000,
+ 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000
+ ];
+ $rcon = array_map('intval', $rcon);
+ }
+
+ if (isset($this->kl['key']) && $this->key === $this->kl['key'] && $this->key_length === $this->kl['key_length'] && $this->block_size === $this->kl['block_size']) {
+ // already expanded
+ return;
+ }
+ $this->kl = ['key' => $this->key, 'key_length' => $this->key_length, 'block_size' => $this->block_size];
+
+ $this->Nk = $this->key_length >> 2;
+ // see Rijndael-ammended.pdf#page=44
+ $this->Nr = max($this->Nk, $this->Nb) + 6;
+
+ // shift offsets for Nb = 5, 7 are defined in Rijndael-ammended.pdf#page=44,
+ // "Table 8: Shift offsets in Shiftrow for the alternative block lengths"
+ // shift offsets for Nb = 4, 6, 8 are defined in Rijndael-ammended.pdf#page=14,
+ // "Table 2: Shift offsets for different block lengths"
+ switch ($this->Nb) {
+ case 4:
+ case 5:
+ case 6:
+ $this->c = [0, 1, 2, 3];
+ break;
+ case 7:
+ $this->c = [0, 1, 2, 4];
+ break;
+ case 8:
+ $this->c = [0, 1, 3, 4];
+ }
+
+ $w = array_values(unpack('N*words', $this->key));
+
+ $length = $this->Nb * ($this->Nr + 1);
+ for ($i = $this->Nk; $i < $length; $i++) {
+ $temp = $w[$i - 1];
+ if ($i % $this->Nk == 0) {
+ // according to , "the size of an integer is platform-dependent".
+ // on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine,
+ // 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and'
+ // with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is.
+ $temp = (($temp << 8) & intval(0xFFFFFF00)) | (($temp >> 24) & 0x000000FF); // rotWord
+ $temp = $this->subWord($temp) ^ $rcon[$i / $this->Nk];
+ } elseif ($this->Nk > 6 && $i % $this->Nk == 4) {
+ $temp = $this->subWord($temp);
+ }
+ $w[$i] = $w[$i - $this->Nk] ^ $temp;
+ }
+
+ // convert the key schedule from a vector of $Nb * ($Nr + 1) length to a matrix with $Nr + 1 rows and $Nb columns
+ // and generate the inverse key schedule. more specifically,
+ // according to (section 5.3.3),
+ // "The key expansion for the Inverse Cipher is defined as follows:
+ // 1. Apply the Key Expansion.
+ // 2. Apply InvMixColumn to all Round Keys except the first and the last one."
+ // also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher"
+ list($dt0, $dt1, $dt2, $dt3) = $this->getInvTables();
+ $temp = $this->w = $this->dw = [];
+ for ($i = $row = $col = 0; $i < $length; $i++, $col++) {
+ if ($col == $this->Nb) {
+ if ($row == 0) {
+ $this->dw[0] = $this->w[0];
+ } else {
+ // subWord + invMixColumn + invSubWord = invMixColumn
+ $j = 0;
+ while ($j < $this->Nb) {
+ $dw = $this->subWord($this->w[$row][$j]);
+ $temp[$j] = $dt0[$dw >> 24 & 0x000000FF] ^
+ $dt1[$dw >> 16 & 0x000000FF] ^
+ $dt2[$dw >> 8 & 0x000000FF] ^
+ $dt3[$dw & 0x000000FF];
+ $j++;
+ }
+ $this->dw[$row] = $temp;
+ }
+
+ $col = 0;
+ $row++;
+ }
+ $this->w[$row][$col] = $w[$i];
+ }
+
+ $this->dw[$row] = $this->w[$row];
+
+ // Converting to 1-dim key arrays (both ascending)
+ $this->dw = array_reverse($this->dw);
+ $w = array_pop($this->w);
+ $dw = array_pop($this->dw);
+ foreach ($this->w as $r => $wr) {
+ foreach ($wr as $c => $wc) {
+ $w[] = $wc;
+ $dw[] = $this->dw[$r][$c];
+ }
+ }
+ $this->w = $w;
+ $this->dw = $dw;
+ }
+
+ /**
+ * Performs S-Box substitutions
+ *
+ * @return array
+ * @param int $word
+ */
+ private function subWord($word)
+ {
+ static $sbox;
+ if (empty($sbox)) {
+ list(, , , , $sbox) = self::getTables();
+ }
+
+ return $sbox[$word & 0x000000FF] |
+ ($sbox[$word >> 8 & 0x000000FF] << 8) |
+ ($sbox[$word >> 16 & 0x000000FF] << 16) |
+ ($sbox[$word >> 24 & 0x000000FF] << 24);
+ }
+
+ /**
+ * Provides the mixColumns and sboxes tables
+ *
+ * @see self::encryptBlock()
+ * @see self::setupInlineCrypt()
+ * @see self::subWord()
+ * @return array &$tables
+ */
+ protected function &getTables()
+ {
+ static $tables;
+ if (empty($tables)) {
+ // according to (section 5.2.1),
+ // precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so
+ // those are the names we'll use.
+ $t3 = array_map('intval', [
+ // with array_map('intval', ...) we ensure we have only int's and not
+ // some slower floats converted by php automatically on high values
+ 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491,
+ 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC,
+ 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB,
+ 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B,
+ 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83,
+ 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A,
+ 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F,
+ 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA,
+ 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B,
+ 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713,
+ 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6,
+ 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85,
+ 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411,
+ 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B,
+ 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1,
+ 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF,
+ 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E,
+ 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6,
+ 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B,
+ 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD,
+ 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8,
+ 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2,
+ 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049,
+ 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810,
+ 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197,
+ 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F,
+ 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C,
+ 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927,
+ 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733,
+ 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5,
+ 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0,
+ 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C
+ ]);
+
+ foreach ($t3 as $t3i) {
+ $t0[] = (($t3i << 24) & intval(0xFF000000)) | (($t3i >> 8) & 0x00FFFFFF);
+ $t1[] = (($t3i << 16) & intval(0xFFFF0000)) | (($t3i >> 16) & 0x0000FFFF);
+ $t2[] = (($t3i << 8) & intval(0xFFFFFF00)) | (($t3i >> 24) & 0x000000FF);
+ }
+
+ $tables = [
+ // The Precomputed mixColumns tables t0 - t3
+ $t0,
+ $t1,
+ $t2,
+ $t3,
+ // The SubByte S-Box
+ [
+ 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
+ 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
+ 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
+ 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
+ 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
+ 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
+ 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
+ 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
+ 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
+ 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
+ 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
+ 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
+ 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
+ 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
+ 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
+ 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
+ ]
+ ];
+ }
+ return $tables;
+ }
+
+ /**
+ * Provides the inverse mixColumns and inverse sboxes tables
+ *
+ * @see self::decryptBlock()
+ * @see self::setupInlineCrypt()
+ * @see self::setupKey()
+ * @return array &$tables
+ */
+ protected function &getInvTables()
+ {
+ static $tables;
+ if (empty($tables)) {
+ $dt3 = array_map('intval', [
+ 0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B,
+ 0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5,
+ 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B,
+ 0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E,
+ 0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D,
+ 0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9,
+ 0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66,
+ 0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED,
+ 0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4,
+ 0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD,
+ 0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60,
+ 0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79,
+ 0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C,
+ 0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24,
+ 0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C,
+ 0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814,
+ 0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B,
+ 0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084,
+ 0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077,
+ 0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22,
+ 0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F,
+ 0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582,
+ 0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB,
+ 0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF,
+ 0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035,
+ 0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17,
+ 0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46,
+ 0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D,
+ 0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A,
+ 0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678,
+ 0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF,
+ 0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0
+ ]);
+
+ foreach ($dt3 as $dt3i) {
+ $dt0[] = (($dt3i << 24) & intval(0xFF000000)) | (($dt3i >> 8) & 0x00FFFFFF);
+ $dt1[] = (($dt3i << 16) & intval(0xFFFF0000)) | (($dt3i >> 16) & 0x0000FFFF);
+ $dt2[] = (($dt3i << 8) & intval(0xFFFFFF00)) | (($dt3i >> 24) & 0x000000FF);
+ };
+
+ $tables = [
+ // The Precomputed inverse mixColumns tables dt0 - dt3
+ $dt0,
+ $dt1,
+ $dt2,
+ $dt3,
+ // The inverse SubByte S-Box
+ [
+ 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
+ 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
+ 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
+ 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
+ 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
+ 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
+ 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
+ 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
+ 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
+ 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
+ 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
+ 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
+ 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
+ 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
+ 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
+ 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
+ ]
+ ];
+ }
+ return $tables;
+ }
+
+ /**
+ * Setup the performance-optimized function for de/encrypt()
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::setupInlineCrypt()
+ */
+ protected function setupInlineCrypt()
+ {
+ $w = $this->w;
+ $dw = $this->dw;
+ $init_encrypt = '';
+ $init_decrypt = '';
+
+ $Nr = $this->Nr;
+ $Nb = $this->Nb;
+ $c = $this->c;
+
+ // Generating encrypt code:
+ $init_encrypt .= '
+ if (empty($tables)) {
+ $tables = &$this->getTables();
+ }
+ $t0 = $tables[0];
+ $t1 = $tables[1];
+ $t2 = $tables[2];
+ $t3 = $tables[3];
+ $sbox = $tables[4];
+ ';
+
+ $s = 'e';
+ $e = 's';
+ $wc = $Nb - 1;
+
+ // Preround: addRoundKey
+ $encrypt_block = '$in = unpack("N*", $in);' . "\n";
+ for ($i = 0; $i < $Nb; ++$i) {
+ $encrypt_block .= '$s' . $i . ' = $in[' . ($i + 1) . '] ^ ' . $w[++$wc] . ";\n";
+ }
+
+ // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey
+ for ($round = 1; $round < $Nr; ++$round) {
+ list($s, $e) = [$e, $s];
+ for ($i = 0; $i < $Nb; ++$i) {
+ $encrypt_block .=
+ '$' . $e . $i . ' =
+ $t0[($' . $s . $i . ' >> 24) & 0xff] ^
+ $t1[($' . $s . (($i + $c[1]) % $Nb) . ' >> 16) & 0xff] ^
+ $t2[($' . $s . (($i + $c[2]) % $Nb) . ' >> 8) & 0xff] ^
+ $t3[ $' . $s . (($i + $c[3]) % $Nb) . ' & 0xff] ^
+ ' . $w[++$wc] . ";\n";
+ }
+ }
+
+ // Finalround: subWord + shiftRows + addRoundKey
+ for ($i = 0; $i < $Nb; ++$i) {
+ $encrypt_block .=
+ '$' . $e . $i . ' =
+ $sbox[ $' . $e . $i . ' & 0xff] |
+ ($sbox[($' . $e . $i . ' >> 8) & 0xff] << 8) |
+ ($sbox[($' . $e . $i . ' >> 16) & 0xff] << 16) |
+ ($sbox[($' . $e . $i . ' >> 24) & 0xff] << 24);' . "\n";
+ }
+ $encrypt_block .= '$in = pack("N*"' . "\n";
+ for ($i = 0; $i < $Nb; ++$i) {
+ $encrypt_block .= ',
+ ($' . $e . $i . ' & ' . ((int)0xFF000000) . ') ^
+ ($' . $e . (($i + $c[1]) % $Nb) . ' & 0x00FF0000 ) ^
+ ($' . $e . (($i + $c[2]) % $Nb) . ' & 0x0000FF00 ) ^
+ ($' . $e . (($i + $c[3]) % $Nb) . ' & 0x000000FF ) ^
+ ' . $w[$i] . "\n";
+ }
+ $encrypt_block .= ');';
+
+ // Generating decrypt code:
+ $init_decrypt .= '
+ if (empty($invtables)) {
+ $invtables = &$this->getInvTables();
+ }
+ $dt0 = $invtables[0];
+ $dt1 = $invtables[1];
+ $dt2 = $invtables[2];
+ $dt3 = $invtables[3];
+ $isbox = $invtables[4];
+ ';
+
+ $s = 'e';
+ $e = 's';
+ $wc = $Nb - 1;
+
+ // Preround: addRoundKey
+ $decrypt_block = '$in = unpack("N*", $in);' . "\n";
+ for ($i = 0; $i < $Nb; ++$i) {
+ $decrypt_block .= '$s' . $i . ' = $in[' . ($i + 1) . '] ^ ' . $dw[++$wc] . ';' . "\n";
+ }
+
+ // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey
+ for ($round = 1; $round < $Nr; ++$round) {
+ list($s, $e) = [$e, $s];
+ for ($i = 0; $i < $Nb; ++$i) {
+ $decrypt_block .=
+ '$' . $e . $i . ' =
+ $dt0[($' . $s . $i . ' >> 24) & 0xff] ^
+ $dt1[($' . $s . (($Nb + $i - $c[1]) % $Nb) . ' >> 16) & 0xff] ^
+ $dt2[($' . $s . (($Nb + $i - $c[2]) % $Nb) . ' >> 8) & 0xff] ^
+ $dt3[ $' . $s . (($Nb + $i - $c[3]) % $Nb) . ' & 0xff] ^
+ ' . $dw[++$wc] . ";\n";
+ }
+ }
+
+ // Finalround: subWord + shiftRows + addRoundKey
+ for ($i = 0; $i < $Nb; ++$i) {
+ $decrypt_block .=
+ '$' . $e . $i . ' =
+ $isbox[ $' . $e . $i . ' & 0xff] |
+ ($isbox[($' . $e . $i . ' >> 8) & 0xff] << 8) |
+ ($isbox[($' . $e . $i . ' >> 16) & 0xff] << 16) |
+ ($isbox[($' . $e . $i . ' >> 24) & 0xff] << 24);' . "\n";
+ }
+ $decrypt_block .= '$in = pack("N*"' . "\n";
+ for ($i = 0; $i < $Nb; ++$i) {
+ $decrypt_block .= ',
+ ($' . $e . $i . ' & ' . ((int)0xFF000000) . ') ^
+ ($' . $e . (($Nb + $i - $c[1]) % $Nb) . ' & 0x00FF0000 ) ^
+ ($' . $e . (($Nb + $i - $c[2]) % $Nb) . ' & 0x0000FF00 ) ^
+ ($' . $e . (($Nb + $i - $c[3]) % $Nb) . ' & 0x000000FF ) ^
+ ' . $dw[$i] . "\n";
+ }
+ $decrypt_block .= ');';
+
+ $this->inline_crypt = $this->createInlineCryptFunction(
+ [
+ 'init_crypt' => 'static $tables; static $invtables;',
+ 'init_encrypt' => $init_encrypt,
+ 'init_decrypt' => $init_decrypt,
+ 'encrypt_block' => $encrypt_block,
+ 'decrypt_block' => $decrypt_block
+ ]
+ );
+ }
+
+ /**
+ * Encrypts a message.
+ *
+ * @see self::decrypt()
+ * @see parent::encrypt()
+ * @param string $plaintext
+ * @return string
+ */
+ public function encrypt($plaintext)
+ {
+ $this->setup();
+
+ switch ($this->engine) {
+ case self::ENGINE_LIBSODIUM:
+ $this->newtag = sodium_crypto_aead_aes256gcm_encrypt($plaintext, $this->aad, $this->nonce, $this->key);
+ return Strings::shift($this->newtag, strlen($plaintext));
+ case self::ENGINE_OPENSSL_GCM:
+ return openssl_encrypt(
+ $plaintext,
+ 'aes-' . $this->getKeyLength() . '-gcm',
+ $this->key,
+ OPENSSL_RAW_DATA,
+ $this->nonce,
+ $this->newtag,
+ $this->aad
+ );
+ }
+
+ return parent::encrypt($plaintext);
+ }
+
+ /**
+ * Decrypts a message.
+ *
+ * @see self::encrypt()
+ * @see parent::decrypt()
+ * @param string $ciphertext
+ * @return string
+ */
+ public function decrypt($ciphertext)
+ {
+ $this->setup();
+
+ switch ($this->engine) {
+ case self::ENGINE_LIBSODIUM:
+ if ($this->oldtag === false) {
+ throw new InsufficientSetupException('Authentication Tag has not been set');
+ }
+ if (strlen($this->oldtag) != 16) {
+ break;
+ }
+ $plaintext = sodium_crypto_aead_aes256gcm_decrypt($ciphertext . $this->oldtag, $this->aad, $this->nonce, $this->key);
+ if ($plaintext === false) {
+ $this->oldtag = false;
+ throw new BadDecryptionException('Error decrypting ciphertext with libsodium');
+ }
+ return $plaintext;
+ case self::ENGINE_OPENSSL_GCM:
+ if ($this->oldtag === false) {
+ throw new InsufficientSetupException('Authentication Tag has not been set');
+ }
+ $plaintext = openssl_decrypt(
+ $ciphertext,
+ 'aes-' . $this->getKeyLength() . '-gcm',
+ $this->key,
+ OPENSSL_RAW_DATA,
+ $this->nonce,
+ $this->oldtag,
+ $this->aad
+ );
+ if ($plaintext === false) {
+ $this->oldtag = false;
+ throw new BadDecryptionException('Error decrypting ciphertext with OpenSSL');
+ }
+ return $plaintext;
+ }
+
+ return parent::decrypt($ciphertext);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Salsa20.php b/Sources/Phpseclib/Crypt/Salsa20.php
new file mode 100644
index 0000000000..785e7aa2dc
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Salsa20.php
@@ -0,0 +1,528 @@
+
+ * @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\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\StreamCipher;
+use phpseclib3\Exception\BadDecryptionException;
+use phpseclib3\Exception\InsufficientSetupException;
+
+/**
+ * Pure-PHP implementation of Salsa20.
+ *
+ * @author Jim Wigginton
+ */
+class Salsa20 extends StreamCipher
+{
+ /**
+ * Part 1 of the state
+ *
+ * @var string|false
+ */
+ protected $p1 = false;
+
+ /**
+ * Part 2 of the state
+ *
+ * @var string|false
+ */
+ protected $p2 = false;
+
+ /**
+ * Key Length (in bytes)
+ *
+ * @var int
+ */
+ protected $key_length = 32; // = 256 bits
+
+ /**
+ * @see \phpseclib3\Crypt\Salsa20::crypt()
+ */
+ const ENCRYPT = 0;
+
+ /**
+ * @see \phpseclib3\Crypt\Salsa20::crypt()
+ */
+ const DECRYPT = 1;
+
+ /**
+ * Encryption buffer for continuous mode
+ *
+ * @var array
+ */
+ protected $enbuffer;
+
+ /**
+ * Decryption buffer for continuous mode
+ *
+ * @var array
+ */
+ protected $debuffer;
+
+ /**
+ * Counter
+ *
+ * @var int
+ */
+ protected $counter = 0;
+
+ /**
+ * Using Generated Poly1305 Key
+ *
+ * @var boolean
+ */
+ protected $usingGeneratedPoly1305Key = false;
+
+ /**
+ * Salsa20 uses a nonce
+ *
+ * @return bool
+ */
+ public function usesNonce()
+ {
+ return true;
+ }
+
+ /**
+ * Sets the key.
+ *
+ * @param string $key
+ * @throws \LengthException if the key length isn't supported
+ */
+ public function setKey($key)
+ {
+ switch (strlen($key)) {
+ case 16:
+ case 32:
+ break;
+ default:
+ throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16 or 32 are supported');
+ }
+
+ parent::setKey($key);
+ }
+
+ /**
+ * Sets the nonce.
+ *
+ * @param string $nonce
+ */
+ public function setNonce($nonce)
+ {
+ if (strlen($nonce) != 8) {
+ throw new \LengthException('Nonce of size ' . strlen($key) . ' not supported by this algorithm. Only an 64-bit nonce is supported');
+ }
+
+ $this->nonce = $nonce;
+ $this->changed = true;
+ $this->setEngine();
+ }
+
+ /**
+ * Sets the counter.
+ *
+ * @param int $counter
+ */
+ public function setCounter($counter)
+ {
+ $this->counter = $counter;
+ $this->setEngine();
+ }
+
+ /**
+ * Creates a Poly1305 key using the method discussed in RFC8439
+ *
+ * See https://tools.ietf.org/html/rfc8439#section-2.6.1
+ */
+ protected function createPoly1305Key()
+ {
+ if ($this->nonce === false) {
+ throw new InsufficientSetupException('No nonce has been defined');
+ }
+
+ if ($this->key === false) {
+ throw new InsufficientSetupException('No key has been defined');
+ }
+
+ $c = clone $this;
+ $c->setCounter(0);
+ $c->usePoly1305 = false;
+ $block = $c->encrypt(str_repeat("\0", 256));
+ $this->setPoly1305Key(substr($block, 0, 32));
+
+ if ($this->counter == 0) {
+ $this->counter++;
+ }
+ }
+
+ /**
+ * 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;
+ $this->createPoly1305Key();
+ }
+
+ $key = $this->key;
+ if (strlen($key) == 16) {
+ $constant = 'expand 16-byte k';
+ $key .= $key;
+ } else {
+ $constant = 'expand 32-byte k';
+ }
+
+ $this->p1 = substr($constant, 0, 4) .
+ substr($key, 0, 16) .
+ substr($constant, 4, 4) .
+ $this->nonce .
+ "\0\0\0\0";
+ $this->p2 = substr($constant, 8, 4) .
+ substr($key, 16, 16) .
+ substr($constant, 12, 4);
+ }
+
+ /**
+ * Setup the key (expansion)
+ */
+ protected function setupKey()
+ {
+ // Salsa20 does not utilize this method
+ }
+
+ /**
+ * Encrypts a message.
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
+ * @see self::crypt()
+ * @param string $plaintext
+ * @return string $ciphertext
+ */
+ public function encrypt($plaintext)
+ {
+ $ciphertext = $this->crypt($plaintext, self::ENCRYPT);
+ if (isset($this->poly1305Key)) {
+ $this->newtag = $this->poly1305($ciphertext);
+ }
+ return $ciphertext;
+ }
+
+ /**
+ * 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)
+ {
+ if (isset($this->poly1305Key)) {
+ if ($this->oldtag === false) {
+ throw new InsufficientSetupException('Authentication Tag has not been set');
+ }
+ $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;
+ }
+
+ return $this->crypt($ciphertext, self::DECRYPT);
+ }
+
+ /**
+ * Encrypts a block
+ *
+ * @param string $in
+ */
+ protected function encryptBlock($in)
+ {
+ // Salsa20 does not utilize this method
+ }
+
+ /**
+ * Decrypts a block
+ *
+ * @param string $in
+ */
+ protected function decryptBlock($in)
+ {
+ // Salsa20 does not utilize this method
+ }
+
+ /**
+ * Encrypts or decrypts a message.
+ *
+ * @see self::encrypt()
+ * @see self::decrypt()
+ * @param string $text
+ * @param int $mode
+ * @return string $text
+ */
+ private function crypt($text, $mode)
+ {
+ $this->setup();
+ if (!$this->continuousBuffer) {
+ if ($this->engine == self::ENGINE_OPENSSL) {
+ $iv = pack('V', $this->counter) . $this->p2;
+ return openssl_encrypt(
+ $text,
+ $this->cipher_name_openssl,
+ $this->key,
+ OPENSSL_RAW_DATA,
+ $iv
+ );
+ }
+ $i = $this->counter;
+ $blocks = str_split($text, 64);
+ foreach ($blocks as &$block) {
+ $block ^= static::salsa20($this->p1 . pack('V', $i++) . $this->p2);
+ }
+ unset($block);
+ return implode('', $blocks);
+ }
+
+ if ($mode == self::ENCRYPT) {
+ $buffer = &$this->enbuffer;
+ } else {
+ $buffer = &$this->debuffer;
+ }
+ if (!strlen($buffer['ciphertext'])) {
+ $ciphertext = '';
+ } else {
+ $ciphertext = $text ^ Strings::shift($buffer['ciphertext'], strlen($text));
+ $text = substr($text, strlen($ciphertext));
+ if (!strlen($text)) {
+ return $ciphertext;
+ }
+ }
+
+ $overflow = strlen($text) % 64; // & 0x3F
+ if ($overflow) {
+ $text2 = Strings::pop($text, $overflow);
+ if ($this->engine == self::ENGINE_OPENSSL) {
+ $iv = pack('V', $buffer['counter']) . $this->p2;
+ // at this point $text should be a multiple of 64
+ $buffer['counter'] += (strlen($text) >> 6) + 1; // ie. divide by 64
+ $encrypted = openssl_encrypt(
+ $text . str_repeat("\0", 64),
+ $this->cipher_name_openssl,
+ $this->key,
+ OPENSSL_RAW_DATA,
+ $iv
+ );
+ $temp = Strings::pop($encrypted, 64);
+ } else {
+ $blocks = str_split($text, 64);
+ if (strlen($text)) {
+ foreach ($blocks as &$block) {
+ $block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
+ }
+ unset($block);
+ }
+ $encrypted = implode('', $blocks);
+ $temp = static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
+ }
+ $ciphertext .= $encrypted . ($text2 ^ $temp);
+ $buffer['ciphertext'] = substr($temp, $overflow);
+ } elseif (!strlen($buffer['ciphertext'])) {
+ if ($this->engine == self::ENGINE_OPENSSL) {
+ $iv = pack('V', $buffer['counter']) . $this->p2;
+ $buffer['counter'] += (strlen($text) >> 6);
+ $ciphertext .= openssl_encrypt(
+ $text,
+ $this->cipher_name_openssl,
+ $this->key,
+ OPENSSL_RAW_DATA,
+ $iv
+ );
+ } else {
+ $blocks = str_split($text, 64);
+ foreach ($blocks as &$block) {
+ $block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
+ }
+ unset($block);
+ $ciphertext .= implode('', $blocks);
+ }
+ }
+
+ return $ciphertext;
+ }
+
+ /**
+ * Left Rotate
+ *
+ * @param int $x
+ * @param int $n
+ * @return int
+ */
+ protected static function leftRotate($x, $n)
+ {
+ if (PHP_INT_SIZE == 8) {
+ $r1 = $x << $n;
+ $r1 &= 0xFFFFFFFF;
+ $r2 = ($x & 0xFFFFFFFF) >> (32 - $n);
+ } else {
+ $x = (int) $x;
+ $r1 = $x << $n;
+ $r2 = $x >> (32 - $n);
+ $r2 &= (1 << $n) - 1;
+ }
+ return $r1 | $r2;
+ }
+
+ /**
+ * The quarterround function
+ *
+ * @param int $a
+ * @param int $b
+ * @param int $c
+ * @param int $d
+ */
+ protected static function quarterRound(&$a, &$b, &$c, &$d)
+ {
+ $b ^= self::leftRotate($a + $d, 7);
+ $c ^= self::leftRotate($b + $a, 9);
+ $d ^= self::leftRotate($c + $b, 13);
+ $a ^= self::leftRotate($d + $c, 18);
+ }
+
+ /**
+ * 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($x5, $x9, $x13, $x1);
+ static::quarterRound($x10, $x14, $x2, $x6);
+ static::quarterRound($x15, $x3, $x7, $x11);
+ // rowRound
+ static::quarterRound($x0, $x1, $x2, $x3);
+ static::quarterRound($x5, $x6, $x7, $x4);
+ static::quarterRound($x10, $x11, $x8, $x9);
+ static::quarterRound($x15, $x12, $x13, $x14);
+ }
+
+ /**
+ * The Salsa20 hash function function
+ *
+ * @param string $x
+ */
+ protected static function salsa20($x)
+ {
+ $z = $x = unpack('V*', $x);
+ for ($i = 0; $i < 10; $i++) {
+ static::doubleRound($z[1], $z[2], $z[3], $z[4], $z[5], $z[6], $z[7], $z[8], $z[9], $z[10], $z[11], $z[12], $z[13], $z[14], $z[15], $z[16]);
+ }
+
+ for ($i = 1; $i <= 16; $i++) {
+ $x[$i] += $z[$i];
+ }
+
+ return pack('V*', ...$x);
+ }
+
+ /**
+ * Calculates Poly1305 MAC
+ *
+ * @see self::decrypt()
+ * @see self::encrypt()
+ * @param string $ciphertext
+ * @return string
+ */
+ protected function poly1305($ciphertext)
+ {
+ if (!$this->usingGeneratedPoly1305Key) {
+ return parent::poly1305($this->aad . $ciphertext);
+ } else {
+ /*
+ sodium_crypto_aead_chacha20poly1305_encrypt does not calculate the poly1305 tag
+ the same way sodium_crypto_aead_chacha20poly1305_ietf_encrypt does. you can see
+ how the latter encrypts it in Salsa20::encrypt(). here's how the former encrypts
+ it:
+
+ $this->newtag = $this->poly1305(
+ $this->aad .
+ pack('V', strlen($this->aad)) . "\0\0\0\0" .
+ $ciphertext .
+ pack('V', strlen($ciphertext)) . "\0\0\0\0"
+ );
+
+ phpseclib opts to use the IETF construction, even when the nonce is 64-bits
+ instead of 96-bits
+ */
+ return parent::poly1305(
+ self::nullPad128($this->aad) .
+ self::nullPad128($ciphertext) .
+ pack('V', strlen($this->aad)) . "\0\0\0\0" .
+ pack('V', strlen($ciphertext)) . "\0\0\0\0"
+ );
+ }
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/TripleDES.php b/Sources/Phpseclib/Crypt/TripleDES.php
new file mode 100644
index 0000000000..932b7c611b
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/TripleDES.php
@@ -0,0 +1,436 @@
+
+ * setKey('abcdefghijklmnopqrstuvwx');
+ *
+ * $size = 10 * 1024;
+ * $plaintext = '';
+ * for ($i = 0; $i < $size; $i++) {
+ * $plaintext.= 'a';
+ * }
+ *
+ * echo $des->decrypt($des->encrypt($plaintext));
+ * ?>
+ *
+ *
+ * @author Jim Wigginton
+ * @copyright 2007 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 Triple DES.
+ *
+ * @author Jim Wigginton
+ */
+class TripleDES extends DES
+{
+ /**
+ * Encrypt / decrypt using inner chaining
+ *
+ * Inner chaining is used by SSH-1 and is generally considered to be less secure then outer chaining (self::MODE_CBC3).
+ */
+ const MODE_3CBC = -2;
+
+ /**
+ * Encrypt / decrypt using outer chaining
+ *
+ * Outer chaining is used by SSH-2 and when the mode is set to \phpseclib3\Crypt\Common\BlockCipher::MODE_CBC.
+ */
+ const MODE_CBC3 = self::MODE_CBC;
+
+ /**
+ * Key Length (in bytes)
+ *
+ * @see \phpseclib3\Crypt\TripleDES::setKeyLength()
+ * @var int
+ */
+ protected $key_length = 24;
+
+ /**
+ * The mcrypt specific name of the cipher
+ *
+ * @see DES::cipher_name_mcrypt
+ * @see Common\SymmetricKey::cipher_name_mcrypt
+ * @var string
+ */
+ protected $cipher_name_mcrypt = 'tripledes';
+
+ /**
+ * Optimizing value while CFB-encrypting
+ *
+ * @see Common\SymmetricKey::cfb_init_len
+ * @var int
+ */
+ protected $cfb_init_len = 750;
+
+ /**
+ * max possible size of $key
+ *
+ * @see self::setKey()
+ * @see DES::setKey()
+ * @var string
+ */
+ protected $key_length_max = 24;
+
+ /**
+ * Internal flag whether using self::MODE_3CBC or not
+ *
+ * @var bool
+ */
+ private $mode_3cbc;
+
+ /**
+ * The \phpseclib3\Crypt\DES objects
+ *
+ * Used only if $mode_3cbc === true
+ *
+ * @var array
+ */
+ private $des;
+
+ /**
+ * Default Constructor.
+ *
+ * Determines whether or not the mcrypt or OpenSSL extensions should be used.
+ *
+ * $mode could be:
+ *
+ * - ecb
+ *
+ * - cbc
+ *
+ * - ctr
+ *
+ * - cfb
+ *
+ * - ofb
+ *
+ * - 3cbc
+ *
+ * - cbc3 (same as cbc)
+ *
+ * @see Crypt\DES::__construct()
+ * @see Common\SymmetricKey::__construct()
+ * @param string $mode
+ */
+ public function __construct($mode)
+ {
+ switch (strtolower($mode)) {
+ // In case of self::MODE_3CBC, we init as CRYPT_DES_MODE_CBC
+ // and additional flag us internally as 3CBC
+ case '3cbc':
+ parent::__construct('cbc');
+ $this->mode_3cbc = true;
+
+ // This three $des'es will do the 3CBC work (if $key > 64bits)
+ $this->des = [
+ new DES('cbc'),
+ new DES('cbc'),
+ new DES('cbc'),
+ ];
+
+ // we're going to be doing the padding, ourselves, so disable it in the \phpseclib3\Crypt\DES objects
+ $this->des[0]->disablePadding();
+ $this->des[1]->disablePadding();
+ $this->des[2]->disablePadding();
+ break;
+ case 'cbc3':
+ $mode = 'cbc';
+ // fall-through
+ // If not 3CBC, we init as usual
+ default:
+ parent::__construct($mode);
+
+ if ($this->mode == self::MODE_STREAM) {
+ throw new BadModeException('Block ciphers cannot be ran in stream mode');
+ }
+ }
+ }
+
+ /**
+ * Test for engine validity
+ *
+ * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
+ *
+ * @see Common\SymmetricKey::__construct()
+ * @param int $engine
+ * @return bool
+ */
+ protected function isValidEngineHelper($engine)
+ {
+ if ($engine == self::ENGINE_OPENSSL) {
+ $this->cipher_name_openssl_ecb = 'des-ede3';
+ $mode = $this->openssl_translate_mode();
+ $this->cipher_name_openssl = $mode == 'ecb' ? 'des-ede3' : 'des-ede3-' . $mode;
+ }
+
+ return parent::isValidEngineHelper($engine);
+ }
+
+ /**
+ * Sets the initialization vector.
+ *
+ * SetIV is not required when \phpseclib3\Crypt\Common\SymmetricKey::MODE_ECB is being used.
+ *
+ * @see Common\SymmetricKey::setIV()
+ * @param string $iv
+ */
+ public function setIV($iv)
+ {
+ parent::setIV($iv);
+ if ($this->mode_3cbc) {
+ $this->des[0]->setIV($iv);
+ $this->des[1]->setIV($iv);
+ $this->des[2]->setIV($iv);
+ }
+ }
+
+ /**
+ * Sets the key length.
+ *
+ * Valid key lengths are 128 and 192 bits.
+ *
+ * If you want to use a 64-bit key use DES.php
+ *
+ * @see Common\SymmetricKey:setKeyLength()
+ * @throws \LengthException if the key length is invalid
+ * @param int $length
+ */
+ public function setKeyLength($length)
+ {
+ switch ($length) {
+ case 128:
+ case 192:
+ break;
+ default:
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes 128 or 192 bits are supported');
+ }
+
+ parent::setKeyLength($length);
+ }
+
+ /**
+ * Sets the key.
+ *
+ * Triple DES can use 128-bit (eg. strlen($key) == 16) or 192-bit (eg. strlen($key) == 24) keys.
+ *
+ * DES also requires that every eighth bit be a parity bit, however, we'll ignore that.
+ *
+ * @see DES::setKey()
+ * @see Common\SymmetricKey::setKey()
+ * @throws \LengthException if the key length is invalid
+ * @param string $key
+ */
+ public function setKey($key)
+ {
+ if ($this->explicit_key_length !== false && strlen($key) != $this->explicit_key_length) {
+ throw new \LengthException('Key length has already been set to ' . $this->explicit_key_length . ' bytes and this key is ' . strlen($key) . ' bytes');
+ }
+
+ switch (strlen($key)) {
+ case 16:
+ $key .= substr($key, 0, 8);
+ break;
+ case 24:
+ break;
+ default:
+ throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16 or 24 are supported');
+ }
+
+ // copied from self::setKey()
+ $this->key = $key;
+ $this->key_length = strlen($key);
+ $this->changed = $this->nonIVChanged = true;
+ $this->setEngine();
+
+ if ($this->mode_3cbc) {
+ $this->des[0]->setKey(substr($key, 0, 8));
+ $this->des[1]->setKey(substr($key, 8, 8));
+ $this->des[2]->setKey(substr($key, 16, 8));
+ }
+ }
+
+ /**
+ * Encrypts a message.
+ *
+ * @see Common\SymmetricKey::encrypt()
+ * @param string $plaintext
+ * @return string $cipertext
+ */
+ public function encrypt($plaintext)
+ {
+ // parent::en/decrypt() is able to do all the work for all modes and keylengths,
+ // except for: self::MODE_3CBC (inner chaining CBC) with a key > 64bits
+
+ // if the key is smaller then 8, do what we'd normally do
+ if ($this->mode_3cbc && strlen($this->key) > 8) {
+ return $this->des[2]->encrypt(
+ $this->des[1]->decrypt(
+ $this->des[0]->encrypt(
+ $this->pad($plaintext)
+ )
+ )
+ );
+ }
+
+ return parent::encrypt($plaintext);
+ }
+
+ /**
+ * Decrypts a message.
+ *
+ * @see Common\SymmetricKey::decrypt()
+ * @param string $ciphertext
+ * @return string $plaintext
+ */
+ public function decrypt($ciphertext)
+ {
+ if ($this->mode_3cbc && strlen($this->key) > 8) {
+ return $this->unpad(
+ $this->des[0]->decrypt(
+ $this->des[1]->encrypt(
+ $this->des[2]->decrypt(
+ str_pad($ciphertext, (strlen($ciphertext) + 7) & 0xFFFFFFF8, "\0")
+ )
+ )
+ )
+ );
+ }
+
+ return parent::decrypt($ciphertext);
+ }
+
+ /**
+ * Treat consecutive "packets" as if they are a continuous buffer.
+ *
+ * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets
+ * will yield different outputs:
+ *
+ *
+ * echo $des->encrypt(substr($plaintext, 0, 8));
+ * echo $des->encrypt(substr($plaintext, 8, 8));
+ *
+ *
+ * echo $des->encrypt($plaintext);
+ *
+ *
+ * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates
+ * another, as demonstrated with the following:
+ *
+ *
+ * $des->encrypt(substr($plaintext, 0, 8));
+ * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8)));
+ *
+ *
+ * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8)));
+ *
+ *
+ * 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\DES() 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.
+ *
+ * @see Common\SymmetricKey::enableContinuousBuffer()
+ * @see self::disableContinuousBuffer()
+ */
+ public function enableContinuousBuffer()
+ {
+ parent::enableContinuousBuffer();
+ if ($this->mode_3cbc) {
+ $this->des[0]->enableContinuousBuffer();
+ $this->des[1]->enableContinuousBuffer();
+ $this->des[2]->enableContinuousBuffer();
+ }
+ }
+
+ /**
+ * Treat consecutive packets as if they are a discontinuous buffer.
+ *
+ * The default behavior.
+ *
+ * @see Common\SymmetricKey::disableContinuousBuffer()
+ * @see self::enableContinuousBuffer()
+ */
+ public function disableContinuousBuffer()
+ {
+ parent::disableContinuousBuffer();
+ if ($this->mode_3cbc) {
+ $this->des[0]->disableContinuousBuffer();
+ $this->des[1]->disableContinuousBuffer();
+ $this->des[2]->disableContinuousBuffer();
+ }
+ }
+
+ /**
+ * Creates the key schedule
+ *
+ * @see DES::setupKey()
+ * @see Common\SymmetricKey::setupKey()
+ */
+ protected function setupKey()
+ {
+ switch (true) {
+ // if $key <= 64bits we configure our internal pure-php cipher engine
+ // to act as regular [1]DES, not as 3DES. mcrypt.so::tripledes does the same.
+ case strlen($this->key) <= 8:
+ $this->des_rounds = 1;
+ break;
+
+ // otherwise, if $key > 64bits, we configure our engine to work as 3DES.
+ default:
+ $this->des_rounds = 3;
+
+ // (only) if 3CBC is used we have, of course, to setup the $des[0-2] keys also separately.
+ if ($this->mode_3cbc) {
+ $this->des[0]->setupKey();
+ $this->des[1]->setupKey();
+ $this->des[2]->setupKey();
+
+ // because $des[0-2] will, now, do all the work we can return here
+ // not need unnecessary stress parent::setupKey() with our, now unused, $key.
+ return;
+ }
+ }
+ // setup our key
+ parent::setupKey();
+ }
+
+ /**
+ * Sets the internal crypt engine
+ *
+ * @see Common\SymmetricKey::__construct()
+ * @see Common\SymmetricKey::setPreferredEngine()
+ * @param int $engine
+ */
+ public function setPreferredEngine($engine)
+ {
+ if ($this->mode_3cbc) {
+ $this->des[0]->setPreferredEngine($engine);
+ $this->des[1]->setPreferredEngine($engine);
+ $this->des[2]->setPreferredEngine($engine);
+ }
+
+ parent::setPreferredEngine($engine);
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/Twofish.php b/Sources/Phpseclib/Crypt/Twofish.php
new file mode 100644
index 0000000000..141ad01417
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/Twofish.php
@@ -0,0 +1,816 @@
+
+ * setKey('12345678901234567890123456789012');
+ *
+ * $plaintext = str_repeat('a', 1024);
+ *
+ * echo $twofish->decrypt($twofish->encrypt($plaintext));
+ * ?>
+ *
+ *
+ * @author Jim Wigginton
+ * @author Hans-Juergen Petrich
+ * @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;
+use phpseclib3\Exception\BadModeException;
+
+/**
+ * Pure-PHP implementation of Twofish.
+ *
+ * @author Jim Wigginton
+ * @author Hans-Juergen Petrich
+ */
+class Twofish extends BlockCipher
+{
+ /**
+ * The mcrypt specific name of the cipher
+ *
+ * @see Common\SymmetricKey::cipher_name_mcrypt
+ * @var string
+ */
+ protected $cipher_name_mcrypt = 'twofish';
+
+ /**
+ * Optimizing value while CFB-encrypting
+ *
+ * @see Common\SymmetricKey::cfb_init_len
+ * @var int
+ */
+ protected $cfb_init_len = 800;
+
+ /**
+ * Q-Table
+ *
+ * @var array
+ */
+ private static $q0 = [
+ 0xA9, 0x67, 0xB3, 0xE8, 0x04, 0xFD, 0xA3, 0x76,
+ 0x9A, 0x92, 0x80, 0x78, 0xE4, 0xDD, 0xD1, 0x38,
+ 0x0D, 0xC6, 0x35, 0x98, 0x18, 0xF7, 0xEC, 0x6C,
+ 0x43, 0x75, 0x37, 0x26, 0xFA, 0x13, 0x94, 0x48,
+ 0xF2, 0xD0, 0x8B, 0x30, 0x84, 0x54, 0xDF, 0x23,
+ 0x19, 0x5B, 0x3D, 0x59, 0xF3, 0xAE, 0xA2, 0x82,
+ 0x63, 0x01, 0x83, 0x2E, 0xD9, 0x51, 0x9B, 0x7C,
+ 0xA6, 0xEB, 0xA5, 0xBE, 0x16, 0x0C, 0xE3, 0x61,
+ 0xC0, 0x8C, 0x3A, 0xF5, 0x73, 0x2C, 0x25, 0x0B,
+ 0xBB, 0x4E, 0x89, 0x6B, 0x53, 0x6A, 0xB4, 0xF1,
+ 0xE1, 0xE6, 0xBD, 0x45, 0xE2, 0xF4, 0xB6, 0x66,
+ 0xCC, 0x95, 0x03, 0x56, 0xD4, 0x1C, 0x1E, 0xD7,
+ 0xFB, 0xC3, 0x8E, 0xB5, 0xE9, 0xCF, 0xBF, 0xBA,
+ 0xEA, 0x77, 0x39, 0xAF, 0x33, 0xC9, 0x62, 0x71,
+ 0x81, 0x79, 0x09, 0xAD, 0x24, 0xCD, 0xF9, 0xD8,
+ 0xE5, 0xC5, 0xB9, 0x4D, 0x44, 0x08, 0x86, 0xE7,
+ 0xA1, 0x1D, 0xAA, 0xED, 0x06, 0x70, 0xB2, 0xD2,
+ 0x41, 0x7B, 0xA0, 0x11, 0x31, 0xC2, 0x27, 0x90,
+ 0x20, 0xF6, 0x60, 0xFF, 0x96, 0x5C, 0xB1, 0xAB,
+ 0x9E, 0x9C, 0x52, 0x1B, 0x5F, 0x93, 0x0A, 0xEF,
+ 0x91, 0x85, 0x49, 0xEE, 0x2D, 0x4F, 0x8F, 0x3B,
+ 0x47, 0x87, 0x6D, 0x46, 0xD6, 0x3E, 0x69, 0x64,
+ 0x2A, 0xCE, 0xCB, 0x2F, 0xFC, 0x97, 0x05, 0x7A,
+ 0xAC, 0x7F, 0xD5, 0x1A, 0x4B, 0x0E, 0xA7, 0x5A,
+ 0x28, 0x14, 0x3F, 0x29, 0x88, 0x3C, 0x4C, 0x02,
+ 0xB8, 0xDA, 0xB0, 0x17, 0x55, 0x1F, 0x8A, 0x7D,
+ 0x57, 0xC7, 0x8D, 0x74, 0xB7, 0xC4, 0x9F, 0x72,
+ 0x7E, 0x15, 0x22, 0x12, 0x58, 0x07, 0x99, 0x34,
+ 0x6E, 0x50, 0xDE, 0x68, 0x65, 0xBC, 0xDB, 0xF8,
+ 0xC8, 0xA8, 0x2B, 0x40, 0xDC, 0xFE, 0x32, 0xA4,
+ 0xCA, 0x10, 0x21, 0xF0, 0xD3, 0x5D, 0x0F, 0x00,
+ 0x6F, 0x9D, 0x36, 0x42, 0x4A, 0x5E, 0xC1, 0xE0
+ ];
+
+ /**
+ * Q-Table
+ *
+ * @var array
+ */
+ private static $q1 = [
+ 0x75, 0xF3, 0xC6, 0xF4, 0xDB, 0x7B, 0xFB, 0xC8,
+ 0x4A, 0xD3, 0xE6, 0x6B, 0x45, 0x7D, 0xE8, 0x4B,
+ 0xD6, 0x32, 0xD8, 0xFD, 0x37, 0x71, 0xF1, 0xE1,
+ 0x30, 0x0F, 0xF8, 0x1B, 0x87, 0xFA, 0x06, 0x3F,
+ 0x5E, 0xBA, 0xAE, 0x5B, 0x8A, 0x00, 0xBC, 0x9D,
+ 0x6D, 0xC1, 0xB1, 0x0E, 0x80, 0x5D, 0xD2, 0xD5,
+ 0xA0, 0x84, 0x07, 0x14, 0xB5, 0x90, 0x2C, 0xA3,
+ 0xB2, 0x73, 0x4C, 0x54, 0x92, 0x74, 0x36, 0x51,
+ 0x38, 0xB0, 0xBD, 0x5A, 0xFC, 0x60, 0x62, 0x96,
+ 0x6C, 0x42, 0xF7, 0x10, 0x7C, 0x28, 0x27, 0x8C,
+ 0x13, 0x95, 0x9C, 0xC7, 0x24, 0x46, 0x3B, 0x70,
+ 0xCA, 0xE3, 0x85, 0xCB, 0x11, 0xD0, 0x93, 0xB8,
+ 0xA6, 0x83, 0x20, 0xFF, 0x9F, 0x77, 0xC3, 0xCC,
+ 0x03, 0x6F, 0x08, 0xBF, 0x40, 0xE7, 0x2B, 0xE2,
+ 0x79, 0x0C, 0xAA, 0x82, 0x41, 0x3A, 0xEA, 0xB9,
+ 0xE4, 0x9A, 0xA4, 0x97, 0x7E, 0xDA, 0x7A, 0x17,
+ 0x66, 0x94, 0xA1, 0x1D, 0x3D, 0xF0, 0xDE, 0xB3,
+ 0x0B, 0x72, 0xA7, 0x1C, 0xEF, 0xD1, 0x53, 0x3E,
+ 0x8F, 0x33, 0x26, 0x5F, 0xEC, 0x76, 0x2A, 0x49,
+ 0x81, 0x88, 0xEE, 0x21, 0xC4, 0x1A, 0xEB, 0xD9,
+ 0xC5, 0x39, 0x99, 0xCD, 0xAD, 0x31, 0x8B, 0x01,
+ 0x18, 0x23, 0xDD, 0x1F, 0x4E, 0x2D, 0xF9, 0x48,
+ 0x4F, 0xF2, 0x65, 0x8E, 0x78, 0x5C, 0x58, 0x19,
+ 0x8D, 0xE5, 0x98, 0x57, 0x67, 0x7F, 0x05, 0x64,
+ 0xAF, 0x63, 0xB6, 0xFE, 0xF5, 0xB7, 0x3C, 0xA5,
+ 0xCE, 0xE9, 0x68, 0x44, 0xE0, 0x4D, 0x43, 0x69,
+ 0x29, 0x2E, 0xAC, 0x15, 0x59, 0xA8, 0x0A, 0x9E,
+ 0x6E, 0x47, 0xDF, 0x34, 0x35, 0x6A, 0xCF, 0xDC,
+ 0x22, 0xC9, 0xC0, 0x9B, 0x89, 0xD4, 0xED, 0xAB,
+ 0x12, 0xA2, 0x0D, 0x52, 0xBB, 0x02, 0x2F, 0xA9,
+ 0xD7, 0x61, 0x1E, 0xB4, 0x50, 0x04, 0xF6, 0xC2,
+ 0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xBE, 0x91
+ ];
+
+ /**
+ * M-Table
+ *
+ * @var array
+ */
+ private static $m0 = [
+ 0xBCBC3275, 0xECEC21F3, 0x202043C6, 0xB3B3C9F4, 0xDADA03DB, 0x02028B7B, 0xE2E22BFB, 0x9E9EFAC8,
+ 0xC9C9EC4A, 0xD4D409D3, 0x18186BE6, 0x1E1E9F6B, 0x98980E45, 0xB2B2387D, 0xA6A6D2E8, 0x2626B74B,
+ 0x3C3C57D6, 0x93938A32, 0x8282EED8, 0x525298FD, 0x7B7BD437, 0xBBBB3771, 0x5B5B97F1, 0x474783E1,
+ 0x24243C30, 0x5151E20F, 0xBABAC6F8, 0x4A4AF31B, 0xBFBF4887, 0x0D0D70FA, 0xB0B0B306, 0x7575DE3F,
+ 0xD2D2FD5E, 0x7D7D20BA, 0x666631AE, 0x3A3AA35B, 0x59591C8A, 0x00000000, 0xCDCD93BC, 0x1A1AE09D,
+ 0xAEAE2C6D, 0x7F7FABC1, 0x2B2BC7B1, 0xBEBEB90E, 0xE0E0A080, 0x8A8A105D, 0x3B3B52D2, 0x6464BAD5,
+ 0xD8D888A0, 0xE7E7A584, 0x5F5FE807, 0x1B1B1114, 0x2C2CC2B5, 0xFCFCB490, 0x3131272C, 0x808065A3,
+ 0x73732AB2, 0x0C0C8173, 0x79795F4C, 0x6B6B4154, 0x4B4B0292, 0x53536974, 0x94948F36, 0x83831F51,
+ 0x2A2A3638, 0xC4C49CB0, 0x2222C8BD, 0xD5D5F85A, 0xBDBDC3FC, 0x48487860, 0xFFFFCE62, 0x4C4C0796,
+ 0x4141776C, 0xC7C7E642, 0xEBEB24F7, 0x1C1C1410, 0x5D5D637C, 0x36362228, 0x6767C027, 0xE9E9AF8C,
+ 0x4444F913, 0x1414EA95, 0xF5F5BB9C, 0xCFCF18C7, 0x3F3F2D24, 0xC0C0E346, 0x7272DB3B, 0x54546C70,
+ 0x29294CCA, 0xF0F035E3, 0x0808FE85, 0xC6C617CB, 0xF3F34F11, 0x8C8CE4D0, 0xA4A45993, 0xCACA96B8,
+ 0x68683BA6, 0xB8B84D83, 0x38382820, 0xE5E52EFF, 0xADAD569F, 0x0B0B8477, 0xC8C81DC3, 0x9999FFCC,
+ 0x5858ED03, 0x19199A6F, 0x0E0E0A08, 0x95957EBF, 0x70705040, 0xF7F730E7, 0x6E6ECF2B, 0x1F1F6EE2,
+ 0xB5B53D79, 0x09090F0C, 0x616134AA, 0x57571682, 0x9F9F0B41, 0x9D9D803A, 0x111164EA, 0x2525CDB9,
+ 0xAFAFDDE4, 0x4545089A, 0xDFDF8DA4, 0xA3A35C97, 0xEAEAD57E, 0x353558DA, 0xEDEDD07A, 0x4343FC17,
+ 0xF8F8CB66, 0xFBFBB194, 0x3737D3A1, 0xFAFA401D, 0xC2C2683D, 0xB4B4CCF0, 0x32325DDE, 0x9C9C71B3,
+ 0x5656E70B, 0xE3E3DA72, 0x878760A7, 0x15151B1C, 0xF9F93AEF, 0x6363BFD1, 0x3434A953, 0x9A9A853E,
+ 0xB1B1428F, 0x7C7CD133, 0x88889B26, 0x3D3DA65F, 0xA1A1D7EC, 0xE4E4DF76, 0x8181942A, 0x91910149,
+ 0x0F0FFB81, 0xEEEEAA88, 0x161661EE, 0xD7D77321, 0x9797F5C4, 0xA5A5A81A, 0xFEFE3FEB, 0x6D6DB5D9,
+ 0x7878AEC5, 0xC5C56D39, 0x1D1DE599, 0x7676A4CD, 0x3E3EDCAD, 0xCBCB6731, 0xB6B6478B, 0xEFEF5B01,
+ 0x12121E18, 0x6060C523, 0x6A6AB0DD, 0x4D4DF61F, 0xCECEE94E, 0xDEDE7C2D, 0x55559DF9, 0x7E7E5A48,
+ 0x2121B24F, 0x03037AF2, 0xA0A02665, 0x5E5E198E, 0x5A5A6678, 0x65654B5C, 0x62624E58, 0xFDFD4519,
+ 0x0606F48D, 0x404086E5, 0xF2F2BE98, 0x3333AC57, 0x17179067, 0x05058E7F, 0xE8E85E05, 0x4F4F7D64,
+ 0x89896AAF, 0x10109563, 0x74742FB6, 0x0A0A75FE, 0x5C5C92F5, 0x9B9B74B7, 0x2D2D333C, 0x3030D6A5,
+ 0x2E2E49CE, 0x494989E9, 0x46467268, 0x77775544, 0xA8A8D8E0, 0x9696044D, 0x2828BD43, 0xA9A92969,
+ 0xD9D97929, 0x8686912E, 0xD1D187AC, 0xF4F44A15, 0x8D8D1559, 0xD6D682A8, 0xB9B9BC0A, 0x42420D9E,
+ 0xF6F6C16E, 0x2F2FB847, 0xDDDD06DF, 0x23233934, 0xCCCC6235, 0xF1F1C46A, 0xC1C112CF, 0x8585EBDC,
+ 0x8F8F9E22, 0x7171A1C9, 0x9090F0C0, 0xAAAA539B, 0x0101F189, 0x8B8BE1D4, 0x4E4E8CED, 0x8E8E6FAB,
+ 0xABABA212, 0x6F6F3EA2, 0xE6E6540D, 0xDBDBF252, 0x92927BBB, 0xB7B7B602, 0x6969CA2F, 0x3939D9A9,
+ 0xD3D30CD7, 0xA7A72361, 0xA2A2AD1E, 0xC3C399B4, 0x6C6C4450, 0x07070504, 0x04047FF6, 0x272746C2,
+ 0xACACA716, 0xD0D07625, 0x50501386, 0xDCDCF756, 0x84841A55, 0xE1E15109, 0x7A7A25BE, 0x1313EF91
+ ];
+
+ /**
+ * M-Table
+ *
+ * @var array
+ */
+ private static $m1 = [
+ 0xA9D93939, 0x67901717, 0xB3719C9C, 0xE8D2A6A6, 0x04050707, 0xFD985252, 0xA3658080, 0x76DFE4E4,
+ 0x9A084545, 0x92024B4B, 0x80A0E0E0, 0x78665A5A, 0xE4DDAFAF, 0xDDB06A6A, 0xD1BF6363, 0x38362A2A,
+ 0x0D54E6E6, 0xC6432020, 0x3562CCCC, 0x98BEF2F2, 0x181E1212, 0xF724EBEB, 0xECD7A1A1, 0x6C774141,
+ 0x43BD2828, 0x7532BCBC, 0x37D47B7B, 0x269B8888, 0xFA700D0D, 0x13F94444, 0x94B1FBFB, 0x485A7E7E,
+ 0xF27A0303, 0xD0E48C8C, 0x8B47B6B6, 0x303C2424, 0x84A5E7E7, 0x54416B6B, 0xDF06DDDD, 0x23C56060,
+ 0x1945FDFD, 0x5BA33A3A, 0x3D68C2C2, 0x59158D8D, 0xF321ECEC, 0xAE316666, 0xA23E6F6F, 0x82165757,
+ 0x63951010, 0x015BEFEF, 0x834DB8B8, 0x2E918686, 0xD9B56D6D, 0x511F8383, 0x9B53AAAA, 0x7C635D5D,
+ 0xA63B6868, 0xEB3FFEFE, 0xA5D63030, 0xBE257A7A, 0x16A7ACAC, 0x0C0F0909, 0xE335F0F0, 0x6123A7A7,
+ 0xC0F09090, 0x8CAFE9E9, 0x3A809D9D, 0xF5925C5C, 0x73810C0C, 0x2C273131, 0x2576D0D0, 0x0BE75656,
+ 0xBB7B9292, 0x4EE9CECE, 0x89F10101, 0x6B9F1E1E, 0x53A93434, 0x6AC4F1F1, 0xB499C3C3, 0xF1975B5B,
+ 0xE1834747, 0xE66B1818, 0xBDC82222, 0x450E9898, 0xE26E1F1F, 0xF4C9B3B3, 0xB62F7474, 0x66CBF8F8,
+ 0xCCFF9999, 0x95EA1414, 0x03ED5858, 0x56F7DCDC, 0xD4E18B8B, 0x1C1B1515, 0x1EADA2A2, 0xD70CD3D3,
+ 0xFB2BE2E2, 0xC31DC8C8, 0x8E195E5E, 0xB5C22C2C, 0xE9894949, 0xCF12C1C1, 0xBF7E9595, 0xBA207D7D,
+ 0xEA641111, 0x77840B0B, 0x396DC5C5, 0xAF6A8989, 0x33D17C7C, 0xC9A17171, 0x62CEFFFF, 0x7137BBBB,
+ 0x81FB0F0F, 0x793DB5B5, 0x0951E1E1, 0xADDC3E3E, 0x242D3F3F, 0xCDA47676, 0xF99D5555, 0xD8EE8282,
+ 0xE5864040, 0xC5AE7878, 0xB9CD2525, 0x4D049696, 0x44557777, 0x080A0E0E, 0x86135050, 0xE730F7F7,
+ 0xA1D33737, 0x1D40FAFA, 0xAA346161, 0xED8C4E4E, 0x06B3B0B0, 0x706C5454, 0xB22A7373, 0xD2523B3B,
+ 0x410B9F9F, 0x7B8B0202, 0xA088D8D8, 0x114FF3F3, 0x3167CBCB, 0xC2462727, 0x27C06767, 0x90B4FCFC,
+ 0x20283838, 0xF67F0404, 0x60784848, 0xFF2EE5E5, 0x96074C4C, 0x5C4B6565, 0xB1C72B2B, 0xAB6F8E8E,
+ 0x9E0D4242, 0x9CBBF5F5, 0x52F2DBDB, 0x1BF34A4A, 0x5FA63D3D, 0x9359A4A4, 0x0ABCB9B9, 0xEF3AF9F9,
+ 0x91EF1313, 0x85FE0808, 0x49019191, 0xEE611616, 0x2D7CDEDE, 0x4FB22121, 0x8F42B1B1, 0x3BDB7272,
+ 0x47B82F2F, 0x8748BFBF, 0x6D2CAEAE, 0x46E3C0C0, 0xD6573C3C, 0x3E859A9A, 0x6929A9A9, 0x647D4F4F,
+ 0x2A948181, 0xCE492E2E, 0xCB17C6C6, 0x2FCA6969, 0xFCC3BDBD, 0x975CA3A3, 0x055EE8E8, 0x7AD0EDED,
+ 0xAC87D1D1, 0x7F8E0505, 0xD5BA6464, 0x1AA8A5A5, 0x4BB72626, 0x0EB9BEBE, 0xA7608787, 0x5AF8D5D5,
+ 0x28223636, 0x14111B1B, 0x3FDE7575, 0x2979D9D9, 0x88AAEEEE, 0x3C332D2D, 0x4C5F7979, 0x02B6B7B7,
+ 0xB896CACA, 0xDA583535, 0xB09CC4C4, 0x17FC4343, 0x551A8484, 0x1FF64D4D, 0x8A1C5959, 0x7D38B2B2,
+ 0x57AC3333, 0xC718CFCF, 0x8DF40606, 0x74695353, 0xB7749B9B, 0xC4F59797, 0x9F56ADAD, 0x72DAE3E3,
+ 0x7ED5EAEA, 0x154AF4F4, 0x229E8F8F, 0x12A2ABAB, 0x584E6262, 0x07E85F5F, 0x99E51D1D, 0x34392323,
+ 0x6EC1F6F6, 0x50446C6C, 0xDE5D3232, 0x68724646, 0x6526A0A0, 0xBC93CDCD, 0xDB03DADA, 0xF8C6BABA,
+ 0xC8FA9E9E, 0xA882D6D6, 0x2BCF6E6E, 0x40507070, 0xDCEB8585, 0xFE750A0A, 0x328A9393, 0xA48DDFDF,
+ 0xCA4C2929, 0x10141C1C, 0x2173D7D7, 0xF0CCB4B4, 0xD309D4D4, 0x5D108A8A, 0x0FE25151, 0x00000000,
+ 0x6F9A1919, 0x9DE01A1A, 0x368F9494, 0x42E6C7C7, 0x4AECC9C9, 0x5EFDD2D2, 0xC1AB7F7F, 0xE0D8A8A8
+ ];
+
+ /**
+ * M-Table
+ *
+ * @var array
+ */
+ private static $m2 = [
+ 0xBC75BC32, 0xECF3EC21, 0x20C62043, 0xB3F4B3C9, 0xDADBDA03, 0x027B028B, 0xE2FBE22B, 0x9EC89EFA,
+ 0xC94AC9EC, 0xD4D3D409, 0x18E6186B, 0x1E6B1E9F, 0x9845980E, 0xB27DB238, 0xA6E8A6D2, 0x264B26B7,
+ 0x3CD63C57, 0x9332938A, 0x82D882EE, 0x52FD5298, 0x7B377BD4, 0xBB71BB37, 0x5BF15B97, 0x47E14783,
+ 0x2430243C, 0x510F51E2, 0xBAF8BAC6, 0x4A1B4AF3, 0xBF87BF48, 0x0DFA0D70, 0xB006B0B3, 0x753F75DE,
+ 0xD25ED2FD, 0x7DBA7D20, 0x66AE6631, 0x3A5B3AA3, 0x598A591C, 0x00000000, 0xCDBCCD93, 0x1A9D1AE0,
+ 0xAE6DAE2C, 0x7FC17FAB, 0x2BB12BC7, 0xBE0EBEB9, 0xE080E0A0, 0x8A5D8A10, 0x3BD23B52, 0x64D564BA,
+ 0xD8A0D888, 0xE784E7A5, 0x5F075FE8, 0x1B141B11, 0x2CB52CC2, 0xFC90FCB4, 0x312C3127, 0x80A38065,
+ 0x73B2732A, 0x0C730C81, 0x794C795F, 0x6B546B41, 0x4B924B02, 0x53745369, 0x9436948F, 0x8351831F,
+ 0x2A382A36, 0xC4B0C49C, 0x22BD22C8, 0xD55AD5F8, 0xBDFCBDC3, 0x48604878, 0xFF62FFCE, 0x4C964C07,
+ 0x416C4177, 0xC742C7E6, 0xEBF7EB24, 0x1C101C14, 0x5D7C5D63, 0x36283622, 0x672767C0, 0xE98CE9AF,
+ 0x441344F9, 0x149514EA, 0xF59CF5BB, 0xCFC7CF18, 0x3F243F2D, 0xC046C0E3, 0x723B72DB, 0x5470546C,
+ 0x29CA294C, 0xF0E3F035, 0x088508FE, 0xC6CBC617, 0xF311F34F, 0x8CD08CE4, 0xA493A459, 0xCAB8CA96,
+ 0x68A6683B, 0xB883B84D, 0x38203828, 0xE5FFE52E, 0xAD9FAD56, 0x0B770B84, 0xC8C3C81D, 0x99CC99FF,
+ 0x580358ED, 0x196F199A, 0x0E080E0A, 0x95BF957E, 0x70407050, 0xF7E7F730, 0x6E2B6ECF, 0x1FE21F6E,
+ 0xB579B53D, 0x090C090F, 0x61AA6134, 0x57825716, 0x9F419F0B, 0x9D3A9D80, 0x11EA1164, 0x25B925CD,
+ 0xAFE4AFDD, 0x459A4508, 0xDFA4DF8D, 0xA397A35C, 0xEA7EEAD5, 0x35DA3558, 0xED7AEDD0, 0x431743FC,
+ 0xF866F8CB, 0xFB94FBB1, 0x37A137D3, 0xFA1DFA40, 0xC23DC268, 0xB4F0B4CC, 0x32DE325D, 0x9CB39C71,
+ 0x560B56E7, 0xE372E3DA, 0x87A78760, 0x151C151B, 0xF9EFF93A, 0x63D163BF, 0x345334A9, 0x9A3E9A85,
+ 0xB18FB142, 0x7C337CD1, 0x8826889B, 0x3D5F3DA6, 0xA1ECA1D7, 0xE476E4DF, 0x812A8194, 0x91499101,
+ 0x0F810FFB, 0xEE88EEAA, 0x16EE1661, 0xD721D773, 0x97C497F5, 0xA51AA5A8, 0xFEEBFE3F, 0x6DD96DB5,
+ 0x78C578AE, 0xC539C56D, 0x1D991DE5, 0x76CD76A4, 0x3EAD3EDC, 0xCB31CB67, 0xB68BB647, 0xEF01EF5B,
+ 0x1218121E, 0x602360C5, 0x6ADD6AB0, 0x4D1F4DF6, 0xCE4ECEE9, 0xDE2DDE7C, 0x55F9559D, 0x7E487E5A,
+ 0x214F21B2, 0x03F2037A, 0xA065A026, 0x5E8E5E19, 0x5A785A66, 0x655C654B, 0x6258624E, 0xFD19FD45,
+ 0x068D06F4, 0x40E54086, 0xF298F2BE, 0x335733AC, 0x17671790, 0x057F058E, 0xE805E85E, 0x4F644F7D,
+ 0x89AF896A, 0x10631095, 0x74B6742F, 0x0AFE0A75, 0x5CF55C92, 0x9BB79B74, 0x2D3C2D33, 0x30A530D6,
+ 0x2ECE2E49, 0x49E94989, 0x46684672, 0x77447755, 0xA8E0A8D8, 0x964D9604, 0x284328BD, 0xA969A929,
+ 0xD929D979, 0x862E8691, 0xD1ACD187, 0xF415F44A, 0x8D598D15, 0xD6A8D682, 0xB90AB9BC, 0x429E420D,
+ 0xF66EF6C1, 0x2F472FB8, 0xDDDFDD06, 0x23342339, 0xCC35CC62, 0xF16AF1C4, 0xC1CFC112, 0x85DC85EB,
+ 0x8F228F9E, 0x71C971A1, 0x90C090F0, 0xAA9BAA53, 0x018901F1, 0x8BD48BE1, 0x4EED4E8C, 0x8EAB8E6F,
+ 0xAB12ABA2, 0x6FA26F3E, 0xE60DE654, 0xDB52DBF2, 0x92BB927B, 0xB702B7B6, 0x692F69CA, 0x39A939D9,
+ 0xD3D7D30C, 0xA761A723, 0xA21EA2AD, 0xC3B4C399, 0x6C506C44, 0x07040705, 0x04F6047F, 0x27C22746,
+ 0xAC16ACA7, 0xD025D076, 0x50865013, 0xDC56DCF7, 0x8455841A, 0xE109E151, 0x7ABE7A25, 0x139113EF
+ ];
+
+ /**
+ * M-Table
+ *
+ * @var array
+ */
+ private static $m3 = [
+ 0xD939A9D9, 0x90176790, 0x719CB371, 0xD2A6E8D2, 0x05070405, 0x9852FD98, 0x6580A365, 0xDFE476DF,
+ 0x08459A08, 0x024B9202, 0xA0E080A0, 0x665A7866, 0xDDAFE4DD, 0xB06ADDB0, 0xBF63D1BF, 0x362A3836,
+ 0x54E60D54, 0x4320C643, 0x62CC3562, 0xBEF298BE, 0x1E12181E, 0x24EBF724, 0xD7A1ECD7, 0x77416C77,
+ 0xBD2843BD, 0x32BC7532, 0xD47B37D4, 0x9B88269B, 0x700DFA70, 0xF94413F9, 0xB1FB94B1, 0x5A7E485A,
+ 0x7A03F27A, 0xE48CD0E4, 0x47B68B47, 0x3C24303C, 0xA5E784A5, 0x416B5441, 0x06DDDF06, 0xC56023C5,
+ 0x45FD1945, 0xA33A5BA3, 0x68C23D68, 0x158D5915, 0x21ECF321, 0x3166AE31, 0x3E6FA23E, 0x16578216,
+ 0x95106395, 0x5BEF015B, 0x4DB8834D, 0x91862E91, 0xB56DD9B5, 0x1F83511F, 0x53AA9B53, 0x635D7C63,
+ 0x3B68A63B, 0x3FFEEB3F, 0xD630A5D6, 0x257ABE25, 0xA7AC16A7, 0x0F090C0F, 0x35F0E335, 0x23A76123,
+ 0xF090C0F0, 0xAFE98CAF, 0x809D3A80, 0x925CF592, 0x810C7381, 0x27312C27, 0x76D02576, 0xE7560BE7,
+ 0x7B92BB7B, 0xE9CE4EE9, 0xF10189F1, 0x9F1E6B9F, 0xA93453A9, 0xC4F16AC4, 0x99C3B499, 0x975BF197,
+ 0x8347E183, 0x6B18E66B, 0xC822BDC8, 0x0E98450E, 0x6E1FE26E, 0xC9B3F4C9, 0x2F74B62F, 0xCBF866CB,
+ 0xFF99CCFF, 0xEA1495EA, 0xED5803ED, 0xF7DC56F7, 0xE18BD4E1, 0x1B151C1B, 0xADA21EAD, 0x0CD3D70C,
+ 0x2BE2FB2B, 0x1DC8C31D, 0x195E8E19, 0xC22CB5C2, 0x8949E989, 0x12C1CF12, 0x7E95BF7E, 0x207DBA20,
+ 0x6411EA64, 0x840B7784, 0x6DC5396D, 0x6A89AF6A, 0xD17C33D1, 0xA171C9A1, 0xCEFF62CE, 0x37BB7137,
+ 0xFB0F81FB, 0x3DB5793D, 0x51E10951, 0xDC3EADDC, 0x2D3F242D, 0xA476CDA4, 0x9D55F99D, 0xEE82D8EE,
+ 0x8640E586, 0xAE78C5AE, 0xCD25B9CD, 0x04964D04, 0x55774455, 0x0A0E080A, 0x13508613, 0x30F7E730,
+ 0xD337A1D3, 0x40FA1D40, 0x3461AA34, 0x8C4EED8C, 0xB3B006B3, 0x6C54706C, 0x2A73B22A, 0x523BD252,
+ 0x0B9F410B, 0x8B027B8B, 0x88D8A088, 0x4FF3114F, 0x67CB3167, 0x4627C246, 0xC06727C0, 0xB4FC90B4,
+ 0x28382028, 0x7F04F67F, 0x78486078, 0x2EE5FF2E, 0x074C9607, 0x4B655C4B, 0xC72BB1C7, 0x6F8EAB6F,
+ 0x0D429E0D, 0xBBF59CBB, 0xF2DB52F2, 0xF34A1BF3, 0xA63D5FA6, 0x59A49359, 0xBCB90ABC, 0x3AF9EF3A,
+ 0xEF1391EF, 0xFE0885FE, 0x01914901, 0x6116EE61, 0x7CDE2D7C, 0xB2214FB2, 0x42B18F42, 0xDB723BDB,
+ 0xB82F47B8, 0x48BF8748, 0x2CAE6D2C, 0xE3C046E3, 0x573CD657, 0x859A3E85, 0x29A96929, 0x7D4F647D,
+ 0x94812A94, 0x492ECE49, 0x17C6CB17, 0xCA692FCA, 0xC3BDFCC3, 0x5CA3975C, 0x5EE8055E, 0xD0ED7AD0,
+ 0x87D1AC87, 0x8E057F8E, 0xBA64D5BA, 0xA8A51AA8, 0xB7264BB7, 0xB9BE0EB9, 0x6087A760, 0xF8D55AF8,
+ 0x22362822, 0x111B1411, 0xDE753FDE, 0x79D92979, 0xAAEE88AA, 0x332D3C33, 0x5F794C5F, 0xB6B702B6,
+ 0x96CAB896, 0x5835DA58, 0x9CC4B09C, 0xFC4317FC, 0x1A84551A, 0xF64D1FF6, 0x1C598A1C, 0x38B27D38,
+ 0xAC3357AC, 0x18CFC718, 0xF4068DF4, 0x69537469, 0x749BB774, 0xF597C4F5, 0x56AD9F56, 0xDAE372DA,
+ 0xD5EA7ED5, 0x4AF4154A, 0x9E8F229E, 0xA2AB12A2, 0x4E62584E, 0xE85F07E8, 0xE51D99E5, 0x39233439,
+ 0xC1F66EC1, 0x446C5044, 0x5D32DE5D, 0x72466872, 0x26A06526, 0x93CDBC93, 0x03DADB03, 0xC6BAF8C6,
+ 0xFA9EC8FA, 0x82D6A882, 0xCF6E2BCF, 0x50704050, 0xEB85DCEB, 0x750AFE75, 0x8A93328A, 0x8DDFA48D,
+ 0x4C29CA4C, 0x141C1014, 0x73D72173, 0xCCB4F0CC, 0x09D4D309, 0x108A5D10, 0xE2510FE2, 0x00000000,
+ 0x9A196F9A, 0xE01A9DE0, 0x8F94368F, 0xE6C742E6, 0xECC94AEC, 0xFDD25EFD, 0xAB7FC1AB, 0xD8A8E0D8
+ ];
+
+ /**
+ * The Key Schedule Array
+ *
+ * @var array
+ */
+ private $K = [];
+
+ /**
+ * The Key depended S-Table 0
+ *
+ * @var array
+ */
+ private $S0 = [];
+
+ /**
+ * The Key depended S-Table 1
+ *
+ * @var array
+ */
+ private $S1 = [];
+
+ /**
+ * The Key depended S-Table 2
+ *
+ * @var array
+ */
+ private $S2 = [];
+
+ /**
+ * The Key depended S-Table 3
+ *
+ * @var array
+ */
+ private $S3 = [];
+
+ /**
+ * Holds the last used key
+ *
+ * @var array
+ */
+ private $kl;
+
+ /**
+ * The Key Length (in bytes)
+ *
+ * @see Crypt_Twofish::setKeyLength()
+ * @var int
+ */
+ protected $key_length = 16;
+
+ /**
+ * Default Constructor.
+ *
+ * @param string $mode
+ * @throws BadModeException if an invalid / unsupported mode is provided
+ */
+ public function __construct($mode)
+ {
+ parent::__construct($mode);
+
+ if ($this->mode == self::MODE_STREAM) {
+ throw new BadModeException('Block ciphers cannot be ran in stream mode');
+ }
+ }
+
+ /**
+ * Initialize Static Variables
+ */
+ protected static function initialize_static_variables()
+ {
+ if (is_float(self::$m3[0])) {
+ self::$m0 = array_map('intval', self::$m0);
+ self::$m1 = array_map('intval', self::$m1);
+ self::$m2 = array_map('intval', self::$m2);
+ self::$m3 = array_map('intval', self::$m3);
+ self::$q0 = array_map('intval', self::$q0);
+ self::$q1 = array_map('intval', self::$q1);
+ }
+
+ parent::initialize_static_variables();
+ }
+
+ /**
+ * Sets the key length.
+ *
+ * Valid key lengths are 128, 192 or 256 bits
+ *
+ * @param int $length
+ */
+ 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 16, 24 or 32 supported');
+ }
+
+ parent::setKeyLength($length);
+ }
+
+ /**
+ * Sets the key.
+ *
+ * Rijndael supports five different key lengths
+ *
+ * @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);
+ }
+
+ /**
+ * Setup the key (expansion)
+ *
+ * @see 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 and generating the key-depended s-boxes */
+ $le_longs = unpack('V*', $this->key);
+ $key = unpack('C*', $this->key);
+ $m0 = self::$m0;
+ $m1 = self::$m1;
+ $m2 = self::$m2;
+ $m3 = self::$m3;
+ $q0 = self::$q0;
+ $q1 = self::$q1;
+
+ $K = $S0 = $S1 = $S2 = $S3 = [];
+
+ switch (strlen($this->key)) {
+ case 16:
+ list($s7, $s6, $s5, $s4) = $this->mdsrem($le_longs[1], $le_longs[2]);
+ list($s3, $s2, $s1, $s0) = $this->mdsrem($le_longs[3], $le_longs[4]);
+ for ($i = 0, $j = 1; $i < 40; $i += 2, $j += 2) {
+ $A = $m0[$q0[$q0[$i] ^ $key[ 9]] ^ $key[1]] ^
+ $m1[$q0[$q1[$i] ^ $key[10]] ^ $key[2]] ^
+ $m2[$q1[$q0[$i] ^ $key[11]] ^ $key[3]] ^
+ $m3[$q1[$q1[$i] ^ $key[12]] ^ $key[4]];
+ $B = $m0[$q0[$q0[$j] ^ $key[13]] ^ $key[5]] ^
+ $m1[$q0[$q1[$j] ^ $key[14]] ^ $key[6]] ^
+ $m2[$q1[$q0[$j] ^ $key[15]] ^ $key[7]] ^
+ $m3[$q1[$q1[$j] ^ $key[16]] ^ $key[8]];
+ $B = ($B << 8) | ($B >> 24 & 0xff);
+ $A = self::safe_intval($A + $B);
+ $K[] = $A;
+ $A = self::safe_intval($A + $B);
+ $K[] = ($A << 9 | $A >> 23 & 0x1ff);
+ }
+ for ($i = 0; $i < 256; ++$i) {
+ $S0[$i] = $m0[$q0[$q0[$i] ^ $s4] ^ $s0];
+ $S1[$i] = $m1[$q0[$q1[$i] ^ $s5] ^ $s1];
+ $S2[$i] = $m2[$q1[$q0[$i] ^ $s6] ^ $s2];
+ $S3[$i] = $m3[$q1[$q1[$i] ^ $s7] ^ $s3];
+ }
+ break;
+ case 24:
+ list($sb, $sa, $s9, $s8) = $this->mdsrem($le_longs[1], $le_longs[2]);
+ list($s7, $s6, $s5, $s4) = $this->mdsrem($le_longs[3], $le_longs[4]);
+ list($s3, $s2, $s1, $s0) = $this->mdsrem($le_longs[5], $le_longs[6]);
+ for ($i = 0, $j = 1; $i < 40; $i += 2, $j += 2) {
+ $A = $m0[$q0[$q0[$q1[$i] ^ $key[17]] ^ $key[ 9]] ^ $key[1]] ^
+ $m1[$q0[$q1[$q1[$i] ^ $key[18]] ^ $key[10]] ^ $key[2]] ^
+ $m2[$q1[$q0[$q0[$i] ^ $key[19]] ^ $key[11]] ^ $key[3]] ^
+ $m3[$q1[$q1[$q0[$i] ^ $key[20]] ^ $key[12]] ^ $key[4]];
+ $B = $m0[$q0[$q0[$q1[$j] ^ $key[21]] ^ $key[13]] ^ $key[5]] ^
+ $m1[$q0[$q1[$q1[$j] ^ $key[22]] ^ $key[14]] ^ $key[6]] ^
+ $m2[$q1[$q0[$q0[$j] ^ $key[23]] ^ $key[15]] ^ $key[7]] ^
+ $m3[$q1[$q1[$q0[$j] ^ $key[24]] ^ $key[16]] ^ $key[8]];
+ $B = ($B << 8) | ($B >> 24 & 0xff);
+ $A = self::safe_intval($A + $B);
+ $K[] = $A;
+ $A = self::safe_intval($A + $B);
+ $K[] = ($A << 9 | $A >> 23 & 0x1ff);
+ }
+ for ($i = 0; $i < 256; ++$i) {
+ $S0[$i] = $m0[$q0[$q0[$q1[$i] ^ $s8] ^ $s4] ^ $s0];
+ $S1[$i] = $m1[$q0[$q1[$q1[$i] ^ $s9] ^ $s5] ^ $s1];
+ $S2[$i] = $m2[$q1[$q0[$q0[$i] ^ $sa] ^ $s6] ^ $s2];
+ $S3[$i] = $m3[$q1[$q1[$q0[$i] ^ $sb] ^ $s7] ^ $s3];
+ }
+ break;
+ default: // 32
+ list($sf, $se, $sd, $sc) = $this->mdsrem($le_longs[1], $le_longs[2]);
+ list($sb, $sa, $s9, $s8) = $this->mdsrem($le_longs[3], $le_longs[4]);
+ list($s7, $s6, $s5, $s4) = $this->mdsrem($le_longs[5], $le_longs[6]);
+ list($s3, $s2, $s1, $s0) = $this->mdsrem($le_longs[7], $le_longs[8]);
+ for ($i = 0, $j = 1; $i < 40; $i += 2, $j += 2) {
+ $A = $m0[$q0[$q0[$q1[$q1[$i] ^ $key[25]] ^ $key[17]] ^ $key[ 9]] ^ $key[1]] ^
+ $m1[$q0[$q1[$q1[$q0[$i] ^ $key[26]] ^ $key[18]] ^ $key[10]] ^ $key[2]] ^
+ $m2[$q1[$q0[$q0[$q0[$i] ^ $key[27]] ^ $key[19]] ^ $key[11]] ^ $key[3]] ^
+ $m3[$q1[$q1[$q0[$q1[$i] ^ $key[28]] ^ $key[20]] ^ $key[12]] ^ $key[4]];
+ $B = $m0[$q0[$q0[$q1[$q1[$j] ^ $key[29]] ^ $key[21]] ^ $key[13]] ^ $key[5]] ^
+ $m1[$q0[$q1[$q1[$q0[$j] ^ $key[30]] ^ $key[22]] ^ $key[14]] ^ $key[6]] ^
+ $m2[$q1[$q0[$q0[$q0[$j] ^ $key[31]] ^ $key[23]] ^ $key[15]] ^ $key[7]] ^
+ $m3[$q1[$q1[$q0[$q1[$j] ^ $key[32]] ^ $key[24]] ^ $key[16]] ^ $key[8]];
+ $B = ($B << 8) | ($B >> 24 & 0xff);
+ $A = self::safe_intval($A + $B);
+ $K[] = $A;
+ $A = self::safe_intval($A + $B);
+ $K[] = ($A << 9 | $A >> 23 & 0x1ff);
+ }
+ for ($i = 0; $i < 256; ++$i) {
+ $S0[$i] = $m0[$q0[$q0[$q1[$q1[$i] ^ $sc] ^ $s8] ^ $s4] ^ $s0];
+ $S1[$i] = $m1[$q0[$q1[$q1[$q0[$i] ^ $sd] ^ $s9] ^ $s5] ^ $s1];
+ $S2[$i] = $m2[$q1[$q0[$q0[$q0[$i] ^ $se] ^ $sa] ^ $s6] ^ $s2];
+ $S3[$i] = $m3[$q1[$q1[$q0[$q1[$i] ^ $sf] ^ $sb] ^ $s7] ^ $s3];
+ }
+ }
+
+ $this->K = $K;
+ $this->S0 = $S0;
+ $this->S1 = $S1;
+ $this->S2 = $S2;
+ $this->S3 = $S3;
+ }
+
+ /**
+ * _mdsrem function using by the twofish cipher algorithm
+ *
+ * @param string $A
+ * @param string $B
+ * @return array
+ */
+ private function mdsrem($A, $B)
+ {
+ // No gain by unrolling this loop.
+ for ($i = 0; $i < 8; ++$i) {
+ // Get most significant coefficient.
+ $t = 0xff & ($B >> 24);
+
+ // Shift the others up.
+ $B = ($B << 8) | (0xff & ($A >> 24));
+ $A <<= 8;
+
+ $u = $t << 1;
+
+ // Subtract the modular polynomial on overflow.
+ if ($t & 0x80) {
+ $u ^= 0x14d;
+ }
+
+ // Remove t * (a * x^2 + 1).
+ $B ^= $t ^ ($u << 16);
+
+ // Form u = a*t + t/a = t*(a + 1/a).
+ $u ^= 0x7fffffff & ($t >> 1);
+
+ // Add the modular polynomial on underflow.
+ if ($t & 0x01) {
+ $u ^= 0xa6 ;
+ }
+
+ // Remove t * (a + 1/a) * (x^3 + x).
+ $B ^= ($u << 24) | ($u << 8);
+ }
+
+ return [
+ 0xff & $B >> 24,
+ 0xff & $B >> 16,
+ 0xff & $B >> 8,
+ 0xff & $B];
+ }
+
+ /**
+ * Encrypts a block
+ *
+ * @param string $in
+ * @return string
+ */
+ protected function encryptBlock($in)
+ {
+ $S0 = $this->S0;
+ $S1 = $this->S1;
+ $S2 = $this->S2;
+ $S3 = $this->S3;
+ $K = $this->K;
+
+ $in = unpack("V4", $in);
+ $R0 = $K[0] ^ $in[1];
+ $R1 = $K[1] ^ $in[2];
+ $R2 = $K[2] ^ $in[3];
+ $R3 = $K[3] ^ $in[4];
+
+ $ki = 7;
+ while ($ki < 39) {
+ $t0 = $S0[ $R0 & 0xff] ^
+ $S1[($R0 >> 8) & 0xff] ^
+ $S2[($R0 >> 16) & 0xff] ^
+ $S3[($R0 >> 24) & 0xff];
+ $t1 = $S0[($R1 >> 24) & 0xff] ^
+ $S1[ $R1 & 0xff] ^
+ $S2[($R1 >> 8) & 0xff] ^
+ $S3[($R1 >> 16) & 0xff];
+ $R2 ^= self::safe_intval($t0 + $t1 + $K[++$ki]);
+ $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31);
+ $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ self::safe_intval($t0 + ($t1 << 1) + $K[++$ki]);
+
+ $t0 = $S0[ $R2 & 0xff] ^
+ $S1[($R2 >> 8) & 0xff] ^
+ $S2[($R2 >> 16) & 0xff] ^
+ $S3[($R2 >> 24) & 0xff];
+ $t1 = $S0[($R3 >> 24) & 0xff] ^
+ $S1[ $R3 & 0xff] ^
+ $S2[($R3 >> 8) & 0xff] ^
+ $S3[($R3 >> 16) & 0xff];
+ $R0 ^= self::safe_intval($t0 + $t1 + $K[++$ki]);
+ $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31);
+ $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ self::safe_intval($t0 + ($t1 << 1) + $K[++$ki]);
+ }
+
+ // @codingStandardsIgnoreStart
+ return pack("V4", $K[4] ^ $R2,
+ $K[5] ^ $R3,
+ $K[6] ^ $R0,
+ $K[7] ^ $R1);
+ // @codingStandardsIgnoreEnd
+ }
+
+ /**
+ * Decrypts a block
+ *
+ * @param string $in
+ * @return string
+ */
+ protected function decryptBlock($in)
+ {
+ $S0 = $this->S0;
+ $S1 = $this->S1;
+ $S2 = $this->S2;
+ $S3 = $this->S3;
+ $K = $this->K;
+
+ $in = unpack("V4", $in);
+ $R0 = $K[4] ^ $in[1];
+ $R1 = $K[5] ^ $in[2];
+ $R2 = $K[6] ^ $in[3];
+ $R3 = $K[7] ^ $in[4];
+
+ $ki = 40;
+ while ($ki > 8) {
+ $t0 = $S0[$R0 & 0xff] ^
+ $S1[$R0 >> 8 & 0xff] ^
+ $S2[$R0 >> 16 & 0xff] ^
+ $S3[$R0 >> 24 & 0xff];
+ $t1 = $S0[$R1 >> 24 & 0xff] ^
+ $S1[$R1 & 0xff] ^
+ $S2[$R1 >> 8 & 0xff] ^
+ $S3[$R1 >> 16 & 0xff];
+ $R3 ^= self::safe_intval($t0 + ($t1 << 1) + $K[--$ki]);
+ $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31;
+ $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ self::safe_intval($t0 + $t1 + $K[--$ki]);
+
+ $t0 = $S0[$R2 & 0xff] ^
+ $S1[$R2 >> 8 & 0xff] ^
+ $S2[$R2 >> 16 & 0xff] ^
+ $S3[$R2 >> 24 & 0xff];
+ $t1 = $S0[$R3 >> 24 & 0xff] ^
+ $S1[$R3 & 0xff] ^
+ $S2[$R3 >> 8 & 0xff] ^
+ $S3[$R3 >> 16 & 0xff];
+ $R1 ^= self::safe_intval($t0 + ($t1 << 1) + $K[--$ki]);
+ $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31;
+ $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ self::safe_intval($t0 + $t1 + $K[--$ki]);
+ }
+
+ // @codingStandardsIgnoreStart
+ return pack("V4", $K[0] ^ $R2,
+ $K[1] ^ $R3,
+ $K[2] ^ $R0,
+ $K[3] ^ $R1);
+ // @codingStandardsIgnoreEnd
+ }
+
+ /**
+ * Setup the performance-optimized function for de/encrypt()
+ *
+ * @see Common\SymmetricKey::_setupInlineCrypt()
+ */
+ protected function setupInlineCrypt()
+ {
+ $K = $this->K;
+ $init_crypt = '
+ static $S0, $S1, $S2, $S3;
+ if (!$S0) {
+ for ($i = 0; $i < 256; ++$i) {
+ $S0[] = (int)$this->S0[$i];
+ $S1[] = (int)$this->S1[$i];
+ $S2[] = (int)$this->S2[$i];
+ $S3[] = (int)$this->S3[$i];
+ }
+ }
+ ';
+
+ $safeint = self::safe_intval_inline();
+
+ // Generating encrypt code:
+ $encrypt_block = '
+ $in = unpack("V4", $in);
+ $R0 = ' . $K[0] . ' ^ $in[1];
+ $R1 = ' . $K[1] . ' ^ $in[2];
+ $R2 = ' . $K[2] . ' ^ $in[3];
+ $R3 = ' . $K[3] . ' ^ $in[4];
+ ';
+ for ($ki = 7, $i = 0; $i < 8; ++$i) {
+ $encrypt_block .= '
+ $t0 = $S0[ $R0 & 0xff] ^
+ $S1[($R0 >> 8) & 0xff] ^
+ $S2[($R0 >> 16) & 0xff] ^
+ $S3[($R0 >> 24) & 0xff];
+ $t1 = $S0[($R1 >> 24) & 0xff] ^
+ $S1[ $R1 & 0xff] ^
+ $S2[($R1 >> 8) & 0xff] ^
+ $S3[($R1 >> 16) & 0xff];
+ $R2^= ' . sprintf($safeint, '$t0 + $t1 + ' . $K[++$ki]) . ';
+ $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31);
+ $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . ';
+
+ $t0 = $S0[ $R2 & 0xff] ^
+ $S1[($R2 >> 8) & 0xff] ^
+ $S2[($R2 >> 16) & 0xff] ^
+ $S3[($R2 >> 24) & 0xff];
+ $t1 = $S0[($R3 >> 24) & 0xff] ^
+ $S1[ $R3 & 0xff] ^
+ $S2[($R3 >> 8) & 0xff] ^
+ $S3[($R3 >> 16) & 0xff];
+ $R0^= ' . sprintf($safeint, '($t0 + $t1 + ' . $K[++$ki] . ')') . ';
+ $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31);
+ $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . ';
+ ';
+ }
+ $encrypt_block .= '
+ $in = pack("V4", ' . $K[4] . ' ^ $R2,
+ ' . $K[5] . ' ^ $R3,
+ ' . $K[6] . ' ^ $R0,
+ ' . $K[7] . ' ^ $R1);
+ ';
+
+ // Generating decrypt code:
+ $decrypt_block = '
+ $in = unpack("V4", $in);
+ $R0 = ' . $K[4] . ' ^ $in[1];
+ $R1 = ' . $K[5] . ' ^ $in[2];
+ $R2 = ' . $K[6] . ' ^ $in[3];
+ $R3 = ' . $K[7] . ' ^ $in[4];
+ ';
+ for ($ki = 40, $i = 0; $i < 8; ++$i) {
+ $decrypt_block .= '
+ $t0 = $S0[$R0 & 0xff] ^
+ $S1[$R0 >> 8 & 0xff] ^
+ $S2[$R0 >> 16 & 0xff] ^
+ $S3[$R0 >> 24 & 0xff];
+ $t1 = $S0[$R1 >> 24 & 0xff] ^
+ $S1[$R1 & 0xff] ^
+ $S2[$R1 >> 8 & 0xff] ^
+ $S3[$R1 >> 16 & 0xff];
+ $R3^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . ';
+ $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31;
+ $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + ' . $K[--$ki] . ')') . ';
+
+ $t0 = $S0[$R2 & 0xff] ^
+ $S1[$R2 >> 8 & 0xff] ^
+ $S2[$R2 >> 16 & 0xff] ^
+ $S3[$R2 >> 24 & 0xff];
+ $t1 = $S0[$R3 >> 24 & 0xff] ^
+ $S1[$R3 & 0xff] ^
+ $S2[$R3 >> 8 & 0xff] ^
+ $S3[$R3 >> 16 & 0xff];
+ $R1^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . ';
+ $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31;
+ $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + ' . $K[--$ki] . ')') . ';
+ ';
+ }
+ $decrypt_block .= '
+ $in = pack("V4", ' . $K[0] . ' ^ $R2,
+ ' . $K[1] . ' ^ $R3,
+ ' . $K[2] . ' ^ $R0,
+ ' . $K[3] . ' ^ $R1);
+ ';
+
+ $this->inline_crypt = $this->createInlineCryptFunction(
+ [
+ 'init_crypt' => $init_crypt,
+ 'init_encrypt' => '',
+ 'init_decrypt' => '',
+ 'encrypt_block' => $encrypt_block,
+ 'decrypt_block' => $decrypt_block
+ ]
+ );
+ }
+}
diff --git a/Sources/Phpseclib/Crypt/index.php b/Sources/Phpseclib/Crypt/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Crypt/index.php
@@ -0,0 +1,8 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * BadConfigurationException
+ *
+ * @author Jim Wigginton
+ */
+class BadConfigurationException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/BadDecryptionException.php b/Sources/Phpseclib/Exception/BadDecryptionException.php
new file mode 100644
index 0000000000..88331dce01
--- /dev/null
+++ b/Sources/Phpseclib/Exception/BadDecryptionException.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * BadDecryptionException
+ *
+ * @author Jim Wigginton
+ */
+class BadDecryptionException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/BadModeException.php b/Sources/Phpseclib/Exception/BadModeException.php
new file mode 100644
index 0000000000..87689b2249
--- /dev/null
+++ b/Sources/Phpseclib/Exception/BadModeException.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * BadModeException
+ *
+ * @author Jim Wigginton
+ */
+class BadModeException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/ConnectionClosedException.php b/Sources/Phpseclib/Exception/ConnectionClosedException.php
new file mode 100644
index 0000000000..6aaccbad64
--- /dev/null
+++ b/Sources/Phpseclib/Exception/ConnectionClosedException.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * ConnectionClosedException
+ *
+ * @author Jim Wigginton
+ */
+class ConnectionClosedException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/FileNotFoundException.php b/Sources/Phpseclib/Exception/FileNotFoundException.php
new file mode 100644
index 0000000000..66e7270910
--- /dev/null
+++ b/Sources/Phpseclib/Exception/FileNotFoundException.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * FileNotFoundException
+ *
+ * @author Jim Wigginton
+ */
+class FileNotFoundException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/InconsistentSetupException.php b/Sources/Phpseclib/Exception/InconsistentSetupException.php
new file mode 100644
index 0000000000..23c38fb021
--- /dev/null
+++ b/Sources/Phpseclib/Exception/InconsistentSetupException.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * InconsistentSetupException
+ *
+ * @author Jim Wigginton
+ */
+class InconsistentSetupException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/InsufficientSetupException.php b/Sources/Phpseclib/Exception/InsufficientSetupException.php
new file mode 100644
index 0000000000..4f4114d707
--- /dev/null
+++ b/Sources/Phpseclib/Exception/InsufficientSetupException.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * InsufficientSetupException
+ *
+ * @author Jim Wigginton
+ */
+class InsufficientSetupException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/InvalidPacketLengthException.php b/Sources/Phpseclib/Exception/InvalidPacketLengthException.php
new file mode 100644
index 0000000000..b96ead1e36
--- /dev/null
+++ b/Sources/Phpseclib/Exception/InvalidPacketLengthException.php
@@ -0,0 +1,10 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * NoKeyLoadedException
+ *
+ * @author Jim Wigginton
+ */
+class NoKeyLoadedException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/NoSupportedAlgorithmsException.php b/Sources/Phpseclib/Exception/NoSupportedAlgorithmsException.php
new file mode 100644
index 0000000000..b3ea8f3844
--- /dev/null
+++ b/Sources/Phpseclib/Exception/NoSupportedAlgorithmsException.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * NoSupportedAlgorithmsException
+ *
+ * @author Jim Wigginton
+ */
+class NoSupportedAlgorithmsException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/TimeoutException.php b/Sources/Phpseclib/Exception/TimeoutException.php
new file mode 100644
index 0000000000..8701f8d761
--- /dev/null
+++ b/Sources/Phpseclib/Exception/TimeoutException.php
@@ -0,0 +1,10 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * UnableToConnectException
+ *
+ * @author Jim Wigginton
+ */
+class UnableToConnectException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/UnsupportedAlgorithmException.php b/Sources/Phpseclib/Exception/UnsupportedAlgorithmException.php
new file mode 100644
index 0000000000..210a9a5cee
--- /dev/null
+++ b/Sources/Phpseclib/Exception/UnsupportedAlgorithmException.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * UnsupportedAlgorithmException
+ *
+ * @author Jim Wigginton
+ */
+class UnsupportedAlgorithmException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/UnsupportedCurveException.php b/Sources/Phpseclib/Exception/UnsupportedCurveException.php
new file mode 100644
index 0000000000..99152152c8
--- /dev/null
+++ b/Sources/Phpseclib/Exception/UnsupportedCurveException.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * UnsupportedCurveException
+ *
+ * @author Jim Wigginton
+ */
+class UnsupportedCurveException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/UnsupportedFormatException.php b/Sources/Phpseclib/Exception/UnsupportedFormatException.php
new file mode 100644
index 0000000000..e207d7e213
--- /dev/null
+++ b/Sources/Phpseclib/Exception/UnsupportedFormatException.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * UnsupportedFormatException
+ *
+ * @author Jim Wigginton
+ */
+class UnsupportedFormatException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/UnsupportedOperationException.php b/Sources/Phpseclib/Exception/UnsupportedOperationException.php
new file mode 100644
index 0000000000..9a11544450
--- /dev/null
+++ b/Sources/Phpseclib/Exception/UnsupportedOperationException.php
@@ -0,0 +1,23 @@
+
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * UnsupportedOperationException
+ *
+ * @author Jim Wigginton
+ */
+class UnsupportedOperationException extends \RuntimeException
+{
+}
diff --git a/Sources/Phpseclib/Exception/index.php b/Sources/Phpseclib/Exception/index.php
new file mode 100644
index 0000000000..cc9dd08570
--- /dev/null
+++ b/Sources/Phpseclib/Exception/index.php
@@ -0,0 +1,8 @@
+
+ * @copyright 2012 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File;
+
+/**
+ * Pure-PHP ANSI Decoder
+ *
+ * @author Jim Wigginton
+ */
+class ANSI
+{
+ /**
+ * Max Width
+ *
+ * @var int
+ */
+ private $max_x;
+
+ /**
+ * Max Height
+ *
+ * @var int
+ */
+ private $max_y;
+
+ /**
+ * Max History
+ *
+ * @var int
+ */
+ private $max_history;
+
+ /**
+ * History
+ *
+ * @var array
+ */
+ private $history;
+
+ /**
+ * History Attributes
+ *
+ * @var array
+ */
+ private $history_attrs;
+
+ /**
+ * Current Column
+ *
+ * @var int
+ */
+ private $x;
+
+ /**
+ * Current Row
+ *
+ * @var int
+ */
+ private $y;
+
+ /**
+ * Old Column
+ *
+ * @var int
+ */
+ private $old_x;
+
+ /**
+ * Old Row
+ *
+ * @var int
+ */
+ private $old_y;
+
+ /**
+ * An empty attribute cell
+ *
+ * @var object
+ */
+ private $base_attr_cell;
+
+ /**
+ * The current attribute cell
+ *
+ * @var object
+ */
+ private $attr_cell;
+
+ /**
+ * An empty attribute row
+ *
+ * @var array
+ */
+ private $attr_row;
+
+ /**
+ * The current screen text
+ *
+ * @var list
+ */
+ private $screen;
+
+ /**
+ * The current screen attributes
+ *
+ * @var array
+ */
+ private $attrs;
+
+ /**
+ * Current ANSI code
+ *
+ * @var string
+ */
+ private $ansi;
+
+ /**
+ * Tokenization
+ *
+ * @var array
+ */
+ private $tokenization;
+
+ /**
+ * Default Constructor.
+ *
+ * @return ANSI
+ */
+ public function __construct()
+ {
+ $attr_cell = new \stdClass();
+ $attr_cell->bold = false;
+ $attr_cell->underline = false;
+ $attr_cell->blink = false;
+ $attr_cell->background = 'black';
+ $attr_cell->foreground = 'white';
+ $attr_cell->reverse = false;
+ $this->base_attr_cell = clone $attr_cell;
+ $this->attr_cell = clone $attr_cell;
+
+ $this->setHistory(200);
+ $this->setDimensions(80, 24);
+ }
+
+ /**
+ * Set terminal width and height
+ *
+ * Resets the screen as well
+ *
+ * @param int $x
+ * @param int $y
+ */
+ public function setDimensions($x, $y)
+ {
+ $this->max_x = $x - 1;
+ $this->max_y = $y - 1;
+ $this->x = $this->y = 0;
+ $this->history = $this->history_attrs = [];
+ $this->attr_row = array_fill(0, $this->max_x + 2, $this->base_attr_cell);
+ $this->screen = array_fill(0, $this->max_y + 1, '');
+ $this->attrs = array_fill(0, $this->max_y + 1, $this->attr_row);
+ $this->ansi = '';
+ }
+
+ /**
+ * Set the number of lines that should be logged past the terminal height
+ *
+ * @param int $history
+ */
+ public function setHistory($history)
+ {
+ $this->max_history = $history;
+ }
+
+ /**
+ * Load a string
+ *
+ * @param string $source
+ */
+ public function loadString($source)
+ {
+ $this->setDimensions($this->max_x + 1, $this->max_y + 1);
+ $this->appendString($source);
+ }
+
+ /**
+ * Appdend a string
+ *
+ * @param string $source
+ */
+ public function appendString($source)
+ {
+ $this->tokenization = [''];
+ for ($i = 0; $i < strlen($source); $i++) {
+ if (strlen($this->ansi)) {
+ $this->ansi .= $source[$i];
+ $chr = ord($source[$i]);
+ // http://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements
+ // single character CSI's not currently supported
+ switch (true) {
+ case $this->ansi == "\x1B=":
+ $this->ansi = '';
+ continue 2;
+ case strlen($this->ansi) == 2 && $chr >= 64 && $chr <= 95 && $chr != ord('['):
+ case strlen($this->ansi) > 2 && $chr >= 64 && $chr <= 126:
+ break;
+ default:
+ continue 2;
+ }
+ $this->tokenization[] = $this->ansi;
+ $this->tokenization[] = '';
+ // http://ascii-table.com/ansi-escape-sequences-vt-100.php
+ switch ($this->ansi) {
+ case "\x1B[H": // Move cursor to upper left corner
+ $this->old_x = $this->x;
+ $this->old_y = $this->y;
+ $this->x = $this->y = 0;
+ break;
+ case "\x1B[J": // Clear screen from cursor down
+ $this->history = array_merge($this->history, array_slice(array_splice($this->screen, $this->y + 1), 0, $this->old_y));
+ $this->screen = array_merge($this->screen, array_fill($this->y, $this->max_y, ''));
+
+ $this->history_attrs = array_merge($this->history_attrs, array_slice(array_splice($this->attrs, $this->y + 1), 0, $this->old_y));
+ $this->attrs = array_merge($this->attrs, array_fill($this->y, $this->max_y, $this->attr_row));
+
+ if (count($this->history) == $this->max_history) {
+ array_shift($this->history);
+ array_shift($this->history_attrs);
+ }
+ // fall-through
+ case "\x1B[K": // Clear screen from cursor right
+ $this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);
+
+ array_splice($this->attrs[$this->y], $this->x + 1, $this->max_x - $this->x, array_fill($this->x, $this->max_x - ($this->x - 1), $this->base_attr_cell));
+ break;
+ case "\x1B[2K": // Clear entire line
+ $this->screen[$this->y] = str_repeat(' ', $this->x);
+ $this->attrs[$this->y] = $this->attr_row;
+ break;
+ case "\x1B[?1h": // set cursor key to application
+ case "\x1B[?25h": // show the cursor
+ case "\x1B(B": // set united states g0 character set
+ break;
+ case "\x1BE": // Move to next line
+ $this->newLine();
+ $this->x = 0;
+ break;
+ default:
+ switch (true) {
+ case preg_match('#\x1B\[(\d+)B#', $this->ansi, $match): // Move cursor down n lines
+ $this->old_y = $this->y;
+ $this->y += (int) $match[1];
+ break;
+ case preg_match('#\x1B\[(\d+);(\d+)H#', $this->ansi, $match): // Move cursor to screen location v,h
+ $this->old_x = $this->x;
+ $this->old_y = $this->y;
+ $this->x = $match[2] - 1;
+ $this->y = (int) $match[1] - 1;
+ break;
+ case preg_match('#\x1B\[(\d+)C#', $this->ansi, $match): // Move cursor right n lines
+ $this->old_x = $this->x;
+ $this->x += $match[1];
+ break;
+ case preg_match('#\x1B\[(\d+)D#', $this->ansi, $match): // Move cursor left n lines
+ $this->old_x = $this->x;
+ $this->x -= $match[1];
+ if ($this->x < 0) {
+ $this->x = 0;
+ }
+ break;
+ case preg_match('#\x1B\[(\d+);(\d+)r#', $this->ansi, $match): // Set top and bottom lines of a window
+ break;
+ case preg_match('#\x1B\[(\d*(?:;\d*)*)m#', $this->ansi, $match): // character attributes
+ $attr_cell = &$this->attr_cell;
+ $mods = explode(';', $match[1]);
+ foreach ($mods as $mod) {
+ switch ($mod) {
+ case '':
+ case '0': // Turn off character attributes
+ $attr_cell = clone $this->base_attr_cell;
+ break;
+ case '1': // Turn bold mode on
+ $attr_cell->bold = true;
+ break;
+ case '4': // Turn underline mode on
+ $attr_cell->underline = true;
+ break;
+ case '5': // Turn blinking mode on
+ $attr_cell->blink = true;
+ break;
+ case '7': // Turn reverse video on
+ $attr_cell->reverse = !$attr_cell->reverse;
+ $temp = $attr_cell->background;
+ $attr_cell->background = $attr_cell->foreground;
+ $attr_cell->foreground = $temp;
+ break;
+ default: // set colors
+ //$front = $attr_cell->reverse ? &$attr_cell->background : &$attr_cell->foreground;
+ $front = &$attr_cell->{ $attr_cell->reverse ? 'background' : 'foreground' };
+ //$back = $attr_cell->reverse ? &$attr_cell->foreground : &$attr_cell->background;
+ $back = &$attr_cell->{ $attr_cell->reverse ? 'foreground' : 'background' };
+ switch ($mod) {
+ // @codingStandardsIgnoreStart
+ case '30': $front = 'black'; break;
+ case '31': $front = 'red'; break;
+ case '32': $front = 'green'; break;
+ case '33': $front = 'yellow'; break;
+ case '34': $front = 'blue'; break;
+ case '35': $front = 'magenta'; break;
+ case '36': $front = 'cyan'; break;
+ case '37': $front = 'white'; break;
+
+ case '40': $back = 'black'; break;
+ case '41': $back = 'red'; break;
+ case '42': $back = 'green'; break;
+ case '43': $back = 'yellow'; break;
+ case '44': $back = 'blue'; break;
+ case '45': $back = 'magenta'; break;
+ case '46': $back = 'cyan'; break;
+ case '47': $back = 'white'; break;
+ // @codingStandardsIgnoreEnd
+
+ default:
+ //user_error('Unsupported attribute: ' . $mod);
+ $this->ansi = '';
+ break 2;
+ }
+ }
+ }
+ break;
+ default:
+ //user_error("{$this->ansi} is unsupported\r\n");
+ }
+ }
+ $this->ansi = '';
+ continue;
+ }
+
+ $this->tokenization[count($this->tokenization) - 1] .= $source[$i];
+ switch ($source[$i]) {
+ case "\r":
+ $this->x = 0;
+ break;
+ case "\n":
+ $this->newLine();
+ break;
+ case "\x08": // backspace
+ if ($this->x) {
+ $this->x--;
+ $this->attrs[$this->y][$this->x] = clone $this->base_attr_cell;
+ $this->screen[$this->y] = substr_replace(
+ $this->screen[$this->y],
+ $source[$i],
+ $this->x,
+ 1
+ );
+ }
+ break;
+ case "\x0F": // shift
+ break;
+ case "\x1B": // start ANSI escape code
+ $this->tokenization[count($this->tokenization) - 1] = substr($this->tokenization[count($this->tokenization) - 1], 0, -1);
+ //if (!strlen($this->tokenization[count($this->tokenization) - 1])) {
+ // array_pop($this->tokenization);
+ //}
+ $this->ansi .= "\x1B";
+ break;
+ default:
+ $this->attrs[$this->y][$this->x] = clone $this->attr_cell;
+ if ($this->x > strlen($this->screen[$this->y])) {
+ $this->screen[$this->y] = str_repeat(' ', $this->x);
+ }
+ $this->screen[$this->y] = substr_replace(
+ $this->screen[$this->y],
+ $source[$i],
+ $this->x,
+ 1
+ );
+
+ if ($this->x > $this->max_x) {
+ $this->x = 0;
+ $this->newLine();
+ } else {
+ $this->x++;
+ }
+ }
+ }
+ }
+
+ /**
+ * Add a new line
+ *
+ * Also update the $this->screen and $this->history buffers
+ *
+ */
+ private function newLine()
+ {
+ //if ($this->y < $this->max_y) {
+ // $this->y++;
+ //}
+
+ while ($this->y >= $this->max_y) {
+ $this->history = array_merge($this->history, [array_shift($this->screen)]);
+ $this->screen[] = '';
+
+ $this->history_attrs = array_merge($this->history_attrs, [array_shift($this->attrs)]);
+ $this->attrs[] = $this->attr_row;
+
+ if (count($this->history) >= $this->max_history) {
+ array_shift($this->history);
+ array_shift($this->history_attrs);
+ }
+
+ $this->y--;
+ }
+ $this->y++;
+ }
+
+ /**
+ * Returns the current coordinate without preformating
+ *
+ * @param \stdClass $last_attr
+ * @param \stdClass $cur_attr
+ * @param string $char
+ * @return string
+ */
+ private function processCoordinate(\stdClass $last_attr, \stdClass $cur_attr, $char)
+ {
+ $output = '';
+
+ if ($last_attr != $cur_attr) {
+ $close = $open = '';
+ if ($last_attr->foreground != $cur_attr->foreground) {
+ if ($cur_attr->foreground != 'white') {
+ $open .= '';
+ }
+ if ($last_attr->foreground != 'white') {
+ $close = '' . $close;
+ }
+ }
+ if ($last_attr->background != $cur_attr->background) {
+ if ($cur_attr->background != 'black') {
+ $open .= '';
+ }
+ if ($last_attr->background != 'black') {
+ $close = '' . $close;
+ }
+ }
+ if ($last_attr->bold != $cur_attr->bold) {
+ if ($cur_attr->bold) {
+ $open .= '';
+ } else {
+ $close = '' . $close;
+ }
+ }
+ if ($last_attr->underline != $cur_attr->underline) {
+ if ($cur_attr->underline) {
+ $open .= '';
+ } else {
+ $close = '' . $close;
+ }
+ }
+ if ($last_attr->blink != $cur_attr->blink) {
+ if ($cur_attr->blink) {
+ $open .= '' . $close;
+ }
+ }
+ $output .= $close . $open;
+ }
+
+ $output .= htmlspecialchars($char);
+
+ return $output;
+ }
+
+ /**
+ * Returns the current screen without preformating
+ *
+ * @return string
+ */
+ private function getScreenHelper()
+ {
+ $output = '';
+ $last_attr = $this->base_attr_cell;
+ for ($i = 0; $i <= $this->max_y; $i++) {
+ for ($j = 0; $j <= $this->max_x; $j++) {
+ $cur_attr = $this->attrs[$i][$j];
+ $output .= $this->processCoordinate($last_attr, $cur_attr, isset($this->screen[$i][$j]) ? $this->screen[$i][$j] : '');
+ $last_attr = $this->attrs[$i][$j];
+ }
+ $output .= "\r\n";
+ }
+ $output = substr($output, 0, -2);
+ // close any remaining open tags
+ $output .= $this->processCoordinate($last_attr, $this->base_attr_cell, '');
+ return rtrim($output);
+ }
+
+ /**
+ * Returns the current screen
+ *
+ * @return string
+ */
+ public function getScreen()
+ {
+ return '' . $this->getScreenHelper() . '
';
+ }
+
+ /**
+ * Returns the current screen and the x previous lines
+ *
+ * @return string
+ */
+ public function getHistory()
+ {
+ $scrollback = '';
+ $last_attr = $this->base_attr_cell;
+ for ($i = 0; $i < count($this->history); $i++) {
+ for ($j = 0; $j <= $this->max_x + 1; $j++) {
+ $cur_attr = $this->history_attrs[$i][$j];
+ $scrollback .= $this->processCoordinate($last_attr, $cur_attr, isset($this->history[$i][$j]) ? $this->history[$i][$j] : '');
+ $last_attr = $this->history_attrs[$i][$j];
+ }
+ $scrollback .= "\r\n";
+ }
+ $base_attr_cell = $this->base_attr_cell;
+ $this->base_attr_cell = $last_attr;
+ $scrollback .= $this->getScreen();
+ $this->base_attr_cell = $base_attr_cell;
+
+ return '' . $scrollback . '
';
+ }
+}
diff --git a/Sources/Phpseclib/File/ASN1.php b/Sources/Phpseclib/File/ASN1.php
new file mode 100644
index 0000000000..2f1fb8a673
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1.php
@@ -0,0 +1,1526 @@
+
+ * @copyright 2012 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\File\ASN1\Element;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Pure-PHP ASN.1 Parser
+ *
+ * @author Jim Wigginton
+ */
+abstract class ASN1
+{
+ // Tag Classes
+ // http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
+ const CLASS_UNIVERSAL = 0;
+ const CLASS_APPLICATION = 1;
+ const CLASS_CONTEXT_SPECIFIC = 2;
+ const CLASS_PRIVATE = 3;
+
+ // Tag Classes
+ // http://www.obj-sys.com/asn1tutorial/node124.html
+ const TYPE_BOOLEAN = 1;
+ const TYPE_INTEGER = 2;
+ const TYPE_BIT_STRING = 3;
+ const TYPE_OCTET_STRING = 4;
+ const TYPE_NULL = 5;
+ const TYPE_OBJECT_IDENTIFIER = 6;
+ //const TYPE_OBJECT_DESCRIPTOR = 7;
+ //const TYPE_INSTANCE_OF = 8; // EXTERNAL
+ const TYPE_REAL = 9;
+ const TYPE_ENUMERATED = 10;
+ //const TYPE_EMBEDDED = 11;
+ const TYPE_UTF8_STRING = 12;
+ //const TYPE_RELATIVE_OID = 13;
+ const TYPE_SEQUENCE = 16; // SEQUENCE OF
+ const TYPE_SET = 17; // SET OF
+
+ // More Tag Classes
+ // http://www.obj-sys.com/asn1tutorial/node10.html
+ const TYPE_NUMERIC_STRING = 18;
+ const TYPE_PRINTABLE_STRING = 19;
+ const TYPE_TELETEX_STRING = 20; // T61String
+ const TYPE_VIDEOTEX_STRING = 21;
+ const TYPE_IA5_STRING = 22;
+ const TYPE_UTC_TIME = 23;
+ const TYPE_GENERALIZED_TIME = 24;
+ const TYPE_GRAPHIC_STRING = 25;
+ const TYPE_VISIBLE_STRING = 26; // ISO646String
+ const TYPE_GENERAL_STRING = 27;
+ const TYPE_UNIVERSAL_STRING = 28;
+ //const TYPE_CHARACTER_STRING = 29;
+ const TYPE_BMP_STRING = 30;
+
+ // Tag Aliases
+ // These tags are kinda place holders for other tags.
+ const TYPE_CHOICE = -1;
+ const TYPE_ANY = -2;
+
+ /**
+ * ASN.1 object identifiers
+ *
+ * @var array
+ * @link http://en.wikipedia.org/wiki/Object_identifier
+ */
+ private static $oids = [];
+
+ /**
+ * ASN.1 object identifier reverse mapping
+ *
+ * @var array
+ */
+ private static $reverseOIDs = [];
+
+ /**
+ * Default date format
+ *
+ * @var string
+ * @link http://php.net/class.datetime
+ */
+ private static $format = 'D, d M Y H:i:s O';
+
+ /**
+ * Filters
+ *
+ * If the mapping type is self::TYPE_ANY what do we actually encode it as?
+ *
+ * @var array
+ * @see self::encode_der()
+ */
+ private static $filters;
+
+ /**
+ * Current Location of most recent ASN.1 encode process
+ *
+ * Useful for debug purposes
+ *
+ * @var array
+ * @see self::encode_der()
+ */
+ private static $location;
+
+ /**
+ * DER Encoded String
+ *
+ * In case we need to create ASN1\Element object's..
+ *
+ * @var string
+ * @see self::decodeDER()
+ */
+ private static $encoded;
+
+ /**
+ * Type mapping table for the ANY type.
+ *
+ * Structured or unknown types are mapped to a \phpseclib3\File\ASN1\Element.
+ * Unambiguous types get the direct mapping (int/real/bool).
+ * Others are mapped as a choice, with an extra indexing level.
+ *
+ * @var array
+ */
+ const ANY_MAP = [
+ self::TYPE_BOOLEAN => true,
+ self::TYPE_INTEGER => true,
+ self::TYPE_BIT_STRING => 'bitString',
+ self::TYPE_OCTET_STRING => 'octetString',
+ self::TYPE_NULL => 'null',
+ self::TYPE_OBJECT_IDENTIFIER => 'objectIdentifier',
+ self::TYPE_REAL => true,
+ self::TYPE_ENUMERATED => 'enumerated',
+ self::TYPE_UTF8_STRING => 'utf8String',
+ self::TYPE_NUMERIC_STRING => 'numericString',
+ self::TYPE_PRINTABLE_STRING => 'printableString',
+ self::TYPE_TELETEX_STRING => 'teletexString',
+ self::TYPE_VIDEOTEX_STRING => 'videotexString',
+ self::TYPE_IA5_STRING => 'ia5String',
+ self::TYPE_UTC_TIME => 'utcTime',
+ self::TYPE_GENERALIZED_TIME => 'generalTime',
+ self::TYPE_GRAPHIC_STRING => 'graphicString',
+ self::TYPE_VISIBLE_STRING => 'visibleString',
+ self::TYPE_GENERAL_STRING => 'generalString',
+ self::TYPE_UNIVERSAL_STRING => 'universalString',
+ //self::TYPE_CHARACTER_STRING => 'characterString',
+ self::TYPE_BMP_STRING => 'bmpString'
+ ];
+
+ /**
+ * String type to character size mapping table.
+ *
+ * Non-convertable types are absent from this table.
+ * size == 0 indicates variable length encoding.
+ *
+ * @var array
+ */
+ const STRING_TYPE_SIZE = [
+ self::TYPE_UTF8_STRING => 0,
+ self::TYPE_BMP_STRING => 2,
+ self::TYPE_UNIVERSAL_STRING => 4,
+ self::TYPE_PRINTABLE_STRING => 1,
+ self::TYPE_TELETEX_STRING => 1,
+ self::TYPE_IA5_STRING => 1,
+ self::TYPE_VISIBLE_STRING => 1,
+ ];
+
+ /**
+ * Parse BER-encoding
+ *
+ * Serves a similar purpose to openssl's asn1parse
+ *
+ * @param Element|string $encoded
+ * @return ?array
+ */
+ public static function decodeBER($encoded)
+ {
+ if ($encoded instanceof Element) {
+ $encoded = $encoded->element;
+ }
+
+ self::$encoded = $encoded;
+
+ $decoded = self::decode_ber($encoded);
+ if ($decoded === false) {
+ return null;
+ }
+
+ return [$decoded];
+ }
+
+ /**
+ * Parse BER-encoding (Helper function)
+ *
+ * Sometimes we want to get the BER encoding of a particular tag. $start lets us do that without having to reencode.
+ * $encoded is passed by reference for the recursive calls done for self::TYPE_BIT_STRING and
+ * self::TYPE_OCTET_STRING. In those cases, the indefinite length is used.
+ *
+ * @param string $encoded
+ * @param int $start
+ * @param int $encoded_pos
+ * @return array|bool
+ */
+ private static function decode_ber($encoded, $start = 0, $encoded_pos = 0)
+ {
+ $current = ['start' => $start];
+
+ if (!isset($encoded[$encoded_pos])) {
+ return false;
+ }
+ $type = ord($encoded[$encoded_pos++]);
+ $startOffset = 1;
+
+ $constructed = ($type >> 5) & 1;
+
+ $tag = $type & 0x1F;
+ if ($tag == 0x1F) {
+ $tag = 0;
+ // process septets (since the eighth bit is ignored, it's not an octet)
+ do {
+ if (!isset($encoded[$encoded_pos])) {
+ return false;
+ }
+ $temp = ord($encoded[$encoded_pos++]);
+ $startOffset++;
+ $loop = $temp >> 7;
+ $tag <<= 7;
+ $temp &= 0x7F;
+ // "bits 7 to 1 of the first subsequent octet shall not all be zero"
+ if ($startOffset == 2 && $temp == 0) {
+ return false;
+ }
+ $tag |= $temp;
+ } while ($loop);
+ }
+
+ $start += $startOffset;
+
+ // Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
+ if (!isset($encoded[$encoded_pos])) {
+ return false;
+ }
+ $length = ord($encoded[$encoded_pos++]);
+ $start++;
+ if ($length == 0x80) { // indefinite length
+ // "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
+ // immediately available." -- paragraph 8.1.3.2.c
+ $length = strlen($encoded) - $encoded_pos;
+ } elseif ($length & 0x80) { // definite length, long form
+ // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
+ // support it up to four.
+ $length &= 0x7F;
+ $temp = substr($encoded, $encoded_pos, $length);
+ $encoded_pos += $length;
+ // tags of indefinte length don't really have a header length; this length includes the tag
+ $current += ['headerlength' => $length + 2];
+ $start += $length;
+ $length = unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4))['length'];
+ } else {
+ $current += ['headerlength' => 2];
+ }
+
+ if ($length > (strlen($encoded) - $encoded_pos)) {
+ return false;
+ }
+
+ $content = substr($encoded, $encoded_pos, $length);
+ $content_pos = 0;
+
+ // at this point $length can be overwritten. it's only accurate for definite length things as is
+
+ /* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
+ built-in types. It defines an application-independent data type that must be distinguishable from all other
+ data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
+ have a wide, scattered use within a particular presentation context. PRIVATE distinguishes data types within
+ a particular organization or country. CONTEXT-SPECIFIC distinguishes members of a sequence or set, the
+ alternatives of a CHOICE, or universally tagged set members. Only the class number appears in braces for this
+ data type; the term CONTEXT-SPECIFIC does not appear.
+
+ -- http://www.obj-sys.com/asn1tutorial/node12.html */
+ $class = ($type >> 6) & 3;
+ switch ($class) {
+ case self::CLASS_APPLICATION:
+ case self::CLASS_PRIVATE:
+ case self::CLASS_CONTEXT_SPECIFIC:
+ if (!$constructed) {
+ return [
+ 'type' => $class,
+ 'constant' => $tag,
+ 'content' => $content,
+ 'length' => $length + $start - $current['start']
+ ] + $current;
+ }
+
+ $newcontent = [];
+ $remainingLength = $length;
+ while ($remainingLength > 0) {
+ $temp = self::decode_ber($content, $start, $content_pos);
+ if ($temp === false) {
+ break;
+ }
+ $length = $temp['length'];
+ // end-of-content octets - see paragraph 8.1.5
+ if (substr($content, $content_pos + $length, 2) == "\0\0") {
+ $length += 2;
+ $start += $length;
+ $newcontent[] = $temp;
+ break;
+ }
+ $start += $length;
+ $remainingLength -= $length;
+ $newcontent[] = $temp;
+ $content_pos += $length;
+ }
+
+ return [
+ 'type' => $class,
+ 'constant' => $tag,
+ // the array encapsulation is for BC with the old format
+ 'content' => $newcontent,
+ // the only time when $content['headerlength'] isn't defined is when the length is indefinite.
+ // the absence of $content['headerlength'] is how we know if something is indefinite or not.
+ // technically, it could be defined to be 2 and then another indicator could be used but whatever.
+ 'length' => $start - $current['start']
+ ] + $current;
+ }
+
+ $current += ['type' => $tag];
+
+ // decode UNIVERSAL tags
+ switch ($tag) {
+ case self::TYPE_BOOLEAN:
+ // "The contents octets shall consist of a single octet." -- paragraph 8.2.1
+ if ($constructed || strlen($content) != 1) {
+ return false;
+ }
+ $current['content'] = (bool) ord($content[$content_pos]);
+ break;
+ case self::TYPE_INTEGER:
+ case self::TYPE_ENUMERATED:
+ if ($constructed) {
+ return false;
+ }
+ $current['content'] = new BigInteger(substr($content, $content_pos), -256);
+ break;
+ case self::TYPE_REAL: // not currently supported
+ return false;
+ case self::TYPE_BIT_STRING:
+ // The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
+ // the number of unused bits in the final subsequent octet. The number shall be in the range zero to
+ // seven.
+ if (!$constructed) {
+ $current['content'] = substr($content, $content_pos);
+ } else {
+ $temp = self::decode_ber($content, $start, $content_pos);
+ if ($temp === false) {
+ return false;
+ }
+ $length -= (strlen($content) - $content_pos);
+ $last = count($temp) - 1;
+ for ($i = 0; $i < $last; $i++) {
+ // all subtags should be bit strings
+ if ($temp[$i]['type'] != self::TYPE_BIT_STRING) {
+ return false;
+ }
+ $current['content'] .= substr($temp[$i]['content'], 1);
+ }
+ // all subtags should be bit strings
+ if ($temp[$last]['type'] != self::TYPE_BIT_STRING) {
+ return false;
+ }
+ $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
+ }
+ break;
+ case self::TYPE_OCTET_STRING:
+ if (!$constructed) {
+ $current['content'] = substr($content, $content_pos);
+ } else {
+ $current['content'] = '';
+ $length = 0;
+ while (substr($content, $content_pos, 2) != "\0\0") {
+ $temp = self::decode_ber($content, $length + $start, $content_pos);
+ if ($temp === false) {
+ return false;
+ }
+ $content_pos += $temp['length'];
+ // all subtags should be octet strings
+ if ($temp['type'] != self::TYPE_OCTET_STRING) {
+ return false;
+ }
+ $current['content'] .= $temp['content'];
+ $length += $temp['length'];
+ }
+ if (substr($content, $content_pos, 2) == "\0\0") {
+ $length += 2; // +2 for the EOC
+ }
+ }
+ break;
+ case self::TYPE_NULL:
+ // "The contents octets shall not contain any octets." -- paragraph 8.8.2
+ if ($constructed || strlen($content)) {
+ return false;
+ }
+ break;
+ case self::TYPE_SEQUENCE:
+ case self::TYPE_SET:
+ if (!$constructed) {
+ return false;
+ }
+ $offset = 0;
+ $current['content'] = [];
+ $content_len = strlen($content);
+ while ($content_pos < $content_len) {
+ // if indefinite length construction was used and we have an end-of-content string next
+ // see paragraphs 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2
+ if (!isset($current['headerlength']) && substr($content, $content_pos, 2) == "\0\0") {
+ $length = $offset + 2; // +2 for the EOC
+ break 2;
+ }
+ $temp = self::decode_ber($content, $start + $offset, $content_pos);
+ if ($temp === false) {
+ return false;
+ }
+ $content_pos += $temp['length'];
+ $current['content'][] = $temp;
+ $offset += $temp['length'];
+ }
+ break;
+ case self::TYPE_OBJECT_IDENTIFIER:
+ if ($constructed) {
+ return false;
+ }
+ $current['content'] = self::decodeOID(substr($content, $content_pos));
+ if ($current['content'] === false) {
+ return false;
+ }
+ break;
+ /* Each character string type shall be encoded as if it had been declared:
+ [UNIVERSAL x] IMPLICIT OCTET STRING
+
+ -- X.690-0207.pdf#page=23 (paragraph 8.21.3)
+
+ Per that, we're not going to do any validation. If there are any illegal characters in the string,
+ we don't really care */
+ case self::TYPE_NUMERIC_STRING:
+ // 0,1,2,3,4,5,6,7,8,9, and space
+ case self::TYPE_PRINTABLE_STRING:
+ // Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma,
+ // hyphen, full stop, solidus, colon, equal sign, question mark
+ case self::TYPE_TELETEX_STRING:
+ // The Teletex character set in CCITT's T61, space, and delete
+ // see http://en.wikipedia.org/wiki/Teletex#Character_sets
+ case self::TYPE_VIDEOTEX_STRING:
+ // The Videotex character set in CCITT's T.100 and T.101, space, and delete
+ case self::TYPE_VISIBLE_STRING:
+ // Printing character sets of international ASCII, and space
+ case self::TYPE_IA5_STRING:
+ // International Alphabet 5 (International ASCII)
+ case self::TYPE_GRAPHIC_STRING:
+ // All registered G sets, and space
+ case self::TYPE_GENERAL_STRING:
+ // All registered C and G sets, space and delete
+ case self::TYPE_UTF8_STRING:
+ // ????
+ case self::TYPE_BMP_STRING:
+ if ($constructed) {
+ return false;
+ }
+ $current['content'] = substr($content, $content_pos);
+ break;
+ case self::TYPE_UTC_TIME:
+ case self::TYPE_GENERALIZED_TIME:
+ if ($constructed) {
+ return false;
+ }
+ $current['content'] = self::decodeTime(substr($content, $content_pos), $tag);
+ break;
+ default:
+ return false;
+ }
+
+ $start += $length;
+
+ // ie. length is the length of the full TLV encoding - it's not just the length of the value
+ return $current + ['length' => $start - $current['start']];
+ }
+
+ /**
+ * ASN.1 Map
+ *
+ * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
+ *
+ * "Special" mappings may be applied on a per tag-name basis via $special.
+ *
+ * @param array $decoded
+ * @param array $mapping
+ * @param array $special
+ * @return array|bool|Element|string|null
+ */
+ public static function asn1map(array $decoded, $mapping, $special = [])
+ {
+ if (isset($mapping['explicit']) && is_array($decoded['content'])) {
+ $decoded = $decoded['content'][0];
+ }
+
+ switch (true) {
+ case $mapping['type'] == self::TYPE_ANY:
+ $intype = $decoded['type'];
+ // !isset(self::ANY_MAP[$intype]) produces a fatal error on PHP 5.6
+ if (isset($decoded['constant']) || !array_key_exists($intype, self::ANY_MAP) || (ord(self::$encoded[$decoded['start']]) & 0x20)) {
+ return new Element(substr(self::$encoded, $decoded['start'], $decoded['length']));
+ }
+ $inmap = self::ANY_MAP[$intype];
+ if (is_string($inmap)) {
+ return [$inmap => self::asn1map($decoded, ['type' => $intype] + $mapping, $special)];
+ }
+ break;
+ case $mapping['type'] == self::TYPE_CHOICE:
+ foreach ($mapping['children'] as $key => $option) {
+ switch (true) {
+ case isset($option['constant']) && $option['constant'] == $decoded['constant']:
+ case !isset($option['constant']) && $option['type'] == $decoded['type']:
+ $value = self::asn1map($decoded, $option, $special);
+ break;
+ case !isset($option['constant']) && $option['type'] == self::TYPE_CHOICE:
+ $v = self::asn1map($decoded, $option, $special);
+ if (isset($v)) {
+ $value = $v;
+ }
+ }
+ if (isset($value)) {
+ if (isset($special[$key])) {
+ $value = $special[$key]($value);
+ }
+ return [$key => $value];
+ }
+ }
+ return null;
+ case isset($mapping['implicit']):
+ case isset($mapping['explicit']):
+ case $decoded['type'] == $mapping['type']:
+ break;
+ default:
+ // if $decoded['type'] and $mapping['type'] are both strings, but different types of strings,
+ // let it through
+ switch (true) {
+ case $decoded['type'] < 18: // self::TYPE_NUMERIC_STRING == 18
+ case $decoded['type'] > 30: // self::TYPE_BMP_STRING == 30
+ case $mapping['type'] < 18:
+ case $mapping['type'] > 30:
+ return null;
+ }
+ }
+
+ if (isset($mapping['implicit'])) {
+ $decoded['type'] = $mapping['type'];
+ }
+
+ switch ($decoded['type']) {
+ case self::TYPE_SEQUENCE:
+ $map = [];
+
+ // ignore the min and max
+ if (isset($mapping['min']) && isset($mapping['max'])) {
+ $child = $mapping['children'];
+ foreach ($decoded['content'] as $content) {
+ if (($map[] = self::asn1map($content, $child, $special)) === null) {
+ return null;
+ }
+ }
+
+ return $map;
+ }
+
+ $n = count($decoded['content']);
+ $i = 0;
+
+ foreach ($mapping['children'] as $key => $child) {
+ $maymatch = $i < $n; // Match only existing input.
+ if ($maymatch) {
+ $temp = $decoded['content'][$i];
+
+ if ($child['type'] != self::TYPE_CHOICE) {
+ // Get the mapping and input class & constant.
+ $childClass = $tempClass = self::CLASS_UNIVERSAL;
+ $constant = null;
+ if (isset($temp['constant'])) {
+ $tempClass = $temp['type'];
+ }
+ if (isset($child['class'])) {
+ $childClass = $child['class'];
+ $constant = $child['cast'];
+ } elseif (isset($child['constant'])) {
+ $childClass = self::CLASS_CONTEXT_SPECIFIC;
+ $constant = $child['constant'];
+ }
+
+ if (isset($constant) && isset($temp['constant'])) {
+ // Can only match if constants and class match.
+ $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
+ } else {
+ // Can only match if no constant expected and type matches or is generic.
+ $maymatch = !isset($child['constant']) && array_search($child['type'], [$temp['type'], self::TYPE_ANY, self::TYPE_CHOICE]) !== false;
+ }
+ }
+ }
+
+ if ($maymatch) {
+ // Attempt submapping.
+ $candidate = self::asn1map($temp, $child, $special);
+ $maymatch = $candidate !== null;
+ }
+
+ if ($maymatch) {
+ // Got the match: use it.
+ if (isset($special[$key])) {
+ $candidate = $special[$key]($candidate);
+ }
+ $map[$key] = $candidate;
+ $i++;
+ } elseif (isset($child['default'])) {
+ $map[$key] = $child['default'];
+ } elseif (!isset($child['optional'])) {
+ return null; // Syntax error.
+ }
+ }
+
+ // Fail mapping if all input items have not been consumed.
+ return $i < $n ? null : $map;
+
+ // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
+ case self::TYPE_SET:
+ $map = [];
+
+ // ignore the min and max
+ if (isset($mapping['min']) && isset($mapping['max'])) {
+ $child = $mapping['children'];
+ foreach ($decoded['content'] as $content) {
+ if (($map[] = self::asn1map($content, $child, $special)) === null) {
+ return null;
+ }
+ }
+
+ return $map;
+ }
+
+ for ($i = 0; $i < count($decoded['content']); $i++) {
+ $temp = $decoded['content'][$i];
+ $tempClass = self::CLASS_UNIVERSAL;
+ if (isset($temp['constant'])) {
+ $tempClass = $temp['type'];
+ }
+
+ foreach ($mapping['children'] as $key => $child) {
+ if (isset($map[$key])) {
+ continue;
+ }
+ $maymatch = true;
+ if ($child['type'] != self::TYPE_CHOICE) {
+ $childClass = self::CLASS_UNIVERSAL;
+ $constant = null;
+ if (isset($child['class'])) {
+ $childClass = $child['class'];
+ $constant = $child['cast'];
+ } elseif (isset($child['constant'])) {
+ $childClass = self::CLASS_CONTEXT_SPECIFIC;
+ $constant = $child['constant'];
+ }
+
+ if (isset($constant) && isset($temp['constant'])) {
+ // Can only match if constants and class match.
+ $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
+ } else {
+ // Can only match if no constant expected and type matches or is generic.
+ $maymatch = !isset($child['constant']) && array_search($child['type'], [$temp['type'], self::TYPE_ANY, self::TYPE_CHOICE]) !== false;
+ }
+ }
+
+ if ($maymatch) {
+ // Attempt submapping.
+ $candidate = self::asn1map($temp, $child, $special);
+ $maymatch = $candidate !== null;
+ }
+
+ if (!$maymatch) {
+ break;
+ }
+
+ // Got the match: use it.
+ if (isset($special[$key])) {
+ $candidate = $special[$key]($candidate);
+ }
+ $map[$key] = $candidate;
+ break;
+ }
+ }
+
+ foreach ($mapping['children'] as $key => $child) {
+ if (!isset($map[$key])) {
+ if (isset($child['default'])) {
+ $map[$key] = $child['default'];
+ } elseif (!isset($child['optional'])) {
+ return null;
+ }
+ }
+ }
+ return $map;
+ case self::TYPE_OBJECT_IDENTIFIER:
+ return isset(self::$oids[$decoded['content']]) ? self::$oids[$decoded['content']] : $decoded['content'];
+ case self::TYPE_UTC_TIME:
+ case self::TYPE_GENERALIZED_TIME:
+ // for explicitly tagged optional stuff
+ if (is_array($decoded['content'])) {
+ $decoded['content'] = $decoded['content'][0]['content'];
+ }
+ // for implicitly tagged optional stuff
+ // in theory, doing isset($mapping['implicit']) would work but malformed certs do exist
+ // in the wild that OpenSSL decodes without issue so we'll support them as well
+ if (!is_object($decoded['content'])) {
+ $decoded['content'] = self::decodeTime($decoded['content'], $decoded['type']);
+ }
+ return $decoded['content'] ? $decoded['content']->format(self::$format) : false;
+ case self::TYPE_BIT_STRING:
+ if (isset($mapping['mapping'])) {
+ $offset = ord($decoded['content'][0]);
+ $size = (strlen($decoded['content']) - 1) * 8 - $offset;
+ /*
+ From X.680-0207.pdf#page=46 (21.7):
+
+ "When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove)
+ arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should
+ therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
+ 0 bits."
+ */
+ $bits = count($mapping['mapping']) == $size ? [] : array_fill(0, count($mapping['mapping']) - $size, false);
+ for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) {
+ $current = ord($decoded['content'][$i]);
+ for ($j = $offset; $j < 8; $j++) {
+ $bits[] = (bool) ($current & (1 << $j));
+ }
+ $offset = 0;
+ }
+ $values = [];
+ $map = array_reverse($mapping['mapping']);
+ foreach ($map as $i => $value) {
+ if ($bits[$i]) {
+ $values[] = $value;
+ }
+ }
+ return $values;
+ }
+ // fall-through
+ case self::TYPE_OCTET_STRING:
+ return $decoded['content'];
+ case self::TYPE_NULL:
+ return '';
+ case self::TYPE_BOOLEAN:
+ case self::TYPE_NUMERIC_STRING:
+ case self::TYPE_PRINTABLE_STRING:
+ case self::TYPE_TELETEX_STRING:
+ case self::TYPE_VIDEOTEX_STRING:
+ case self::TYPE_IA5_STRING:
+ case self::TYPE_GRAPHIC_STRING:
+ case self::TYPE_VISIBLE_STRING:
+ case self::TYPE_GENERAL_STRING:
+ case self::TYPE_UNIVERSAL_STRING:
+ case self::TYPE_UTF8_STRING:
+ case self::TYPE_BMP_STRING:
+ return $decoded['content'];
+ case self::TYPE_INTEGER:
+ case self::TYPE_ENUMERATED:
+ $temp = $decoded['content'];
+ if (isset($mapping['implicit'])) {
+ $temp = new BigInteger($decoded['content'], -256);
+ }
+ if (isset($mapping['mapping'])) {
+ $temp = (int) $temp->toString();
+ return isset($mapping['mapping'][$temp]) ?
+ $mapping['mapping'][$temp] :
+ false;
+ }
+ return $temp;
+ }
+ }
+
+ /**
+ * DER-decode the length
+ *
+ * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
+ * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
+ *
+ * @param string $string
+ * @return int
+ */
+ public static function decodeLength(&$string)
+ {
+ $length = ord(Strings::shift($string));
+ if ($length & 0x80) { // definite length, long form
+ $length &= 0x7F;
+ $temp = Strings::shift($string, $length);
+ list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4));
+ }
+ return $length;
+ }
+
+ /**
+ * ASN.1 Encode
+ *
+ * DER-encodes an ASN.1 semantic mapping ($mapping). Some libraries would probably call this function
+ * an ASN.1 compiler.
+ *
+ * "Special" mappings can be applied via $special.
+ *
+ * @param Element|string|array $source
+ * @param array $mapping
+ * @param array $special
+ * @return string
+ */
+ public static function encodeDER($source, $mapping, $special = [])
+ {
+ self::$location = [];
+ return self::encode_der($source, $mapping, null, $special);
+ }
+
+ /**
+ * ASN.1 Encode (Helper function)
+ *
+ * @param Element|string|array|null $source
+ * @param array $mapping
+ * @param int $idx
+ * @param array $special
+ * @return string
+ */
+ private static function encode_der($source, array $mapping, $idx = null, array $special = [])
+ {
+ if ($source instanceof Element) {
+ return $source->element;
+ }
+
+ // do not encode (implicitly optional) fields with value set to default
+ if (isset($mapping['default']) && $source === $mapping['default']) {
+ return '';
+ }
+
+ if (isset($idx)) {
+ if (isset($special[$idx])) {
+ $source = $special[$idx]($source);
+ }
+ self::$location[] = $idx;
+ }
+
+ $tag = $mapping['type'];
+
+ switch ($tag) {
+ case self::TYPE_SET: // Children order is not important, thus process in sequence.
+ case self::TYPE_SEQUENCE:
+ $tag |= 0x20; // set the constructed bit
+
+ // ignore the min and max
+ if (isset($mapping['min']) && isset($mapping['max'])) {
+ $value = [];
+ $child = $mapping['children'];
+
+ foreach ($source as $content) {
+ $temp = self::encode_der($content, $child, null, $special);
+ if ($temp === false) {
+ return false;
+ }
+ $value[] = $temp;
+ }
+ /* "The encodings of the component values of a set-of value shall appear in ascending order, the encodings being compared
+ as octet strings with the shorter components being padded at their trailing end with 0-octets.
+ NOTE - The padding octets are for comparison purposes only and do not appear in the encodings."
+
+ -- sec 11.6 of http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf */
+ if ($mapping['type'] == self::TYPE_SET) {
+ sort($value);
+ }
+ $value = implode('', $value);
+ break;
+ }
+
+ $value = '';
+ foreach ($mapping['children'] as $key => $child) {
+ if (!array_key_exists($key, $source)) {
+ if (!isset($child['optional'])) {
+ return false;
+ }
+ continue;
+ }
+
+ $temp = self::encode_der($source[$key], $child, $key, $special);
+ if ($temp === false) {
+ return false;
+ }
+
+ // An empty child encoding means it has been optimized out.
+ // Else we should have at least one tag byte.
+ if ($temp === '') {
+ continue;
+ }
+
+ // if isset($child['constant']) is true then isset($child['optional']) should be true as well
+ if (isset($child['constant'])) {
+ /*
+ From X.680-0207.pdf#page=58 (30.6):
+
+ "The tagging construction specifies explicit tagging if any of the following holds:
+ ...
+ c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or
+ AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or
+ an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
+ */
+ if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
+ if ($child['constant'] <= 30) {
+ $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
+ } else {
+ $constant = $child['constant'];
+ $subtag = '';
+ while ($constant > 0) {
+ $subtagvalue = $constant & 0x7F;
+ $subtag = (chr(0x80 | $subtagvalue)) . $subtag;
+ $constant = $constant >> 7;
+ }
+ $subtag[strlen($subtag) - 1] = $subtag[strlen($subtag) - 1] & chr(0x7F);
+ $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | 0x1f) . $subtag;
+ }
+ $temp = $subtag . self::encodeLength(strlen($temp)) . $temp;
+ } else {
+ $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
+ $temp = $subtag . substr($temp, 1);
+ }
+ }
+ $value .= $temp;
+ }
+ break;
+ case self::TYPE_CHOICE:
+ $temp = false;
+
+ foreach ($mapping['children'] as $key => $child) {
+ if (!isset($source[$key])) {
+ continue;
+ }
+
+ $temp = self::encode_der($source[$key], $child, $key, $special);
+ if ($temp === false) {
+ return false;
+ }
+
+ // An empty child encoding means it has been optimized out.
+ // Else we should have at least one tag byte.
+ if ($temp === '') {
+ continue;
+ }
+
+ $tag = ord($temp[0]);
+
+ // if isset($child['constant']) is true then isset($child['optional']) should be true as well
+ if (isset($child['constant'])) {
+ if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
+ $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
+ $temp = $subtag . self::encodeLength(strlen($temp)) . $temp;
+ } else {
+ $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
+ $temp = $subtag . substr($temp, 1);
+ }
+ }
+ }
+
+ if (isset($idx)) {
+ array_pop(self::$location);
+ }
+
+ if ($temp && isset($mapping['cast'])) {
+ $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']);
+ }
+
+ return $temp;
+ case self::TYPE_INTEGER:
+ case self::TYPE_ENUMERATED:
+ if (!isset($mapping['mapping'])) {
+ if (is_numeric($source)) {
+ $source = new BigInteger($source);
+ }
+ $value = $source->toBytes(true);
+ } else {
+ $value = array_search($source, $mapping['mapping']);
+ if ($value === false) {
+ return false;
+ }
+ $value = new BigInteger($value);
+ $value = $value->toBytes(true);
+ }
+ if (!strlen($value)) {
+ $value = chr(0);
+ }
+ break;
+ case self::TYPE_UTC_TIME:
+ case self::TYPE_GENERALIZED_TIME:
+ $format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y';
+ $format .= 'mdHis';
+ // if $source does _not_ include timezone information within it then assume that the timezone is GMT
+ $date = new \DateTime($source, new \DateTimeZone('GMT'));
+ // if $source _does_ include timezone information within it then convert the time to GMT
+ $date->setTimezone(new \DateTimeZone('GMT'));
+ $value = $date->format($format) . 'Z';
+ break;
+ case self::TYPE_BIT_STRING:
+ if (isset($mapping['mapping'])) {
+ $bits = array_fill(0, count($mapping['mapping']), 0);
+ $size = 0;
+ for ($i = 0; $i < count($mapping['mapping']); $i++) {
+ if (in_array($mapping['mapping'][$i], $source)) {
+ $bits[$i] = 1;
+ $size = $i;
+ }
+ }
+
+ if (isset($mapping['min']) && $mapping['min'] >= 1 && $size < $mapping['min']) {
+ $size = $mapping['min'] - 1;
+ }
+
+ $offset = 8 - (($size + 1) & 7);
+ $offset = $offset !== 8 ? $offset : 0;
+
+ $value = chr($offset);
+
+ for ($i = $size + 1; $i < count($mapping['mapping']); $i++) {
+ unset($bits[$i]);
+ }
+
+ $bits = implode('', array_pad($bits, $size + $offset + 1, 0));
+ $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
+ foreach ($bytes as $byte) {
+ $value .= chr(bindec($byte));
+ }
+
+ break;
+ }
+ // fall-through
+ case self::TYPE_OCTET_STRING:
+ /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
+ the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.
+
+ -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
+ $value = $source;
+ break;
+ case self::TYPE_OBJECT_IDENTIFIER:
+ $value = self::encodeOID($source);
+ break;
+ case self::TYPE_ANY:
+ $loc = self::$location;
+ if (isset($idx)) {
+ array_pop(self::$location);
+ }
+
+ switch (true) {
+ case !isset($source):
+ return self::encode_der(null, ['type' => self::TYPE_NULL] + $mapping, null, $special);
+ case is_int($source):
+ case $source instanceof BigInteger:
+ return self::encode_der($source, ['type' => self::TYPE_INTEGER] + $mapping, null, $special);
+ case is_float($source):
+ return self::encode_der($source, ['type' => self::TYPE_REAL] + $mapping, null, $special);
+ case is_bool($source):
+ return self::encode_der($source, ['type' => self::TYPE_BOOLEAN] + $mapping, null, $special);
+ case is_array($source) && count($source) == 1:
+ $typename = implode('', array_keys($source));
+ $outtype = array_search($typename, self::ANY_MAP, true);
+ if ($outtype !== false) {
+ return self::encode_der($source[$typename], ['type' => $outtype] + $mapping, null, $special);
+ }
+ }
+
+ $filters = self::$filters;
+ foreach ($loc as $part) {
+ if (!isset($filters[$part])) {
+ $filters = false;
+ break;
+ }
+ $filters = $filters[$part];
+ }
+ if ($filters === false) {
+ throw new \RuntimeException('No filters defined for ' . implode('/', $loc));
+ }
+ return self::encode_der($source, $filters + $mapping, null, $special);
+ case self::TYPE_NULL:
+ $value = '';
+ break;
+ case self::TYPE_NUMERIC_STRING:
+ case self::TYPE_TELETEX_STRING:
+ case self::TYPE_PRINTABLE_STRING:
+ case self::TYPE_UNIVERSAL_STRING:
+ case self::TYPE_UTF8_STRING:
+ case self::TYPE_BMP_STRING:
+ case self::TYPE_IA5_STRING:
+ case self::TYPE_VISIBLE_STRING:
+ case self::TYPE_VIDEOTEX_STRING:
+ case self::TYPE_GRAPHIC_STRING:
+ case self::TYPE_GENERAL_STRING:
+ $value = $source;
+ break;
+ case self::TYPE_BOOLEAN:
+ $value = $source ? "\xFF" : "\x00";
+ break;
+ default:
+ throw new \RuntimeException('Mapping provides no type definition for ' . implode('/', self::$location));
+ }
+
+ if (isset($idx)) {
+ array_pop(self::$location);
+ }
+
+ if (isset($mapping['cast'])) {
+ if (isset($mapping['explicit']) || $mapping['type'] == self::TYPE_CHOICE) {
+ $value = chr($tag) . self::encodeLength(strlen($value)) . $value;
+ $tag = ($mapping['class'] << 6) | 0x20 | $mapping['cast'];
+ } else {
+ $tag = ($mapping['class'] << 6) | (ord($temp[0]) & 0x20) | $mapping['cast'];
+ }
+ }
+
+ return chr($tag) . self::encodeLength(strlen($value)) . $value;
+ }
+
+ /**
+ * BER-decode the OID
+ *
+ * Called by _decode_ber()
+ *
+ * @param string $content
+ * @return string
+ */
+ public static function decodeOID($content)
+ {
+ // BigInteger's are used because of OIDs like 2.25.329800735698586629295641978511506172918
+ // https://healthcaresecprivacy.blogspot.com/2011/02/creating-and-using-unique-id-uuid-oid.html elaborates.
+ static $eighty;
+ if (!$eighty) {
+ $eighty = new BigInteger(80);
+ }
+
+ $oid = [];
+ $pos = 0;
+ $len = strlen($content);
+ // see https://github.com/openjdk/jdk/blob/2deb318c9f047ec5a4b160d66a4b52f93688ec42/src/java.base/share/classes/sun/security/util/ObjectIdentifier.java#L55
+ if ($len > 4096) {
+ //throw new \RuntimeException("Object identifier size is limited to 4096 bytes ($len bytes present)");
+ return false;
+ }
+
+ if (ord($content[$len - 1]) & 0x80) {
+ return false;
+ }
+
+ $n = new BigInteger();
+ while ($pos < $len) {
+ $temp = ord($content[$pos++]);
+ $n = $n->bitwise_leftShift(7);
+ $n = $n->bitwise_or(new BigInteger($temp & 0x7F));
+ if (~$temp & 0x80) {
+ $oid[] = $n;
+ $n = new BigInteger();
+ }
+ }
+ $part1 = array_shift($oid);
+ $first = floor(ord($content[0]) / 40);
+ /*
+ "This packing of the first two object identifier components recognizes that only three values are allocated from the root
+ node, and at most 39 subsequent values from nodes reached by X = 0 and X = 1."
+
+ -- https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=22
+ */
+ if ($first <= 2) { // ie. 0 <= ord($content[0]) < 120 (0x78)
+ array_unshift($oid, ord($content[0]) % 40);
+ array_unshift($oid, $first);
+ } else {
+ array_unshift($oid, $part1->subtract($eighty));
+ array_unshift($oid, 2);
+ }
+
+ return implode('.', $oid);
+ }
+
+ /**
+ * DER-encode the OID
+ *
+ * Called by _encode_der()
+ *
+ * @param string $source
+ * @return string
+ */
+ public static function encodeOID($source)
+ {
+ static $mask, $zero, $forty;
+ if (!$mask) {
+ $mask = new BigInteger(0x7F);
+ $zero = new BigInteger();
+ $forty = new BigInteger(40);
+ }
+
+ if (!preg_match('#(?:\d+\.)+#', $source)) {
+ $oid = isset(self::$reverseOIDs[$source]) ? self::$reverseOIDs[$source] : false;
+ } else {
+ $oid = $source;
+ }
+ if ($oid === false) {
+ throw new \RuntimeException('Invalid OID');
+ }
+
+ $parts = explode('.', $oid);
+ $part1 = array_shift($parts);
+ $part2 = array_shift($parts);
+
+ $first = new BigInteger($part1);
+ $first = $first->multiply($forty);
+ $first = $first->add(new BigInteger($part2));
+
+ array_unshift($parts, $first->toString());
+
+ $value = '';
+ foreach ($parts as $part) {
+ if (!$part) {
+ $temp = "\0";
+ } else {
+ $temp = '';
+ $part = new BigInteger($part);
+ while (!$part->equals($zero)) {
+ $submask = $part->bitwise_and($mask);
+ $submask->setPrecision(8);
+ $temp = (chr(0x80) | $submask->toBytes()) . $temp;
+ $part = $part->bitwise_rightShift(7);
+ }
+ $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
+ }
+ $value .= $temp;
+ }
+
+ return $value;
+ }
+
+ /**
+ * BER-decode the time
+ *
+ * Called by _decode_ber() and in the case of implicit tags asn1map().
+ *
+ * @param string $content
+ * @param int $tag
+ * @return \DateTime|false
+ */
+ private static function decodeTime($content, $tag)
+ {
+ /* UTCTime:
+ http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
+ http://www.obj-sys.com/asn1tutorial/node15.html
+
+ GeneralizedTime:
+ http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
+ http://www.obj-sys.com/asn1tutorial/node14.html */
+
+ $format = 'YmdHis';
+
+ if ($tag == self::TYPE_UTC_TIME) {
+ // https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=28 says "the seconds
+ // element shall always be present" but none-the-less I've seen X509 certs where it isn't and if the
+ // browsers parse it phpseclib ought to too
+ if (preg_match('#^(\d{10})(Z|[+-]\d{4})$#', $content, $matches)) {
+ $content = $matches[1] . '00' . $matches[2];
+ }
+ $prefix = substr($content, 0, 2) >= 50 ? '19' : '20';
+ $content = $prefix . $content;
+ } elseif (strpos($content, '.') !== false) {
+ $format .= '.u';
+ }
+
+ if ($content[strlen($content) - 1] == 'Z') {
+ $content = substr($content, 0, -1) . '+0000';
+ }
+
+ if (strpos($content, '-') !== false || strpos($content, '+') !== false) {
+ $format .= 'O';
+ }
+
+ // error supression isn't necessary as of PHP 7.0:
+ // http://php.net/manual/en/migration70.other-changes.php
+ return @\DateTime::createFromFormat($format, $content);
+ }
+
+ /**
+ * Set the time format
+ *
+ * Sets the time / date format for asn1map().
+ *
+ * @param string $format
+ */
+ public static function setTimeFormat($format)
+ {
+ self::$format = $format;
+ }
+
+ /**
+ * Load OIDs
+ *
+ * Load the relevant OIDs for a particular ASN.1 semantic mapping.
+ * Previously loaded OIDs are retained.
+ *
+ * @param array $oids
+ */
+ public static function loadOIDs(array $oids)
+ {
+ self::$reverseOIDs += $oids;
+ self::$oids = array_flip(self::$reverseOIDs);
+ }
+
+ /**
+ * Set filters
+ *
+ * See \phpseclib3\File\X509, etc, for an example.
+ * Previously loaded filters are not retained.
+ *
+ * @param array $filters
+ */
+ public static function setFilters(array $filters)
+ {
+ self::$filters = $filters;
+ }
+
+ /**
+ * String type conversion
+ *
+ * This is a lazy conversion, dealing only with character size.
+ * No real conversion table is used.
+ *
+ * @param string $in
+ * @param int $from
+ * @param int $to
+ * @return string
+ */
+ public static function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRING)
+ {
+ // isset(self::STRING_TYPE_SIZE[$from] returns a fatal error on PHP 5.6
+ if (!array_key_exists($from, self::STRING_TYPE_SIZE) || !array_key_exists($to, self::STRING_TYPE_SIZE)) {
+ return false;
+ }
+ $insize = self::STRING_TYPE_SIZE[$from];
+ $outsize = self::STRING_TYPE_SIZE[$to];
+ $inlength = strlen($in);
+ $out = '';
+
+ for ($i = 0; $i < $inlength;) {
+ if ($inlength - $i < $insize) {
+ return false;
+ }
+
+ // Get an input character as a 32-bit value.
+ $c = ord($in[$i++]);
+ switch (true) {
+ case $insize == 4:
+ $c = ($c << 8) | ord($in[$i++]);
+ $c = ($c << 8) | ord($in[$i++]);
+ // fall-through
+ case $insize == 2:
+ $c = ($c << 8) | ord($in[$i++]);
+ // fall-through
+ case $insize == 1:
+ break;
+ case ($c & 0x80) == 0x00:
+ break;
+ case ($c & 0x40) == 0x00:
+ return false;
+ default:
+ $bit = 6;
+ do {
+ if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) {
+ return false;
+ }
+ $c = ($c << 6) | (ord($in[$i++]) & 0x3F);
+ $bit += 5;
+ $mask = 1 << $bit;
+ } while ($c & $bit);
+ $c &= $mask - 1;
+ break;
+ }
+
+ // Convert and append the character to output string.
+ $v = '';
+ switch (true) {
+ case $outsize == 4:
+ $v .= chr($c & 0xFF);
+ $c >>= 8;
+ $v .= chr($c & 0xFF);
+ $c >>= 8;
+ // fall-through
+ case $outsize == 2:
+ $v .= chr($c & 0xFF);
+ $c >>= 8;
+ // fall-through
+ case $outsize == 1:
+ $v .= chr($c & 0xFF);
+ $c >>= 8;
+ if ($c) {
+ return false;
+ }
+ break;
+ case ($c & (PHP_INT_SIZE == 8 ? 0x80000000 : (1 << 31))) != 0:
+ return false;
+ case $c >= 0x04000000:
+ $v .= chr(0x80 | ($c & 0x3F));
+ $c = ($c >> 6) | 0x04000000;
+ // fall-through
+ case $c >= 0x00200000:
+ $v .= chr(0x80 | ($c & 0x3F));
+ $c = ($c >> 6) | 0x00200000;
+ // fall-through
+ case $c >= 0x00010000:
+ $v .= chr(0x80 | ($c & 0x3F));
+ $c = ($c >> 6) | 0x00010000;
+ // fall-through
+ case $c >= 0x00000800:
+ $v .= chr(0x80 | ($c & 0x3F));
+ $c = ($c >> 6) | 0x00000800;
+ // fall-through
+ case $c >= 0x00000080:
+ $v .= chr(0x80 | ($c & 0x3F));
+ $c = ($c >> 6) | 0x000000C0;
+ // fall-through
+ default:
+ $v .= chr($c);
+ break;
+ }
+ $out .= strrev($v);
+ }
+ return $out;
+ }
+
+ /**
+ * Extract raw BER from Base64 encoding
+ *
+ * @param string $str
+ * @return string
+ */
+ public static function extractBER($str)
+ {
+ /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them
+ * above and beyond the ceritificate.
+ * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line:
+ *
+ * Bag Attributes
+ * localKeyID: 01 00 00 00
+ * subject=/O=organization/OU=org unit/CN=common name
+ * issuer=/O=organization/CN=common name
+ */
+ if (strlen($str) > ini_get('pcre.backtrack_limit')) {
+ $temp = $str;
+ } else {
+ $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1);
+ $temp = preg_replace('#-+END.*[\r\n ]*.*#ms', '', $temp, 1);
+ }
+ // remove new lines
+ $temp = str_replace(["\r", "\n", ' '], '', $temp);
+ // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
+ $temp = preg_replace('#^-+[^-]+-+|-+[^-]+-+$#', '', $temp);
+ $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Strings::base64_decode($temp) : false;
+ return $temp != false ? $temp : $str;
+ }
+
+ /**
+ * DER-encode the length
+ *
+ * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
+ * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
+ *
+ * @param int $length
+ * @return string
+ */
+ public static function encodeLength($length)
+ {
+ if ($length <= 0x7F) {
+ return chr($length);
+ }
+
+ $temp = ltrim(pack('N', $length), chr(0));
+ return pack('Ca*', 0x80 | strlen($temp), $temp);
+ }
+
+ /**
+ * Returns the OID corresponding to a name
+ *
+ * What's returned in the associative array returned by loadX509() (or load*()) is either a name or an OID if
+ * no OID to name mapping is available. The problem with this is that what may be an unmapped OID in one version
+ * of phpseclib may not be unmapped in the next version, so apps that are looking at this OID may not be able
+ * to work from version to version.
+ *
+ * This method will return the OID if a name is passed to it and if no mapping is avialable it'll assume that
+ * what's being passed to it already is an OID and return that instead. A few examples.
+ *
+ * getOID('2.16.840.1.101.3.4.2.1') == '2.16.840.1.101.3.4.2.1'
+ * getOID('id-sha256') == '2.16.840.1.101.3.4.2.1'
+ * getOID('zzz') == 'zzz'
+ *
+ * @param string $name
+ * @return string
+ */
+ public static function getOID($name)
+ {
+ return isset(self::$reverseOIDs[$name]) ? self::$reverseOIDs[$name] : $name;
+ }
+}
diff --git a/Sources/Phpseclib/File/ASN1/Element.php b/Sources/Phpseclib/File/ASN1/Element.php
new file mode 100644
index 0000000000..ae4b764b09
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Element.php
@@ -0,0 +1,43 @@
+
+ * @copyright 2012 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1;
+
+/**
+ * ASN.1 Raw Element
+ *
+ * An ASN.1 ANY mapping will return an ASN1\Element object. Use of this object
+ * will also bypass the normal encoding rules in ASN1::encodeDER()
+ *
+ * @author Jim Wigginton
+ */
+class Element
+{
+ /**
+ * Raw element value
+ *
+ * @var string
+ */
+ public $element;
+
+ /**
+ * Constructor
+ *
+ * @param string $encoded
+ * @return Element
+ */
+ public function __construct($encoded)
+ {
+ $this->element = $encoded;
+ }
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/AccessDescription.php b/Sources/Phpseclib/File/ASN1/Maps/AccessDescription.php
new file mode 100644
index 0000000000..1cbc5a5941
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/AccessDescription.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AccessDescription
+ *
+ * @author Jim Wigginton
+ */
+abstract class AccessDescription
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'accessMethod' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'accessLocation' => GeneralName::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/AdministrationDomainName.php b/Sources/Phpseclib/File/ASN1/Maps/AdministrationDomainName.php
new file mode 100644
index 0000000000..04183a13b9
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/AdministrationDomainName.php
@@ -0,0 +1,36 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AdministrationDomainName
+ *
+ * @author Jim Wigginton
+ */
+abstract class AdministrationDomainName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ // if class isn't present it's assumed to be \phpseclib3\File\ASN1::CLASS_UNIVERSAL or
+ // (if constant is present) \phpseclib3\File\ASN1::CLASS_CONTEXT_SPECIFIC
+ 'class' => ASN1::CLASS_APPLICATION,
+ 'cast' => 2,
+ 'children' => [
+ 'numeric' => ['type' => ASN1::TYPE_NUMERIC_STRING],
+ 'printable' => ['type' => ASN1::TYPE_PRINTABLE_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php b/Sources/Phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php
new file mode 100644
index 0000000000..0da7eb1028
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php
@@ -0,0 +1,35 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AlgorithmIdentifier
+ *
+ * @author Jim Wigginton
+ */
+abstract class AlgorithmIdentifier
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'algorithm' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'parameters' => [
+ 'type' => ASN1::TYPE_ANY,
+ 'optional' => true
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/AnotherName.php b/Sources/Phpseclib/File/ASN1/Maps/AnotherName.php
new file mode 100644
index 0000000000..d96c170be2
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/AnotherName.php
@@ -0,0 +1,37 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AnotherName
+ *
+ * @author Jim Wigginton
+ */
+abstract class AnotherName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'type-id' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'value' => [
+ 'type' => ASN1::TYPE_ANY,
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/Attribute.php b/Sources/Phpseclib/File/ASN1/Maps/Attribute.php
new file mode 100644
index 0000000000..38a6aeefa2
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/Attribute.php
@@ -0,0 +1,37 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Attribute
+ *
+ * @author Jim Wigginton
+ */
+abstract class Attribute
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'type' => AttributeType::MAP,
+ 'value' => [
+ 'type' => ASN1::TYPE_SET,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => AttributeValue::MAP
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/AttributeType.php b/Sources/Phpseclib/File/ASN1/Maps/AttributeType.php
new file mode 100644
index 0000000000..5cbc2bcc21
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/AttributeType.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AttributeType
+ *
+ * @author Jim Wigginton
+ */
+abstract class AttributeType
+{
+ const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php b/Sources/Phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php
new file mode 100644
index 0000000000..fe414f16b0
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AttributeTypeAndValue
+ *
+ * @author Jim Wigginton
+ */
+abstract class AttributeTypeAndValue
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'type' => AttributeType::MAP,
+ 'value' => AttributeValue::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/AttributeValue.php b/Sources/Phpseclib/File/ASN1/Maps/AttributeValue.php
new file mode 100644
index 0000000000..3b3b6d2ede
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/AttributeValue.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AttributeValue
+ *
+ * @author Jim Wigginton
+ */
+abstract class AttributeValue
+{
+ const MAP = ['type' => ASN1::TYPE_ANY];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/Attributes.php b/Sources/Phpseclib/File/ASN1/Maps/Attributes.php
new file mode 100644
index 0000000000..cd53ecfafe
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/Attributes.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Attributes
+ *
+ * @author Jim Wigginton
+ */
+abstract class Attributes
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SET,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => Attribute::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php b/Sources/Phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php
new file mode 100644
index 0000000000..3e80a55d11
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AuthorityInfoAccessSyntax
+ *
+ * @author Jim Wigginton
+ */
+abstract class AuthorityInfoAccessSyntax
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => AccessDescription::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php b/Sources/Phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php
new file mode 100644
index 0000000000..e7ec5b28cc
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php
@@ -0,0 +1,45 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AuthorityKeyIdentifier
+ *
+ * @author Jim Wigginton
+ */
+abstract class AuthorityKeyIdentifier
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'keyIdentifier' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + KeyIdentifier::MAP,
+ 'authorityCertIssuer' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + GeneralNames::MAP,
+ 'authorityCertSerialNumber' => [
+ 'constant' => 2,
+ 'optional' => true,
+ 'implicit' => true
+ ] + CertificateSerialNumber::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/BaseDistance.php b/Sources/Phpseclib/File/ASN1/Maps/BaseDistance.php
new file mode 100644
index 0000000000..e59668ab92
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/BaseDistance.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * BaseDistance
+ *
+ * @author Jim Wigginton
+ */
+abstract class BaseDistance
+{
+ const MAP = ['type' => ASN1::TYPE_INTEGER];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/BasicConstraints.php b/Sources/Phpseclib/File/ASN1/Maps/BasicConstraints.php
new file mode 100644
index 0000000000..587ef1b0e9
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/BasicConstraints.php
@@ -0,0 +1,39 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * BasicConstraints
+ *
+ * @author Jim Wigginton
+ */
+abstract class BasicConstraints
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'cA' => [
+ 'type' => ASN1::TYPE_BOOLEAN,
+ 'optional' => true,
+ 'default' => false
+ ],
+ 'pathLenConstraint' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'optional' => true
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php b/Sources/Phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php
new file mode 100644
index 0000000000..e81bc78e83
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * BuiltInDomainDefinedAttribute
+ *
+ * @author Jim Wigginton
+ */
+abstract class BuiltInDomainDefinedAttribute
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'type' => ['type' => ASN1::TYPE_PRINTABLE_STRING],
+ 'value' => ['type' => ASN1::TYPE_PRINTABLE_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php b/Sources/Phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php
new file mode 100644
index 0000000000..471e88f927
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * BuiltInDomainDefinedAttributes
+ *
+ * @author Jim Wigginton
+ */
+abstract class BuiltInDomainDefinedAttributes
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => 4, // ub-domain-defined-attributes
+ 'children' => BuiltInDomainDefinedAttribute::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php b/Sources/Phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php
new file mode 100644
index 0000000000..752f400da3
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php
@@ -0,0 +1,67 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * BuiltInStandardAttributes
+ *
+ * @author Jim Wigginton
+ */
+abstract class BuiltInStandardAttributes
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'country-name' => ['optional' => true] + CountryName::MAP,
+ 'administration-domain-name' => ['optional' => true] + AdministrationDomainName::MAP,
+ 'network-address' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + NetworkAddress::MAP,
+ 'terminal-identifier' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + TerminalIdentifier::MAP,
+ 'private-domain-name' => [
+ 'constant' => 2,
+ 'optional' => true,
+ 'explicit' => true
+ ] + PrivateDomainName::MAP,
+ 'organization-name' => [
+ 'constant' => 3,
+ 'optional' => true,
+ 'implicit' => true
+ ] + OrganizationName::MAP,
+ 'numeric-user-identifier' => [
+ 'constant' => 4,
+ 'optional' => true,
+ 'implicit' => true
+ ] + NumericUserIdentifier::MAP,
+ 'personal-name' => [
+ 'constant' => 5,
+ 'optional' => true,
+ 'implicit' => true
+ ] + PersonalName::MAP,
+ 'organizational-unit-names' => [
+ 'constant' => 6,
+ 'optional' => true,
+ 'implicit' => true
+ ] + OrganizationalUnitNames::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/CPSuri.php b/Sources/Phpseclib/File/ASN1/Maps/CPSuri.php
new file mode 100644
index 0000000000..56e58887ef
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/CPSuri.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CPSuri
+ *
+ * @author Jim Wigginton
+ */
+abstract class CPSuri
+{
+ const MAP = ['type' => ASN1::TYPE_IA5_STRING];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/CRLDistributionPoints.php b/Sources/Phpseclib/File/ASN1/Maps/CRLDistributionPoints.php
new file mode 100644
index 0000000000..79860b2fdd
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/CRLDistributionPoints.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CRLDistributionPoints
+ *
+ * @author Jim Wigginton
+ */
+abstract class CRLDistributionPoints
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => DistributionPoint::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/CRLNumber.php b/Sources/Phpseclib/File/ASN1/Maps/CRLNumber.php
new file mode 100644
index 0000000000..f6cb956726
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/CRLNumber.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CRLNumber
+ *
+ * @author Jim Wigginton
+ */
+abstract class CRLNumber
+{
+ const MAP = ['type' => ASN1::TYPE_INTEGER];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/CRLReason.php b/Sources/Phpseclib/File/ASN1/Maps/CRLReason.php
new file mode 100644
index 0000000000..d373652998
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/CRLReason.php
@@ -0,0 +1,41 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CRLReason
+ *
+ * @author Jim Wigginton
+ */
+abstract class CRLReason
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_ENUMERATED,
+ 'mapping' => [
+ 'unspecified',
+ 'keyCompromise',
+ 'cACompromise',
+ 'affiliationChanged',
+ 'superseded',
+ 'cessationOfOperation',
+ 'certificateHold',
+ // Value 7 is not used.
+ 8 => 'removeFromCRL',
+ 'privilegeWithdrawn',
+ 'aACompromise'
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/CertPolicyId.php b/Sources/Phpseclib/File/ASN1/Maps/CertPolicyId.php
new file mode 100644
index 0000000000..d7e7776e8c
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/CertPolicyId.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CertPolicyId
+ *
+ * @author Jim Wigginton
+ */
+abstract class CertPolicyId
+{
+ const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/Certificate.php b/Sources/Phpseclib/File/ASN1/Maps/Certificate.php
new file mode 100644
index 0000000000..01943a0d4e
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/Certificate.php
@@ -0,0 +1,33 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Certificate
+ *
+ * @author Jim Wigginton
+ */
+abstract class Certificate
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'tbsCertificate' => TBSCertificate::MAP,
+ 'signatureAlgorithm' => AlgorithmIdentifier::MAP,
+ 'signature' => ['type' => ASN1::TYPE_BIT_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/CertificateIssuer.php b/Sources/Phpseclib/File/ASN1/Maps/CertificateIssuer.php
new file mode 100644
index 0000000000..ccd68dded1
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/CertificateIssuer.php
@@ -0,0 +1,24 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+/**
+ * CertificateIssuer
+ *
+ * @author Jim Wigginton
+ */
+abstract class CertificateIssuer
+{
+ const MAP = GeneralNames::MAP;
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/CertificateList.php b/Sources/Phpseclib/File/ASN1/Maps/CertificateList.php
new file mode 100644
index 0000000000..d54ed6d96f
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/CertificateList.php
@@ -0,0 +1,33 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CertificateList
+ *
+ * @author Jim Wigginton
+ */
+abstract class CertificateList
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'tbsCertList' => TBSCertList::MAP,
+ 'signatureAlgorithm' => AlgorithmIdentifier::MAP,
+ 'signature' => ['type' => ASN1::TYPE_BIT_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/CertificatePolicies.php b/Sources/Phpseclib/File/ASN1/Maps/CertificatePolicies.php
new file mode 100644
index 0000000000..ec0fa6b5dd
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/CertificatePolicies.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CertificatePolicies
+ *
+ * @author Jim Wigginton
+ */
+abstract class CertificatePolicies
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => PolicyInformation::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/CertificateSerialNumber.php b/Sources/Phpseclib/File/ASN1/Maps/CertificateSerialNumber.php
new file mode 100644
index 0000000000..06ec944c44
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/CertificateSerialNumber.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CertificateSerialNumber
+ *
+ * @author Jim Wigginton
+ */
+abstract class CertificateSerialNumber
+{
+ const MAP = ['type' => ASN1::TYPE_INTEGER];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/CertificationRequest.php b/Sources/Phpseclib/File/ASN1/Maps/CertificationRequest.php
new file mode 100644
index 0000000000..2da70ed6a2
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/CertificationRequest.php
@@ -0,0 +1,33 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CertificationRequest
+ *
+ * @author Jim Wigginton
+ */
+abstract class CertificationRequest
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'certificationRequestInfo' => CertificationRequestInfo::MAP,
+ 'signatureAlgorithm' => AlgorithmIdentifier::MAP,
+ 'signature' => ['type' => ASN1::TYPE_BIT_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/CertificationRequestInfo.php b/Sources/Phpseclib/File/ASN1/Maps/CertificationRequestInfo.php
new file mode 100644
index 0000000000..ce6dc8800a
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/CertificationRequestInfo.php
@@ -0,0 +1,41 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CertificationRequestInfo
+ *
+ * @author Jim Wigginton
+ */
+abstract class CertificationRequestInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => ['v1']
+ ],
+ 'subject' => Name::MAP,
+ 'subjectPKInfo' => SubjectPublicKeyInfo::MAP,
+ 'attributes' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + Attributes::MAP,
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/Characteristic_two.php b/Sources/Phpseclib/File/ASN1/Maps/Characteristic_two.php
new file mode 100644
index 0000000000..5bf59bb893
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/Characteristic_two.php
@@ -0,0 +1,36 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Characteristic_two
+ *
+ * @author Jim Wigginton
+ */
+abstract class Characteristic_two
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'm' => ['type' => ASN1::TYPE_INTEGER], // field size 2**m
+ 'basis' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'parameters' => [
+ 'type' => ASN1::TYPE_ANY,
+ 'optional' => true
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/CountryName.php b/Sources/Phpseclib/File/ASN1/Maps/CountryName.php
new file mode 100644
index 0000000000..737d844d1e
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/CountryName.php
@@ -0,0 +1,36 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CountryName
+ *
+ * @author Jim Wigginton
+ */
+abstract class CountryName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ // if class isn't present it's assumed to be \phpseclib3\File\ASN1::CLASS_UNIVERSAL or
+ // (if constant is present) \phpseclib3\File\ASN1::CLASS_CONTEXT_SPECIFIC
+ 'class' => ASN1::CLASS_APPLICATION,
+ 'cast' => 1,
+ 'children' => [
+ 'x121-dcc-code' => ['type' => ASN1::TYPE_NUMERIC_STRING],
+ 'iso-3166-alpha2-code' => ['type' => ASN1::TYPE_PRINTABLE_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/Curve.php b/Sources/Phpseclib/File/ASN1/Maps/Curve.php
new file mode 100644
index 0000000000..621f103550
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/Curve.php
@@ -0,0 +1,36 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Curve
+ *
+ * @author Jim Wigginton
+ */
+abstract class Curve
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'a' => FieldElement::MAP,
+ 'b' => FieldElement::MAP,
+ 'seed' => [
+ 'type' => ASN1::TYPE_BIT_STRING,
+ 'optional' => true
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/DHParameter.php b/Sources/Phpseclib/File/ASN1/Maps/DHParameter.php
new file mode 100644
index 0000000000..26863dbcf4
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/DHParameter.php
@@ -0,0 +1,38 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DHParameter
+ *
+ * @author Jim Wigginton
+ */
+abstract class DHParameter
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'prime' => ['type' => ASN1::TYPE_INTEGER],
+ 'base' => ['type' => ASN1::TYPE_INTEGER],
+ 'privateValueLength' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'optional' => true
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/DSAParams.php b/Sources/Phpseclib/File/ASN1/Maps/DSAParams.php
new file mode 100644
index 0000000000..7af397bb00
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/DSAParams.php
@@ -0,0 +1,33 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DSAParams
+ *
+ * @author Jim Wigginton
+ */
+abstract class DSAParams
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'p' => ['type' => ASN1::TYPE_INTEGER],
+ 'q' => ['type' => ASN1::TYPE_INTEGER],
+ 'g' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/DSAPrivateKey.php b/Sources/Phpseclib/File/ASN1/Maps/DSAPrivateKey.php
new file mode 100644
index 0000000000..d97cd023c9
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/DSAPrivateKey.php
@@ -0,0 +1,36 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DSAPrivateKey
+ *
+ * @author Jim Wigginton
+ */
+abstract class DSAPrivateKey
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => ['type' => ASN1::TYPE_INTEGER],
+ 'p' => ['type' => ASN1::TYPE_INTEGER],
+ 'q' => ['type' => ASN1::TYPE_INTEGER],
+ 'g' => ['type' => ASN1::TYPE_INTEGER],
+ 'y' => ['type' => ASN1::TYPE_INTEGER],
+ 'x' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/DSAPublicKey.php b/Sources/Phpseclib/File/ASN1/Maps/DSAPublicKey.php
new file mode 100644
index 0000000000..f795747a28
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/DSAPublicKey.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DSAPublicKey
+ *
+ * @author Jim Wigginton
+ */
+abstract class DSAPublicKey
+{
+ const MAP = ['type' => ASN1::TYPE_INTEGER];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/DigestInfo.php b/Sources/Phpseclib/File/ASN1/Maps/DigestInfo.php
new file mode 100644
index 0000000000..b38ff3c44b
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/DigestInfo.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DigestInfo
+ *
+ * from https://tools.ietf.org/html/rfc2898#appendix-A.3
+ *
+ * @author Jim Wigginton
+ */
+abstract class DigestInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'digestAlgorithm' => AlgorithmIdentifier::MAP,
+ 'digest' => ['type' => ASN1::TYPE_OCTET_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/DirectoryString.php b/Sources/Phpseclib/File/ASN1/Maps/DirectoryString.php
new file mode 100644
index 0000000000..45218e3e69
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/DirectoryString.php
@@ -0,0 +1,35 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DirectoryString
+ *
+ * @author Jim Wigginton
+ */
+abstract class DirectoryString
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'teletexString' => ['type' => ASN1::TYPE_TELETEX_STRING],
+ 'printableString' => ['type' => ASN1::TYPE_PRINTABLE_STRING],
+ 'universalString' => ['type' => ASN1::TYPE_UNIVERSAL_STRING],
+ 'utf8String' => ['type' => ASN1::TYPE_UTF8_STRING],
+ 'bmpString' => ['type' => ASN1::TYPE_BMP_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/DisplayText.php b/Sources/Phpseclib/File/ASN1/Maps/DisplayText.php
new file mode 100644
index 0000000000..a13e6a64e0
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/DisplayText.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DisplayText
+ *
+ * @author Jim Wigginton
+ */
+abstract class DisplayText
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'ia5String' => ['type' => ASN1::TYPE_IA5_STRING],
+ 'visibleString' => ['type' => ASN1::TYPE_VISIBLE_STRING],
+ 'bmpString' => ['type' => ASN1::TYPE_BMP_STRING],
+ 'utf8String' => ['type' => ASN1::TYPE_UTF8_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/DistributionPoint.php b/Sources/Phpseclib/File/ASN1/Maps/DistributionPoint.php
new file mode 100644
index 0000000000..4d9af6b59d
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/DistributionPoint.php
@@ -0,0 +1,45 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DistributionPoint
+ *
+ * @author Jim Wigginton
+ */
+abstract class DistributionPoint
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'distributionPoint' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true
+ ] + DistributionPointName::MAP,
+ 'reasons' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + ReasonFlags::MAP,
+ 'cRLIssuer' => [
+ 'constant' => 2,
+ 'optional' => true,
+ 'implicit' => true
+ ] + GeneralNames::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/DistributionPointName.php b/Sources/Phpseclib/File/ASN1/Maps/DistributionPointName.php
new file mode 100644
index 0000000000..bc0cec8f78
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/DistributionPointName.php
@@ -0,0 +1,40 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DistributionPointName
+ *
+ * @author Jim Wigginton
+ */
+abstract class DistributionPointName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'fullName' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + GeneralNames::MAP,
+ 'nameRelativeToCRLIssuer' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + RelativeDistinguishedName::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/DssSigValue.php b/Sources/Phpseclib/File/ASN1/Maps/DssSigValue.php
new file mode 100644
index 0000000000..2af7408838
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/DssSigValue.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DssSigValue
+ *
+ * @author Jim Wigginton
+ */
+abstract class DssSigValue
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'r' => ['type' => ASN1::TYPE_INTEGER],
+ 's' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/ECParameters.php b/Sources/Phpseclib/File/ASN1/Maps/ECParameters.php
new file mode 100644
index 0000000000..f25f6faaa1
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/ECParameters.php
@@ -0,0 +1,45 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ECParameters
+ *
+ * ECParameters ::= CHOICE {
+ * namedCurve OBJECT IDENTIFIER
+ * -- implicitCurve NULL
+ * -- specifiedCurve SpecifiedECDomain
+ * }
+ * -- implicitCurve and specifiedCurve MUST NOT be used in PKIX.
+ * -- Details for SpecifiedECDomain can be found in [X9.62].
+ * -- Any future additions to this CHOICE should be coordinated
+ * -- with ANSI X9.
+ *
+ * @author Jim Wigginton
+ */
+abstract class ECParameters
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'namedCurve' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'implicitCurve' => ['type' => ASN1::TYPE_NULL],
+ 'specifiedCurve' => SpecifiedECDomain::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/ECPoint.php b/Sources/Phpseclib/File/ASN1/Maps/ECPoint.php
new file mode 100644
index 0000000000..fb11db83f8
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/ECPoint.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ECPoint
+ *
+ * @author Jim Wigginton
+ */
+abstract class ECPoint
+{
+ const MAP = ['type' => ASN1::TYPE_OCTET_STRING];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/ECPrivateKey.php b/Sources/Phpseclib/File/ASN1/Maps/ECPrivateKey.php
new file mode 100644
index 0000000000..7454f38747
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/ECPrivateKey.php
@@ -0,0 +1,48 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ECPrivateKey
+ *
+ * @author Jim Wigginton
+ */
+abstract class ECPrivateKey
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => [1 => 'ecPrivkeyVer1']
+ ],
+ 'privateKey' => ['type' => ASN1::TYPE_OCTET_STRING],
+ 'parameters' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true
+ ] + ECParameters::MAP,
+ 'publicKey' => [
+ 'type' => ASN1::TYPE_BIT_STRING,
+ 'constant' => 1,
+ 'optional' => true,
+ 'explicit' => true
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/EDIPartyName.php b/Sources/Phpseclib/File/ASN1/Maps/EDIPartyName.php
new file mode 100644
index 0000000000..ea7dcf194b
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/EDIPartyName.php
@@ -0,0 +1,42 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * EDIPartyName
+ *
+ * @author Jim Wigginton
+ */
+abstract class EDIPartyName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'nameAssigner' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + DirectoryString::MAP,
+ // partyName is technically required but \phpseclib3\File\ASN1 doesn't currently support non-optional constants and
+ // setting it to optional gets the job done in any event.
+ 'partyName' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + DirectoryString::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/EcdsaSigValue.php b/Sources/Phpseclib/File/ASN1/Maps/EcdsaSigValue.php
new file mode 100644
index 0000000000..8ab9ff1eb2
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/EcdsaSigValue.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * EcdsaSigValue
+ *
+ * @author Jim Wigginton
+ */
+abstract class EcdsaSigValue
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'r' => ['type' => ASN1::TYPE_INTEGER],
+ 's' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/EncryptedData.php b/Sources/Phpseclib/File/ASN1/Maps/EncryptedData.php
new file mode 100644
index 0000000000..8d8739e1c8
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/EncryptedData.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * EncryptedData
+ *
+ * @author Jim Wigginton
+ */
+abstract class EncryptedData
+{
+ const MAP = ['type' => ASN1::TYPE_OCTET_STRING];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php b/Sources/Phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php
new file mode 100644
index 0000000000..2c9356769b
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * EncryptedPrivateKeyInfo
+ *
+ * @author Jim Wigginton
+ */
+abstract class EncryptedPrivateKeyInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'encryptionAlgorithm' => AlgorithmIdentifier::MAP,
+ 'encryptedData' => EncryptedData::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php b/Sources/Phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php
new file mode 100644
index 0000000000..f9bc5deff3
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ExtKeyUsageSyntax
+ *
+ * @author Jim Wigginton
+ */
+abstract class ExtKeyUsageSyntax
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => KeyPurposeId::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/Extension.php b/Sources/Phpseclib/File/ASN1/Maps/Extension.php
new file mode 100644
index 0000000000..e32668fb53
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/Extension.php
@@ -0,0 +1,43 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Extension
+ *
+ * A certificate using system MUST reject the certificate if it encounters
+ * a critical extension it does not recognize; however, a non-critical
+ * extension may be ignored if it is not recognized.
+ *
+ * http://tools.ietf.org/html/rfc5280#section-4.2
+ *
+ * @author Jim Wigginton
+ */
+abstract class Extension
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'extnId' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'critical' => [
+ 'type' => ASN1::TYPE_BOOLEAN,
+ 'optional' => true,
+ 'default' => false
+ ],
+ 'extnValue' => ['type' => ASN1::TYPE_OCTET_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/ExtensionAttribute.php b/Sources/Phpseclib/File/ASN1/Maps/ExtensionAttribute.php
new file mode 100644
index 0000000000..565b36d393
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/ExtensionAttribute.php
@@ -0,0 +1,42 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ExtensionAttribute
+ *
+ * @author Jim Wigginton
+ */
+abstract class ExtensionAttribute
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'extension-attribute-type' => [
+ 'type' => ASN1::TYPE_PRINTABLE_STRING,
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'extension-attribute-value' => [
+ 'type' => ASN1::TYPE_ANY,
+ 'constant' => 1,
+ 'optional' => true,
+ 'explicit' => true
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/ExtensionAttributes.php b/Sources/Phpseclib/File/ASN1/Maps/ExtensionAttributes.php
new file mode 100644
index 0000000000..a2e9bfaec8
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/ExtensionAttributes.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ExtensionAttributes
+ *
+ * @author Jim Wigginton
+ */
+abstract class ExtensionAttributes
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SET,
+ 'min' => 1,
+ 'max' => 256, // ub-extension-attributes
+ 'children' => ExtensionAttribute::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/Extensions.php b/Sources/Phpseclib/File/ASN1/Maps/Extensions.php
new file mode 100644
index 0000000000..5015c976c0
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/Extensions.php
@@ -0,0 +1,33 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Extensions
+ *
+ * @author Jim Wigginton
+ */
+abstract class Extensions
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ // technically, it's MAX, but we'll assume anything < 0 is MAX
+ 'max' => -1,
+ // if 'children' isn't an array then 'min' and 'max' must be defined
+ 'children' => Extension::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/FieldElement.php b/Sources/Phpseclib/File/ASN1/Maps/FieldElement.php
new file mode 100644
index 0000000000..31734078dd
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/FieldElement.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * FieldElement
+ *
+ * @author Jim Wigginton
+ */
+abstract class FieldElement
+{
+ const MAP = ['type' => ASN1::TYPE_OCTET_STRING];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/FieldID.php b/Sources/Phpseclib/File/ASN1/Maps/FieldID.php
new file mode 100644
index 0000000000..e32a9c03dc
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/FieldID.php
@@ -0,0 +1,35 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * FieldID
+ *
+ * @author Jim Wigginton
+ */
+abstract class FieldID
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'fieldType' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'parameters' => [
+ 'type' => ASN1::TYPE_ANY,
+ 'optional' => true
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/GeneralName.php b/Sources/Phpseclib/File/ASN1/Maps/GeneralName.php
new file mode 100644
index 0000000000..57d86da856
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/GeneralName.php
@@ -0,0 +1,80 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * GeneralName
+ *
+ * @author Jim Wigginton
+ */
+abstract class GeneralName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'otherName' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + AnotherName::MAP,
+ 'rfc822Name' => [
+ 'type' => ASN1::TYPE_IA5_STRING,
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'dNSName' => [
+ 'type' => ASN1::TYPE_IA5_STRING,
+ 'constant' => 2,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'x400Address' => [
+ 'constant' => 3,
+ 'optional' => true,
+ 'implicit' => true
+ ] + ORAddress::MAP,
+ 'directoryName' => [
+ 'constant' => 4,
+ 'optional' => true,
+ 'explicit' => true
+ ] + Name::MAP,
+ 'ediPartyName' => [
+ 'constant' => 5,
+ 'optional' => true,
+ 'implicit' => true
+ ] + EDIPartyName::MAP,
+ 'uniformResourceIdentifier' => [
+ 'type' => ASN1::TYPE_IA5_STRING,
+ 'constant' => 6,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'iPAddress' => [
+ 'type' => ASN1::TYPE_OCTET_STRING,
+ 'constant' => 7,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'registeredID' => [
+ 'type' => ASN1::TYPE_OBJECT_IDENTIFIER,
+ 'constant' => 8,
+ 'optional' => true,
+ 'implicit' => true
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/GeneralNames.php b/Sources/Phpseclib/File/ASN1/Maps/GeneralNames.php
new file mode 100644
index 0000000000..5d931532d9
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/GeneralNames.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * GeneralNames
+ *
+ * @author Jim Wigginton
+ */
+abstract class GeneralNames
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => GeneralName::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/GeneralSubtree.php b/Sources/Phpseclib/File/ASN1/Maps/GeneralSubtree.php
new file mode 100644
index 0000000000..5388db559a
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/GeneralSubtree.php
@@ -0,0 +1,42 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * GeneralSubtree
+ *
+ * @author Jim Wigginton
+ */
+abstract class GeneralSubtree
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'base' => GeneralName::MAP,
+ 'minimum' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true,
+ 'default' => '0'
+ ] + BaseDistance::MAP,
+ 'maximum' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true,
+ ] + BaseDistance::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/GeneralSubtrees.php b/Sources/Phpseclib/File/ASN1/Maps/GeneralSubtrees.php
new file mode 100644
index 0000000000..27548cfecd
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/GeneralSubtrees.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * GeneralSubtrees
+ *
+ * @author Jim Wigginton
+ */
+abstract class GeneralSubtrees
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => GeneralSubtree::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/HashAlgorithm.php b/Sources/Phpseclib/File/ASN1/Maps/HashAlgorithm.php
new file mode 100644
index 0000000000..deb13cabe2
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/HashAlgorithm.php
@@ -0,0 +1,24 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+/**
+ * HashAglorithm
+ *
+ * @author Jim Wigginton
+ */
+abstract class HashAlgorithm
+{
+ const MAP = AlgorithmIdentifier::MAP;
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/HoldInstructionCode.php b/Sources/Phpseclib/File/ASN1/Maps/HoldInstructionCode.php
new file mode 100644
index 0000000000..88d6ff3ea0
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/HoldInstructionCode.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * HoldInstructionCode
+ *
+ * @author Jim Wigginton
+ */
+abstract class HoldInstructionCode
+{
+ const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/InvalidityDate.php b/Sources/Phpseclib/File/ASN1/Maps/InvalidityDate.php
new file mode 100644
index 0000000000..f34b5f728d
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/InvalidityDate.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * InvalidityDate
+ *
+ * @author Jim Wigginton
+ */
+abstract class InvalidityDate
+{
+ const MAP = ['type' => ASN1::TYPE_GENERALIZED_TIME];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/IssuerAltName.php b/Sources/Phpseclib/File/ASN1/Maps/IssuerAltName.php
new file mode 100644
index 0000000000..e9d0324480
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/IssuerAltName.php
@@ -0,0 +1,24 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+/**
+ * IssuerAltName
+ *
+ * @author Jim Wigginton
+ */
+abstract class IssuerAltName
+{
+ const MAP = GeneralNames::MAP;
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php b/Sources/Phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php
new file mode 100644
index 0000000000..415996f52b
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php
@@ -0,0 +1,68 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * IssuingDistributionPoint
+ *
+ * @author Jim Wigginton
+ */
+abstract class IssuingDistributionPoint
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'distributionPoint' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true
+ ] + DistributionPointName::MAP,
+ 'onlyContainsUserCerts' => [
+ 'type' => ASN1::TYPE_BOOLEAN,
+ 'constant' => 1,
+ 'optional' => true,
+ 'default' => false,
+ 'implicit' => true
+ ],
+ 'onlyContainsCACerts' => [
+ 'type' => ASN1::TYPE_BOOLEAN,
+ 'constant' => 2,
+ 'optional' => true,
+ 'default' => false,
+ 'implicit' => true
+ ],
+ 'onlySomeReasons' => [
+ 'constant' => 3,
+ 'optional' => true,
+ 'implicit' => true
+ ] + ReasonFlags::MAP,
+ 'indirectCRL' => [
+ 'type' => ASN1::TYPE_BOOLEAN,
+ 'constant' => 4,
+ 'optional' => true,
+ 'default' => false,
+ 'implicit' => true
+ ],
+ 'onlyContainsAttributeCerts' => [
+ 'type' => ASN1::TYPE_BOOLEAN,
+ 'constant' => 5,
+ 'optional' => true,
+ 'default' => false,
+ 'implicit' => true
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/KeyIdentifier.php b/Sources/Phpseclib/File/ASN1/Maps/KeyIdentifier.php
new file mode 100644
index 0000000000..82a415199b
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/KeyIdentifier.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * KeyIdentifier
+ *
+ * @author Jim Wigginton
+ */
+abstract class KeyIdentifier
+{
+ const MAP = ['type' => ASN1::TYPE_OCTET_STRING];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/KeyPurposeId.php b/Sources/Phpseclib/File/ASN1/Maps/KeyPurposeId.php
new file mode 100644
index 0000000000..b8509f1961
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/KeyPurposeId.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * KeyPurposeId
+ *
+ * @author Jim Wigginton
+ */
+abstract class KeyPurposeId
+{
+ const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/KeyUsage.php b/Sources/Phpseclib/File/ASN1/Maps/KeyUsage.php
new file mode 100644
index 0000000000..827ce03302
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/KeyUsage.php
@@ -0,0 +1,39 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * KeyUsage
+ *
+ * @author Jim Wigginton
+ */
+abstract class KeyUsage
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_BIT_STRING,
+ 'mapping' => [
+ 'digitalSignature',
+ 'nonRepudiation',
+ 'keyEncipherment',
+ 'dataEncipherment',
+ 'keyAgreement',
+ 'keyCertSign',
+ 'cRLSign',
+ 'encipherOnly',
+ 'decipherOnly'
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php b/Sources/Phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php
new file mode 100644
index 0000000000..ea3f998b41
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php
@@ -0,0 +1,24 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+/**
+ * MaskGenAglorithm
+ *
+ * @author Jim Wigginton
+ */
+abstract class MaskGenAlgorithm
+{
+ const MAP = AlgorithmIdentifier::MAP;
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/Name.php b/Sources/Phpseclib/File/ASN1/Maps/Name.php
new file mode 100644
index 0000000000..a6a9009dcd
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/Name.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Name
+ *
+ * @author Jim Wigginton
+ */
+abstract class Name
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'rdnSequence' => RDNSequence::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/NameConstraints.php b/Sources/Phpseclib/File/ASN1/Maps/NameConstraints.php
new file mode 100644
index 0000000000..80486f94d6
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/NameConstraints.php
@@ -0,0 +1,40 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * NameConstraints
+ *
+ * @author Jim Wigginton
+ */
+abstract class NameConstraints
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'permittedSubtrees' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + GeneralSubtrees::MAP,
+ 'excludedSubtrees' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + GeneralSubtrees::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/NetworkAddress.php b/Sources/Phpseclib/File/ASN1/Maps/NetworkAddress.php
new file mode 100644
index 0000000000..6c68df0028
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/NetworkAddress.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * NetworkAddress
+ *
+ * @author Jim Wigginton
+ */
+abstract class NetworkAddress
+{
+ const MAP = ['type' => ASN1::TYPE_NUMERIC_STRING];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/NoticeReference.php b/Sources/Phpseclib/File/ASN1/Maps/NoticeReference.php
new file mode 100644
index 0000000000..9eec123a9f
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/NoticeReference.php
@@ -0,0 +1,37 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * NoticeReference
+ *
+ * @author Jim Wigginton
+ */
+abstract class NoticeReference
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'organization' => DisplayText::MAP,
+ 'noticeNumbers' => [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => 200,
+ 'children' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/NumericUserIdentifier.php b/Sources/Phpseclib/File/ASN1/Maps/NumericUserIdentifier.php
new file mode 100644
index 0000000000..635a89dcb0
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/NumericUserIdentifier.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * NumericUserIdentifier
+ *
+ * @author Jim Wigginton
+ */
+abstract class NumericUserIdentifier
+{
+ const MAP = ['type' => ASN1::TYPE_NUMERIC_STRING];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/ORAddress.php b/Sources/Phpseclib/File/ASN1/Maps/ORAddress.php
new file mode 100644
index 0000000000..b853abe828
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/ORAddress.php
@@ -0,0 +1,33 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ORAddress
+ *
+ * @author Jim Wigginton
+ */
+abstract class ORAddress
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'built-in-standard-attributes' => BuiltInStandardAttributes::MAP,
+ 'built-in-domain-defined-attributes' => ['optional' => true] + BuiltInDomainDefinedAttributes::MAP,
+ 'extension-attributes' => ['optional' => true] + ExtensionAttributes::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/OneAsymmetricKey.php b/Sources/Phpseclib/File/ASN1/Maps/OneAsymmetricKey.php
new file mode 100644
index 0000000000..59530248c1
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/OneAsymmetricKey.php
@@ -0,0 +1,48 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * OneAsymmetricKey
+ *
+ * @author Jim Wigginton
+ */
+abstract class OneAsymmetricKey
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => ['v1', 'v2']
+ ],
+ 'privateKeyAlgorithm' => AlgorithmIdentifier::MAP,
+ 'privateKey' => PrivateKey::MAP,
+ 'attributes' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + Attributes::MAP,
+ 'publicKey' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + PublicKey::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/OrganizationName.php b/Sources/Phpseclib/File/ASN1/Maps/OrganizationName.php
new file mode 100644
index 0000000000..b5cc9491ac
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/OrganizationName.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * OrganizationName
+ *
+ * @author Jim Wigginton
+ */
+abstract class OrganizationName
+{
+ const MAP = ['type' => ASN1::TYPE_PRINTABLE_STRING];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php b/Sources/Phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php
new file mode 100644
index 0000000000..b3e57809ba
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * OrganizationalUnitNames
+ *
+ * @author Jim Wigginton
+ */
+abstract class OrganizationalUnitNames
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => 4, // ub-organizational-units
+ 'children' => ['type' => ASN1::TYPE_PRINTABLE_STRING]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/OtherPrimeInfo.php b/Sources/Phpseclib/File/ASN1/Maps/OtherPrimeInfo.php
new file mode 100644
index 0000000000..5d565605e0
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/OtherPrimeInfo.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * OtherPrimeInfo
+ *
+ * @author Jim Wigginton
+ */
+abstract class OtherPrimeInfo
+{
+ // version must be multi if otherPrimeInfos present
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'prime' => ['type' => ASN1::TYPE_INTEGER], // ri
+ 'exponent' => ['type' => ASN1::TYPE_INTEGER], // di
+ 'coefficient' => ['type' => ASN1::TYPE_INTEGER] // ti
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/OtherPrimeInfos.php b/Sources/Phpseclib/File/ASN1/Maps/OtherPrimeInfos.php
new file mode 100644
index 0000000000..9802a8089f
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/OtherPrimeInfos.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * OtherPrimeInfos
+ *
+ * @author Jim Wigginton
+ */
+abstract class OtherPrimeInfos
+{
+ // version must be multi if otherPrimeInfos present
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => OtherPrimeInfo::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PBEParameter.php b/Sources/Phpseclib/File/ASN1/Maps/PBEParameter.php
new file mode 100644
index 0000000000..8eb27cf629
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PBEParameter.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PBEParameter
+ *
+ * from https://tools.ietf.org/html/rfc2898#appendix-A.3
+ *
+ * @author Jim Wigginton
+ */
+abstract class PBEParameter
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'salt' => ['type' => ASN1::TYPE_OCTET_STRING],
+ 'iterationCount' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PBES2params.php b/Sources/Phpseclib/File/ASN1/Maps/PBES2params.php
new file mode 100644
index 0000000000..bd31699ff1
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PBES2params.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PBES2params
+ *
+ * from https://tools.ietf.org/html/rfc2898#appendix-A.3
+ *
+ * @author Jim Wigginton
+ */
+abstract class PBES2params
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'keyDerivationFunc' => AlgorithmIdentifier::MAP,
+ 'encryptionScheme' => AlgorithmIdentifier::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PBKDF2params.php b/Sources/Phpseclib/File/ASN1/Maps/PBKDF2params.php
new file mode 100644
index 0000000000..2dafed9ca7
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PBKDF2params.php
@@ -0,0 +1,41 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PBKDF2params
+ *
+ * from https://tools.ietf.org/html/rfc2898#appendix-A.3
+ *
+ * @author Jim Wigginton
+ */
+abstract class PBKDF2params
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ // technically, this is a CHOICE in RFC2898 but the other "choice" is, currently, more of a placeholder
+ // in the RFC
+ 'salt' => ['type' => ASN1::TYPE_OCTET_STRING],
+ 'iterationCount' => ['type' => ASN1::TYPE_INTEGER],
+ 'keyLength' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'optional' => true
+ ],
+ 'prf' => AlgorithmIdentifier::MAP + ['optional' => true]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PBMAC1params.php b/Sources/Phpseclib/File/ASN1/Maps/PBMAC1params.php
new file mode 100644
index 0000000000..91319f5825
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PBMAC1params.php
@@ -0,0 +1,34 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PBMAC1params
+ *
+ * from https://tools.ietf.org/html/rfc2898#appendix-A.3
+ *
+ * @author Jim Wigginton
+ */
+abstract class PBMAC1params
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'keyDerivationFunc' => AlgorithmIdentifier::MAP,
+ 'messageAuthScheme' => AlgorithmIdentifier::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PKCS9String.php b/Sources/Phpseclib/File/ASN1/Maps/PKCS9String.php
new file mode 100644
index 0000000000..87d0862f52
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PKCS9String.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PKCS9String
+ *
+ * @author Jim Wigginton
+ */
+abstract class PKCS9String
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'ia5String' => ['type' => ASN1::TYPE_IA5_STRING],
+ 'directoryString' => DirectoryString::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/Pentanomial.php b/Sources/Phpseclib/File/ASN1/Maps/Pentanomial.php
new file mode 100644
index 0000000000..b8c8c02fd3
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/Pentanomial.php
@@ -0,0 +1,33 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Pentanomial
+ *
+ * @author Jim Wigginton
+ */
+abstract class Pentanomial
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'k1' => ['type' => ASN1::TYPE_INTEGER], // k1 > 0
+ 'k2' => ['type' => ASN1::TYPE_INTEGER], // k2 > k1
+ 'k3' => ['type' => ASN1::TYPE_INTEGER], // k3 > h2
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PersonalName.php b/Sources/Phpseclib/File/ASN1/Maps/PersonalName.php
new file mode 100644
index 0000000000..14e2860e51
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PersonalName.php
@@ -0,0 +1,54 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PersonalName
+ *
+ * @author Jim Wigginton
+ */
+abstract class PersonalName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SET,
+ 'children' => [
+ 'surname' => [
+ 'type' => ASN1::TYPE_PRINTABLE_STRING,
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'given-name' => [
+ 'type' => ASN1::TYPE_PRINTABLE_STRING,
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'initials' => [
+ 'type' => ASN1::TYPE_PRINTABLE_STRING,
+ 'constant' => 2,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'generation-qualifier' => [
+ 'type' => ASN1::TYPE_PRINTABLE_STRING,
+ 'constant' => 3,
+ 'optional' => true,
+ 'implicit' => true
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PolicyInformation.php b/Sources/Phpseclib/File/ASN1/Maps/PolicyInformation.php
new file mode 100644
index 0000000000..1625d199a3
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PolicyInformation.php
@@ -0,0 +1,38 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PolicyInformation
+ *
+ * @author Jim Wigginton
+ */
+abstract class PolicyInformation
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'policyIdentifier' => CertPolicyId::MAP,
+ 'policyQualifiers' => [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 0,
+ 'max' => -1,
+ 'optional' => true,
+ 'children' => PolicyQualifierInfo::MAP
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PolicyMappings.php b/Sources/Phpseclib/File/ASN1/Maps/PolicyMappings.php
new file mode 100644
index 0000000000..d30b85235b
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PolicyMappings.php
@@ -0,0 +1,37 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PolicyMappings
+ *
+ * @author Jim Wigginton
+ */
+abstract class PolicyMappings
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'issuerDomainPolicy' => CertPolicyId::MAP,
+ 'subjectDomainPolicy' => CertPolicyId::MAP
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PolicyQualifierId.php b/Sources/Phpseclib/File/ASN1/Maps/PolicyQualifierId.php
new file mode 100644
index 0000000000..7b7cd6a76a
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PolicyQualifierId.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PolicyQualifierId
+ *
+ * @author Jim Wigginton
+ */
+abstract class PolicyQualifierId
+{
+ const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php b/Sources/Phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php
new file mode 100644
index 0000000000..d227702efc
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PolicyQualifierInfo
+ *
+ * @author Jim Wigginton
+ */
+abstract class PolicyQualifierInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'policyQualifierId' => PolicyQualifierId::MAP,
+ 'qualifier' => ['type' => ASN1::TYPE_ANY]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PostalAddress.php b/Sources/Phpseclib/File/ASN1/Maps/PostalAddress.php
new file mode 100644
index 0000000000..142b309e46
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PostalAddress.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PostalAddress
+ *
+ * @author Jim Wigginton
+ */
+abstract class PostalAddress
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'optional' => true,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => DirectoryString::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/Prime_p.php b/Sources/Phpseclib/File/ASN1/Maps/Prime_p.php
new file mode 100644
index 0000000000..7743034489
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/Prime_p.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Prime_p
+ *
+ * @author Jim Wigginton
+ */
+abstract class Prime_p
+{
+ const MAP = ['type' => ASN1::TYPE_INTEGER];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PrivateDomainName.php b/Sources/Phpseclib/File/ASN1/Maps/PrivateDomainName.php
new file mode 100644
index 0000000000..195dcaa5e4
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PrivateDomainName.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PrivateDomainName
+ *
+ * @author Jim Wigginton
+ */
+abstract class PrivateDomainName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'numeric' => ['type' => ASN1::TYPE_NUMERIC_STRING],
+ 'printable' => ['type' => ASN1::TYPE_PRINTABLE_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PrivateKey.php b/Sources/Phpseclib/File/ASN1/Maps/PrivateKey.php
new file mode 100644
index 0000000000..3c8959411f
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PrivateKey.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PrivateKey
+ *
+ * @author Jim Wigginton
+ */
+abstract class PrivateKey
+{
+ const MAP = ['type' => ASN1::TYPE_OCTET_STRING];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PrivateKeyInfo.php b/Sources/Phpseclib/File/ASN1/Maps/PrivateKeyInfo.php
new file mode 100644
index 0000000000..b440b78dfc
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PrivateKeyInfo.php
@@ -0,0 +1,41 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PrivateKeyInfo
+ *
+ * @author Jim Wigginton
+ */
+abstract class PrivateKeyInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => ['v1']
+ ],
+ 'privateKeyAlgorithm' => AlgorithmIdentifier::MAP,
+ 'privateKey' => PrivateKey::MAP,
+ 'attributes' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + Attributes::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php b/Sources/Phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php
new file mode 100644
index 0000000000..5b87036e62
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php
@@ -0,0 +1,40 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PrivateKeyUsagePeriod
+ *
+ * @author Jim Wigginton
+ */
+abstract class PrivateKeyUsagePeriod
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'notBefore' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true,
+ 'type' => ASN1::TYPE_GENERALIZED_TIME],
+ 'notAfter' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true,
+ 'type' => ASN1::TYPE_GENERALIZED_TIME]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PublicKey.php b/Sources/Phpseclib/File/ASN1/Maps/PublicKey.php
new file mode 100644
index 0000000000..4840920427
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PublicKey.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PublicKey
+ *
+ * @author Jim Wigginton
+ */
+abstract class PublicKey
+{
+ const MAP = ['type' => ASN1::TYPE_BIT_STRING];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php b/Sources/Phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php
new file mode 100644
index 0000000000..432581e48b
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PublicKeyAndChallenge
+ *
+ * @author Jim Wigginton
+ */
+abstract class PublicKeyAndChallenge
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'spki' => SubjectPublicKeyInfo::MAP,
+ 'challenge' => ['type' => ASN1::TYPE_IA5_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/PublicKeyInfo.php b/Sources/Phpseclib/File/ASN1/Maps/PublicKeyInfo.php
new file mode 100644
index 0000000000..b39a341f04
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/PublicKeyInfo.php
@@ -0,0 +1,35 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PublicKeyInfo
+ *
+ * this format is not formally defined anywhere but is none-the-less the form you
+ * get when you do "openssl rsa -in private.pem -outform PEM -pubout"
+ *
+ * @author Jim Wigginton
+ */
+abstract class PublicKeyInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'publicKeyAlgorithm' => AlgorithmIdentifier::MAP,
+ 'publicKey' => ['type' => ASN1::TYPE_BIT_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/RC2CBCParameter.php b/Sources/Phpseclib/File/ASN1/Maps/RC2CBCParameter.php
new file mode 100644
index 0000000000..48649abd53
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/RC2CBCParameter.php
@@ -0,0 +1,37 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RC2CBCParameter
+ *
+ * from https://tools.ietf.org/html/rfc2898#appendix-A.3
+ *
+ * @author Jim Wigginton
+ */
+abstract class RC2CBCParameter
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'rc2ParametersVersion' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'optional' => true
+ ],
+ 'iv' => ['type' => ASN1::TYPE_OCTET_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/RDNSequence.php b/Sources/Phpseclib/File/ASN1/Maps/RDNSequence.php
new file mode 100644
index 0000000000..04b071c27d
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/RDNSequence.php
@@ -0,0 +1,38 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RDNSequence
+ *
+ * In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare,
+ * but they can be useful at times when either there is no unique attribute in the entry or you
+ * want to ensure that the entry's DN contains some useful identifying information.
+ *
+ * - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName
+ *
+ * @author Jim Wigginton
+ */
+abstract class RDNSequence
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ // RDNSequence does not define a min or a max, which means it doesn't have one
+ 'min' => 0,
+ 'max' => -1,
+ 'children' => RelativeDistinguishedName::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/RSAPrivateKey.php b/Sources/Phpseclib/File/ASN1/Maps/RSAPrivateKey.php
new file mode 100644
index 0000000000..8c19c658ec
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/RSAPrivateKey.php
@@ -0,0 +1,44 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RSAPrivateKey
+ *
+ * @author Jim Wigginton
+ */
+abstract class RSAPrivateKey
+{
+ // version must be multi if otherPrimeInfos present
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => ['two-prime', 'multi']
+ ],
+ 'modulus' => ['type' => ASN1::TYPE_INTEGER], // n
+ 'publicExponent' => ['type' => ASN1::TYPE_INTEGER], // e
+ 'privateExponent' => ['type' => ASN1::TYPE_INTEGER], // d
+ 'prime1' => ['type' => ASN1::TYPE_INTEGER], // p
+ 'prime2' => ['type' => ASN1::TYPE_INTEGER], // q
+ 'exponent1' => ['type' => ASN1::TYPE_INTEGER], // d mod (p-1)
+ 'exponent2' => ['type' => ASN1::TYPE_INTEGER], // d mod (q-1)
+ 'coefficient' => ['type' => ASN1::TYPE_INTEGER], // (inverse of q) mod p
+ 'otherPrimeInfos' => OtherPrimeInfos::MAP + ['optional' => true]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/RSAPublicKey.php b/Sources/Phpseclib/File/ASN1/Maps/RSAPublicKey.php
new file mode 100644
index 0000000000..b14c32c428
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/RSAPublicKey.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RSAPublicKey
+ *
+ * @author Jim Wigginton
+ */
+abstract class RSAPublicKey
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'modulus' => ['type' => ASN1::TYPE_INTEGER],
+ 'publicExponent' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php b/Sources/Phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php
new file mode 100644
index 0000000000..1a784bf4da
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php
@@ -0,0 +1,58 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RSASSA_PSS_params
+ *
+ * @author Jim Wigginton
+ */
+abstract class RSASSA_PSS_params
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'hashAlgorithm' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true,
+ //'default' => 'sha1Identifier'
+ ] + HashAlgorithm::MAP,
+ 'maskGenAlgorithm' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'explicit' => true,
+ //'default' => 'mgf1SHA1Identifier'
+ ] + MaskGenAlgorithm::MAP,
+ 'saltLength' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'constant' => 2,
+ 'optional' => true,
+ 'explicit' => true,
+ 'default' => 20
+ ],
+ 'trailerField' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'constant' => 3,
+ 'optional' => true,
+ 'explicit' => true,
+ 'default' => 1
+ ]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/ReasonFlags.php b/Sources/Phpseclib/File/ASN1/Maps/ReasonFlags.php
new file mode 100644
index 0000000000..2e62fcdb3b
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/ReasonFlags.php
@@ -0,0 +1,39 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ReasonFlags
+ *
+ * @author Jim Wigginton
+ */
+abstract class ReasonFlags
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_BIT_STRING,
+ 'mapping' => [
+ 'unused',
+ 'keyCompromise',
+ 'cACompromise',
+ 'affiliationChanged',
+ 'superseded',
+ 'cessationOfOperation',
+ 'certificateHold',
+ 'privilegeWithdrawn',
+ 'aACompromise'
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php b/Sources/Phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php
new file mode 100644
index 0000000000..a0421f7315
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php
@@ -0,0 +1,37 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RelativeDistinguishedName
+ *
+ * In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare,
+ * but they can be useful at times when either there is no unique attribute in the entry or you
+ * want to ensure that the entry's DN contains some useful identifying information.
+ *
+ * - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName
+ *
+ * @author Jim Wigginton
+ */
+abstract class RelativeDistinguishedName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SET,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => AttributeTypeAndValue::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/RevokedCertificate.php b/Sources/Phpseclib/File/ASN1/Maps/RevokedCertificate.php
new file mode 100644
index 0000000000..ff759eb738
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/RevokedCertificate.php
@@ -0,0 +1,35 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RevokedCertificate
+ *
+ * @author Jim Wigginton
+ */
+abstract class RevokedCertificate
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'userCertificate' => CertificateSerialNumber::MAP,
+ 'revocationDate' => Time::MAP,
+ 'crlEntryExtensions' => [
+ 'optional' => true
+ ] + Extensions::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php b/Sources/Phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php
new file mode 100644
index 0000000000..0f482a2618
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php
@@ -0,0 +1,33 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * SignedPublicKeyAndChallenge
+ *
+ * @author Jim Wigginton
+ */
+abstract class SignedPublicKeyAndChallenge
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'publicKeyAndChallenge' => PublicKeyAndChallenge::MAP,
+ 'signatureAlgorithm' => AlgorithmIdentifier::MAP,
+ 'signature' => ['type' => ASN1::TYPE_BIT_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/SpecifiedECDomain.php b/Sources/Phpseclib/File/ASN1/Maps/SpecifiedECDomain.php
new file mode 100644
index 0000000000..7408a5637d
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/SpecifiedECDomain.php
@@ -0,0 +1,45 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * SpecifiedECDomain
+ *
+ * @author Jim Wigginton
+ */
+abstract class SpecifiedECDomain
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => [1 => 'ecdpVer1', 'ecdpVer2', 'ecdpVer3']
+ ],
+ 'fieldID' => FieldID::MAP,
+ 'curve' => Curve::MAP,
+ 'base' => ECPoint::MAP,
+ 'order' => ['type' => ASN1::TYPE_INTEGER],
+ 'cofactor' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'optional' => true
+ ],
+ 'hash' => ['optional' => true] + HashAlgorithm::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/SubjectAltName.php b/Sources/Phpseclib/File/ASN1/Maps/SubjectAltName.php
new file mode 100644
index 0000000000..39138a94fb
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/SubjectAltName.php
@@ -0,0 +1,24 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+/**
+ * SubjectAltName
+ *
+ * @author Jim Wigginton
+ */
+abstract class SubjectAltName
+{
+ const MAP = GeneralNames::MAP;
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php b/Sources/Phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php
new file mode 100644
index 0000000000..f2e206f6ab
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * SubjectDirectoryAttributes
+ *
+ * @author Jim Wigginton
+ */
+abstract class SubjectDirectoryAttributes
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => Attribute::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php b/Sources/Phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php
new file mode 100644
index 0000000000..1ff241f71d
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php
@@ -0,0 +1,31 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * SubjectInfoAccessSyntax
+ *
+ * @author Jim Wigginton
+ */
+abstract class SubjectInfoAccessSyntax
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => AccessDescription::MAP
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php b/Sources/Phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php
new file mode 100644
index 0000000000..0d53d54015
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * SubjectPublicKeyInfo
+ *
+ * @author Jim Wigginton
+ */
+abstract class SubjectPublicKeyInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'algorithm' => AlgorithmIdentifier::MAP,
+ 'subjectPublicKey' => ['type' => ASN1::TYPE_BIT_STRING]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/TBSCertList.php b/Sources/Phpseclib/File/ASN1/Maps/TBSCertList.php
new file mode 100644
index 0000000000..8e00f4d859
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/TBSCertList.php
@@ -0,0 +1,54 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * TBSCertList
+ *
+ * @author Jim Wigginton
+ */
+abstract class TBSCertList
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => ['v1', 'v2'],
+ 'optional' => true,
+ 'default' => 'v1'
+ ],
+ 'signature' => AlgorithmIdentifier::MAP,
+ 'issuer' => Name::MAP,
+ 'thisUpdate' => Time::MAP,
+ 'nextUpdate' => [
+ 'optional' => true
+ ] + Time::MAP,
+ 'revokedCertificates' => [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'optional' => true,
+ 'min' => 0,
+ 'max' => -1,
+ 'children' => RevokedCertificate::MAP
+ ],
+ 'crlExtensions' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true
+ ] + Extensions::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/TBSCertificate.php b/Sources/Phpseclib/File/ASN1/Maps/TBSCertificate.php
new file mode 100644
index 0000000000..007360c97c
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/TBSCertificate.php
@@ -0,0 +1,65 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * TBSCertificate
+ *
+ * @author Jim Wigginton
+ */
+abstract class TBSCertificate
+{
+ // assert($TBSCertificate['children']['signature'] == $Certificate['children']['signatureAlgorithm'])
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ // technically, default implies optional, but we'll define it as being optional, none-the-less, just to
+ // reenforce that fact
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true,
+ 'mapping' => ['v1', 'v2', 'v3'],
+ 'default' => 'v1'
+ ],
+ 'serialNumber' => CertificateSerialNumber::MAP,
+ 'signature' => AlgorithmIdentifier::MAP,
+ 'issuer' => Name::MAP,
+ 'validity' => Validity::MAP,
+ 'subject' => Name::MAP,
+ 'subjectPublicKeyInfo' => SubjectPublicKeyInfo::MAP,
+ // implicit means that the T in the TLV structure is to be rewritten, regardless of the type
+ 'issuerUniqueID' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + UniqueIdentifier::MAP,
+ 'subjectUniqueID' => [
+ 'constant' => 2,
+ 'optional' => true,
+ 'implicit' => true
+ ] + UniqueIdentifier::MAP,
+ // doesn't use the EXPLICIT keyword but if
+ // it's not IMPLICIT, it's EXPLICIT
+ 'extensions' => [
+ 'constant' => 3,
+ 'optional' => true,
+ 'explicit' => true
+ ] + Extensions::MAP
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/TerminalIdentifier.php b/Sources/Phpseclib/File/ASN1/Maps/TerminalIdentifier.php
new file mode 100644
index 0000000000..7f6d9d2e90
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/TerminalIdentifier.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * TerminalIdentifier
+ *
+ * @author Jim Wigginton
+ */
+abstract class TerminalIdentifier
+{
+ const MAP = ['type' => ASN1::TYPE_PRINTABLE_STRING];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/Time.php b/Sources/Phpseclib/File/ASN1/Maps/Time.php
new file mode 100644
index 0000000000..744ee70496
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/Time.php
@@ -0,0 +1,32 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Time
+ *
+ * @author Jim Wigginton
+ */
+abstract class Time
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'utcTime' => ['type' => ASN1::TYPE_UTC_TIME],
+ 'generalTime' => ['type' => ASN1::TYPE_GENERALIZED_TIME]
+ ]
+ ];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/Trinomial.php b/Sources/Phpseclib/File/ASN1/Maps/Trinomial.php
new file mode 100644
index 0000000000..33baa91e67
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/Trinomial.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Trinomial
+ *
+ * @author Jim Wigginton
+ */
+abstract class Trinomial
+{
+ const MAP = ['type' => ASN1::TYPE_INTEGER];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/UniqueIdentifier.php b/Sources/Phpseclib/File/ASN1/Maps/UniqueIdentifier.php
new file mode 100644
index 0000000000..f4c954bbc5
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/UniqueIdentifier.php
@@ -0,0 +1,26 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * UniqueIdentifier
+ *
+ * @author Jim Wigginton
+ */
+abstract class UniqueIdentifier
+{
+ const MAP = ['type' => ASN1::TYPE_BIT_STRING];
+}
diff --git a/Sources/Phpseclib/File/ASN1/Maps/UserNotice.php b/Sources/Phpseclib/File/ASN1/Maps/UserNotice.php
new file mode 100644
index 0000000000..98d527b7b7
--- /dev/null
+++ b/Sources/Phpseclib/File/ASN1/Maps/UserNotice.php
@@ -0,0 +1,38 @@
+
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * UserNotice
+ *
+ * @author Jim Wigginton