From b92b92fec066349bbf3be17bec141f309544d5ec Mon Sep 17 00:00:00 2001 From: allyans3 Date: Sun, 4 Dec 2022 12:30:27 +0100 Subject: [PATCH] First commit --- .gitignore | 120 + README.md | 66 + composer.json | 25 + images/protobuf-steam-auth-logo.png | Bin 0 -> 97038 bytes src/Configs/SteamConfig.php | 132 + src/Exceptions/SteamErrorException.php | 10 + src/Exceptions/SteamResponseException.php | 10 + src/RSA/Crypt/Crypt_Hash.php | 847 ++++ src/RSA/Crypt/Crypt_RSA.php | 3116 ++++++++++++++ src/RSA/Crypt/Hash.php | 842 ++++ src/RSA/Crypt/Random.php | 300 ++ src/RSA/Math/MathBigInteger.php | 3768 +++++++++++++++++ src/SteamAuth.php | 454 ++ .../CAuthentication_AllowedConfirmation.php | 126 + ...BeginAuthSessionViaCredentials_Request.php | 447 ++ ...eginAuthSessionViaCredentials_Response.php | 326 ++ src/pb2/CAuthentication_DeviceDetails.php | 206 + ...cation_GetPasswordRSAPublicKey_Request.php | 85 + ...ation_GetPasswordRSAPublicKey_Response.php | 165 + ...tication_PollAuthSessionStatus_Request.php | 165 + ...ication_PollAuthSessionStatus_Response.php | 325 ++ ...eAuthSessionWithSteamGuardCode_Request.php | 206 + ...AuthSessionWithSteamGuardCode_Response.php | 45 + src/pb2/EAuthSessionGuardType.php | 38 + src/pb2/EAuthTokenPlatformType.php | 32 + src/pb2/ESessionPersistence.php | 30 + 26 files changed, 11886 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 composer.json create mode 100644 images/protobuf-steam-auth-logo.png create mode 100644 src/Configs/SteamConfig.php create mode 100644 src/Exceptions/SteamErrorException.php create mode 100644 src/Exceptions/SteamResponseException.php create mode 100644 src/RSA/Crypt/Crypt_Hash.php create mode 100644 src/RSA/Crypt/Crypt_RSA.php create mode 100644 src/RSA/Crypt/Hash.php create mode 100644 src/RSA/Crypt/Random.php create mode 100644 src/RSA/Math/MathBigInteger.php create mode 100644 src/SteamAuth.php create mode 100644 src/pb2/CAuthentication_AllowedConfirmation.php create mode 100644 src/pb2/CAuthentication_BeginAuthSessionViaCredentials_Request.php create mode 100644 src/pb2/CAuthentication_BeginAuthSessionViaCredentials_Response.php create mode 100644 src/pb2/CAuthentication_DeviceDetails.php create mode 100644 src/pb2/CAuthentication_GetPasswordRSAPublicKey_Request.php create mode 100644 src/pb2/CAuthentication_GetPasswordRSAPublicKey_Response.php create mode 100644 src/pb2/CAuthentication_PollAuthSessionStatus_Request.php create mode 100644 src/pb2/CAuthentication_PollAuthSessionStatus_Response.php create mode 100644 src/pb2/CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request.php create mode 100644 src/pb2/CAuthentication_UpdateAuthSessionWithSteamGuardCode_Response.php create mode 100644 src/pb2/EAuthSessionGuardType.php create mode 100644 src/pb2/EAuthTokenPlatformType.php create mode 100644 src/pb2/ESessionPersistence.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3346489 --- /dev/null +++ b/.gitignore @@ -0,0 +1,120 @@ +# Created by .ignore support plugin (hsz.mobi) +### Composer template +composer.phar +/vendor/ + +# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control +# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file +# composer.lock + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +/.idea +/.idea/* +.idea/ +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Windows template +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +/example +/example/* + +composer.lock + +/tmp/ +/tmp/* + +index.php \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c9c6bbf --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +

Protobuf-Steam-Auth

+ +

+Latest Stable Version +Total Downloads +License +

+ +## About Protobuf Steam Auth + +This package provides the ability to authorize to the Steam using Google Protobuf. + +## Installation + +Run this text in a console to install this package from Packagist: + +``` +composer require allyans3/protobuf-steam-auth +``` + +## Usage + +```php +use SteamAuth\SteamAuth; + +require "vendor/autoload.php"; + +$auth = new SteamAuth('login', 'password', 'shared_secret'); + +$auth->login(); + +// You can check if you are authorized +$auth->isAuthorized(); + +// If auth `true` you can get cookies +$auth->getCookies(); +// or by host +$auth->getCookiesByHost(); + +``` + +## Handle Exceptions + +```php +use SteamAuth\SteamAuth; + +require "vendor/autoload.php"; + +$auth = new SteamAuth('login', 'password', 'shared_secret'); + +try { + $auth->login(); +} catch (\SteamAuth\Exceptions\SteamErrorException $e) { + $e->getMessage(); +} catch (\SteamAuth\Exceptions\SteamResponseException $e) { + $e->getMessage(); +} +``` + +## Support + +Report bugs on the [issue tracker](https://github.com/Allyans3/protobuf-steam-auth/issues). + +## License + +Protobuf Steam Auth is open-sourced Composer package licensed under the [MIT license](https://opensource.org/licenses/MIT). \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..480b8b5 --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "allyans3/protobuf-steam-auth", + "description": "description", + "minimum-stability": "stable", + "license": "proprietary", + "authors": [ + { + "name": "allyans3", + "email": "email@example.com" + } + ], + "require": { + "allegro/php-protobuf": "^0.12.4", + "php-curl-class/php-curl-class": "^9.10", + "doctormckay/steam-totp": "^1.0" + }, + "autoload": { + "psr-4": { + "SteamAuth\\": "src/" + } + }, + "require-dev": { + "symfony/var-dumper": "^5.4" + } +} diff --git a/images/protobuf-steam-auth-logo.png b/images/protobuf-steam-auth-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1a130243fc8a42b0c4138ecf77ecd563d77fa0cb GIT binary patch literal 97038 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

AdOY^UmsOnl8mB5Q2tO2t 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 "OK", + 2 => "Fail", + 3 => "NoConnection", + 4 => "NoConnectionRetry", + 5 => "InvalidPassword", + 6 => "LoggedInElsewhere", + 7 => "InvalidProtocolVer", + 8 => "InvalidParam", + 9 => "FileNotFound", + 10 => "Busy", + 11 => "InvalidState", + 12 => "InvalidName", + 13 => "InvalidEmail", + 14 => "DuplicateName", + 15 => "AccessDenied", + 16 => "Timeout", + 17 => "Banned", + 18 => "AccountNotFound", + 19 => "InvalidSteamID", + 20 => "ServiceUnavailable", + 21 => "NotLoggedOn", + 22 => "Pending", + 23 => "EncryptionFailure", + 24 => "InsufficientPrivilege", + 25 => "LimitExceeded", + 26 => "Revoked", + 27 => "Expired", + 28 => "AlreadyRedeemed", + 29 => "DuplicateRequest", + 30 => "AlreadyOwned", + 31 => "IPNotFound", + 32 => "PersistFailed", + 33 => "LockingFailed", + 34 => "LogonSessionReplaced", + 35 => "ConnectFailed", + 36 => "HandshakeFailed", + 37 => "IOFailure", + 38 => "RemoteDisconnect", + 39 => "ShoppingCartNotFound", + 40 => "Blocked", + 41 => "Ignored", + 42 => "NoMatch", + 43 => "AccountDisabled", + 44 => "ServiceReadOnly", + 45 => "AccountNotFeatured", + 46 => "AdministratorOK", + 47 => "ContentVersion", + 48 => "TryAnotherCM", + 49 => "PasswordRequiredToKickSession", + 50 => "AlreadyLoggedInElsewhere", + 51 => "Suspended", + 52 => "Cancelled", + 53 => "DataCorruption", + 54 => "DiskFull", + 55 => "RemoteCallFailed", + 56 => "PasswordUnset", + 57 => "ExternalAccountUnlinked", + 58 => "PSNTicketInvalid", + 59 => "ExternalAccountAlreadyLinked", + 60 => "RemoteFileConflict", + 61 => "IllegalPassword", + 62 => "SameAsPreviousValue", + 63 => "AccountLogonDenied", + 64 => "CannotUseOldPassword", + 65 => "InvalidLoginAuthCode", + 66 => "AccountLogonDeniedNoMail", + 67 => "HardwareNotCapableOfIPT", + 68 => "IPTInitError", + 69 => "ParentalControlRestricted", + 70 => "FacebookQueryError", + 71 => "ExpiredLoginAuthCode", + 72 => "IPLoginRestrictionFailed", + 73 => "AccountLockedDown", + 74 => "AccountLogonDeniedVerifiedEmailRequired", + 75 => "NoMatchingURL", + 76 => "BadResponse", + 77 => "RequirePasswordReEntry", + 78 => "ValueOutOfRange", + 79 => "UnexpectedError", + 80 => "Disabled", + 81 => "InvalidCEGSubmission", + 82 => "RestrictedDevice", + 83 => "RegionLocked", + 84 => "RateLimitExceeded", + 85 => "AccountLoginDeniedNeedTwoFactor", + 86 => "ItemDeleted", + 87 => "AccountLoginDeniedThrottle", + 88 => "TwoFactorCodeMismatch", + 89 => "TwoFactorActivationCodeMismatch", + 90 => "AccountAssociatedToMultiplePartners", + 91 => "NotModified", + 92 => "NoMobileDevice", + 93 => "TimeNotSynced", + 94 => "SmsCodeFailed", + 95 => "AccountLimitExceeded", + 96 => "AccountActivityLimitExceeded", + 97 => "PhoneActivityLimitExceeded", + 98 => "RefundToWallet", + 99 => "EmailSendFailure", + 100 => "NotSettled", + 101 => "NeedCaptcha", + 102 => "GSLTDenied", + 103 => "GSOwnerDenied", + 104 => "InvalidItemType", + 105 => "IPBanned", + 106 => "GSLTExpired", + 107 => "InsufficientFunds", + 108 => "TooManyPending", + 109 => "NoSiteLicensesFound", + 110 => "WGNetworkSendExceeded", + 111 => "AccountNotFriends", + 112 => "LimitedUserAccount", + 113 => "CantRemoveItem", + 114 => "AccountDeleted", + 115 => "ExistingUserCancelledLicense", + 116 => "CommunityCooldown", + 117 => "NoLauncherSpecified", + 118 => "MustAgreeToSSA", + 119 => "LauncherMigrated", + 120 => "SteamRealmMismatch", + 121 => "InvalidSignature", + 122 => "ParseFailure", + 123 => "NoVerifiedPhone" + ]; +} \ No newline at end of file diff --git a/src/Exceptions/SteamErrorException.php b/src/Exceptions/SteamErrorException.php new file mode 100644 index 0000000..8be66ab --- /dev/null +++ b/src/Exceptions/SteamErrorException.php @@ -0,0 +1,10 @@ + + * setKey('abcdefg'); + * + * echo base64_encode($hash->hash('abcdefg')); + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_Hash + * @author Jim Wigginton + * @copyright 2007 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access private + * @see Crypt_Hash::Crypt_Hash() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_HASH_MODE_INTERNAL', 1); +/** + * Toggles the mhash() implementation, which has been deprecated on PHP 5.3.0+. + */ +define('CRYPT_HASH_MODE_MHASH', 2); +/** + * Toggles the hash() implementation, which works on PHP 5.1.2+. + */ +define('CRYPT_HASH_MODE_HASH', 3); +/**#@-*/ + +/** + * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions. + * + * @package Crypt_Hash + * @author Jim Wigginton + * @access public + */ +class Crypt_Hash +{ + /** + * Hash Parameter + * + * @see Crypt_Hash::setHash() + * @var Integer + * @access private + */ + var $hashParam; + + /** + * Byte-length of compression blocks / key (Internal HMAC) + * + * @see Crypt_Hash::setAlgorithm() + * @var Integer + * @access private + */ + var $b; + + /** + * Byte-length of hash output (Internal HMAC) + * + * @see Crypt_Hash::setHash() + * @var Integer + * @access private + */ + var $l = false; + + /** + * Hash Algorithm + * + * @see Crypt_Hash::setHash() + * @var String + * @access private + */ + var $hash; + + /** + * Key + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $key = false; + + /** + * Outer XOR (Internal HMAC) + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $opad; + + /** + * Inner XOR (Internal HMAC) + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $ipad; + + /** + * Default Constructor. + * + * @param optional String $hash + * @return Crypt_Hash + * @access public + */ + public function __construct($hash = 'sha1') + { + if (!defined('CRYPT_HASH_MODE')) { + switch (true) { + case extension_loaded('hash'): + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_HASH); + break; + case extension_loaded('mhash'): + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_MHASH); + break; + default: + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_INTERNAL); + } + } + + $this->setHash($hash); + } + + /** + * Sets the key for HMACs + * + * Keys can be of any length. + * + * @access public + * @param optional String $key + */ + function setKey($key = false) + { + $this->key = $key; + } + + /** + * Gets the hash function. + * + * As set by the constructor or by the setHash() method. + * + * @access public + * @return String + */ + function getHash() + { + return $this->hashParam; + } + + /** + * Sets the hash function. + * + * @access public + * @param String $hash + */ + function setHash($hash) + { + $this->hashParam = $hash = strtolower($hash); + switch ($hash) { + case 'md5-96': + case 'sha1-96': + case 'sha256-96': + case 'sha512-96': + $hash = substr($hash, 0, -3); + $this->l = 12; // 96 / 8 = 12 + break; + case 'md2': + case 'md5': + $this->l = 16; + break; + case 'sha1': + $this->l = 20; + break; + case 'sha256': + $this->l = 32; + break; + case 'sha384': + $this->l = 48; + break; + case 'sha512': + $this->l = 64; + } + + switch ($hash) { + case 'md2': + $mode = CRYPT_HASH_MODE == CRYPT_HASH_MODE_HASH && in_array('md2', hash_algos()) ? + CRYPT_HASH_MODE_HASH : CRYPT_HASH_MODE_INTERNAL; + break; + case 'sha384': + case 'sha512': + $mode = CRYPT_HASH_MODE == CRYPT_HASH_MODE_MHASH ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; + break; + default: + $mode = CRYPT_HASH_MODE; + } + + switch ($mode) { + case CRYPT_HASH_MODE_MHASH: + switch ($hash) { + case 'md5': + $this->hash = MHASH_MD5; + break; + case 'sha256': + $this->hash = MHASH_SHA256; + break; + case 'sha1': + default: + $this->hash = MHASH_SHA1; + } + return; + case CRYPT_HASH_MODE_HASH: + switch ($hash) { + case 'md5': + $this->hash = 'md5'; + return; + case 'md2': + case 'sha256': + case 'sha384': + case 'sha512': + $this->hash = $hash; + return; + case 'sha1': + default: + $this->hash = 'sha1'; + } + return; + } + + switch ($hash) { + case 'md2': + $this->b = 16; + $this->hash = array($this, '_md2'); + break; + case 'md5': + $this->b = 64; + $this->hash = array($this, '_md5'); + break; + case 'sha256': + $this->b = 64; + $this->hash = array($this, '_sha256'); + break; + case 'sha384': + case 'sha512': + $this->b = 128; + $this->hash = array($this, '_sha512'); + break; + case 'sha1': + default: + $this->b = 64; + $this->hash = array($this, '_sha1'); + } + + $this->ipad = str_repeat(chr(0x36), $this->b); + $this->opad = str_repeat(chr(0x5C), $this->b); + } + + /** + * Compute the HMAC. + * + * @access public + * @param String $text + * @return String + */ + function hash($text) + { + $mode = is_array($this->hash) ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; + + if (!empty($this->key) || is_string($this->key)) { + switch ($mode) { + case CRYPT_HASH_MODE_MHASH: + $output = mhash($this->hash, $text, $this->key); + break; + case CRYPT_HASH_MODE_HASH: + $output = hash_hmac($this->hash, $text, $this->key, true); + break; + case CRYPT_HASH_MODE_INTERNAL: + /* "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." + + -- http://tools.ietf.org/html/rfc2104#section-2 */ + $key = strlen($this->key) > $this->b ? call_user_func($this->hash, $this->key) : $this->key; + + $key = str_pad($key, $this->b, chr(0)); // step 1 + $temp = $this->ipad ^ $key; // step 2 + $temp .= $text; // step 3 + $temp = call_user_func($this->hash, $temp); // step 4 + $output = $this->opad ^ $key; // step 5 + $output.= $temp; // step 6 + $output = call_user_func($this->hash, $output); // step 7 + } + } else { + switch ($mode) { + case CRYPT_HASH_MODE_MHASH: + $output = mhash($this->hash, $text); + break; + case CRYPT_HASH_MODE_HASH: + $output = hash($this->hash, $text, true); + break; + case CRYPT_HASH_MODE_INTERNAL: + $output = call_user_func($this->hash, $text); + } + } + + return substr($output, 0, $this->l); + } + + /** + * Returns the hash length (in bytes) + * + * @access public + * @return Integer + */ + function getLength() + { + return $this->l; + } + + /** + * Wrapper for MD5 + * + * @access private + * @param String $m + */ + function _md5($m) + { + return pack('H*', md5($m)); + } + + /** + * Wrapper for SHA1 + * + * @access private + * @param String $m + */ + function _sha1($m) + { + return pack('H*', sha1($m)); + } + + /** + * Pure-PHP implementation of MD2 + * + * See {@link http://tools.ietf.org/html/rfc1319 RFC1319}. + * + * @access private + * @param String $m + */ + function _md2($m) + { + static $s = array( + 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, + 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, + 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, + 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, + 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, + 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, + 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, + 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, + 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, + 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, + 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, + 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, + 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, + 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, + 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, + 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, + 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, + 31, 26, 219, 153, 141, 51, 159, 17, 131, 20 + ); + + // Step 1. Append Padding Bytes + $pad = 16 - (strlen($m) & 0xF); + $m.= str_repeat(chr($pad), $pad); + + $length = strlen($m); + + // Step 2. Append Checksum + $c = str_repeat(chr(0), 16); + $l = chr(0); + for ($i = 0; $i < $length; $i+= 16) { + for ($j = 0; $j < 16; $j++) { + // RFC1319 incorrectly states that C[j] should be set to S[c xor L] + //$c[$j] = chr($s[ord($m[$i + $j] ^ $l)]); + // per , however, C[j] should be set to S[c xor L] xor C[j] + $c[$j] = chr($s[ord($m[$i + $j] ^ $l)] ^ ord($c[$j])); + $l = $c[$j]; + } + } + $m.= $c; + + $length+= 16; + + // Step 3. Initialize MD Buffer + $x = str_repeat(chr(0), 48); + + // Step 4. Process Message in 16-Byte Blocks + for ($i = 0; $i < $length; $i+= 16) { + for ($j = 0; $j < 16; $j++) { + $x[$j + 16] = $m[$i + $j]; + $x[$j + 32] = $x[$j + 16] ^ $x[$j]; + } + $t = chr(0); + for ($j = 0; $j < 18; $j++) { + for ($k = 0; $k < 48; $k++) { + $x[$k] = $t = $x[$k] ^ chr($s[ord($t)]); + //$t = $x[$k] = $x[$k] ^ chr($s[ord($t)]); + } + $t = chr(ord($t) + $j); + } + } + + // Step 5. Output + return substr($x, 0, 16); + } + + /** + * Pure-PHP implementation of SHA256 + * + * See {@link http://en.wikipedia.org/wiki/SHA_hash_functions#SHA-256_.28a_SHA-2_variant.29_pseudocode SHA-256 (a SHA-2 variant) pseudocode - Wikipedia}. + * + * @access private + * @param String $m + */ + function _sha256($m) + { + if (extension_loaded('suhosin')) { + return pack('H*', sha256($m)); + } + + // Initialize variables + $hash = array( + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + ); + // Initialize table of round constants + // (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311) + static $k = array( + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ); + + // Pre-processing + $length = strlen($m); + // to round to nearest 56 mod 64, we'll add 64 - (length + (64 - 56)) % 64 + $m.= str_repeat(chr(0), 64 - (($length + 8) & 0x3F)); + $m[$length] = chr(0x80); + // we don't support hashing strings 512MB long + $m.= pack('N2', 0, $length << 3); + + // Process the message in successive 512-bit chunks + $chunks = str_split($m, 64); + foreach ($chunks as $chunk) { + $w = array(); + for ($i = 0; $i < 16; $i++) { + extract(unpack('Ntemp', $this->_string_shift($chunk, 4))); + $w[] = $temp; + } + + // Extend the sixteen 32-bit words into sixty-four 32-bit words + for ($i = 16; $i < 64; $i++) { + // @codingStandardsIgnoreStart + $s0 = $this->_rightRotate($w[$i - 15], 7) ^ + $this->_rightRotate($w[$i - 15], 18) ^ + $this->_rightShift( $w[$i - 15], 3); + $s1 = $this->_rightRotate($w[$i - 2], 17) ^ + $this->_rightRotate($w[$i - 2], 19) ^ + $this->_rightShift( $w[$i - 2], 10); + // @codingStandardsIgnoreEnd + $w[$i] = $this->_add($w[$i - 16], $s0, $w[$i - 7], $s1); + + } + + // Initialize hash value for this chunk + list($a, $b, $c, $d, $e, $f, $g, $h) = $hash; + + // Main loop + for ($i = 0; $i < 64; $i++) { + $s0 = $this->_rightRotate($a, 2) ^ + $this->_rightRotate($a, 13) ^ + $this->_rightRotate($a, 22); + $maj = ($a & $b) ^ + ($a & $c) ^ + ($b & $c); + $t2 = $this->_add($s0, $maj); + + $s1 = $this->_rightRotate($e, 6) ^ + $this->_rightRotate($e, 11) ^ + $this->_rightRotate($e, 25); + $ch = ($e & $f) ^ + ($this->_not($e) & $g); + $t1 = $this->_add($h, $s1, $ch, $k[$i], $w[$i]); + + $h = $g; + $g = $f; + $f = $e; + $e = $this->_add($d, $t1); + $d = $c; + $c = $b; + $b = $a; + $a = $this->_add($t1, $t2); + } + + // Add this chunk's hash to result so far + $hash = array( + $this->_add($hash[0], $a), + $this->_add($hash[1], $b), + $this->_add($hash[2], $c), + $this->_add($hash[3], $d), + $this->_add($hash[4], $e), + $this->_add($hash[5], $f), + $this->_add($hash[6], $g), + $this->_add($hash[7], $h) + ); + } + + // Produce the final hash value (big-endian) + return pack('N8', $hash[0], $hash[1], $hash[2], $hash[3], $hash[4], $hash[5], $hash[6], $hash[7]); + } + + /** + * Pure-PHP implementation of SHA384 and SHA512 + * + * @access private + * @param String $m + */ + function _sha512($m) + { + if (!class_exists('MathBigInteger')) { + include_once 'Math/BigInteger.php'; + } + + static $init384, $init512, $k; + + if (!isset($k)) { + // Initialize variables + $init384 = array( // initial values for SHA384 + 'cbbb9d5dc1059ed8', '629a292a367cd507', '9159015a3070dd17', '152fecd8f70e5939', + '67332667ffc00b31', '8eb44a8768581511', 'db0c2e0d64f98fa7', '47b5481dbefa4fa4' + ); + $init512 = array( // initial values for SHA512 + '6a09e667f3bcc908', 'bb67ae8584caa73b', '3c6ef372fe94f82b', 'a54ff53a5f1d36f1', + '510e527fade682d1', '9b05688c2b3e6c1f', '1f83d9abfb41bd6b', '5be0cd19137e2179' + ); + + for ($i = 0; $i < 8; $i++) { + $init384[$i] = new MathBigInteger($init384[$i], 16); + $init384[$i]->setPrecision(64); + $init512[$i] = new MathBigInteger($init512[$i], 16); + $init512[$i]->setPrecision(64); + } + + // Initialize table of round constants + // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409) + $k = array( + '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 MathBigInteger($k[$i], 16); + } + } + + $hash = $this->l == 48 ? $init384 : $init512; + + // 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 = array(); + for ($i = 0; $i < 16; $i++) { + $temp = new MathBigInteger($this->_string_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 = array( + $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 = array( + $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] = $w[$i - 16]->copy(); + $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 = $hash[0]->copy(); + $b = $hash[1]->copy(); + $c = $hash[2]->copy(); + $d = $hash[3]->copy(); + $e = $hash[4]->copy(); + $f = $hash[5]->copy(); + $g = $hash[6]->copy(); + $h = $hash[7]->copy(); + + // Main loop + for ($i = 0; $i < 80; $i++) { + $temp = array( + $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 = array( + $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 = array( + $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 = array( + $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 = $g->copy(); + $g = $f->copy(); + $f = $e->copy(); + $e = $d->add($t1); + $d = $c->copy(); + $c = $b->copy(); + $b = $a->copy(); + $a = $t1->add($t2); + } + + // Add this chunk's hash to result so far + $hash = array( + $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) + // (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(); + if ($this->l != 48) { + $temp.= $hash[6]->toBytes() . $hash[7]->toBytes(); + } + + return $temp; + } + + /** + * Right Rotate + * + * @access private + * @param Integer $int + * @param Integer $amt + * @see _sha256() + * @return Integer + */ + function _rightRotate($int, $amt) + { + $invamt = 32 - $amt; + $mask = (1 << $invamt) - 1; + return (($int << $invamt) & 0xFFFFFFFF) | (($int >> $amt) & $mask); + } + + /** + * Right Shift + * + * @access private + * @param Integer $int + * @param Integer $amt + * @see _sha256() + * @return Integer + */ + function _rightShift($int, $amt) + { + $mask = (1 << (32 - $amt)) - 1; + return ($int >> $amt) & $mask; + } + + /** + * Not + * + * @access private + * @param Integer $int + * @see _sha256() + * @return Integer + */ + function _not($int) + { + return ~$int & 0xFFFFFFFF; + } + + /** + * Add + * + * _sha256() adds multiple unsigned 32-bit integers. Since PHP doesn't support unsigned integers and since the + * possibility of overflow exists, care has to be taken. MathBigInteger() could be used but this should be faster. + * + * @param Integer $... + * @return Integer + * @see _sha256() + * @access private + */ + function _add() + { + static $mod; + if (!isset($mod)) { + $mod = pow(2, 32); + } + + $result = 0; + $arguments = func_get_args(); + foreach ($arguments as $argument) { + $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument; + } + + return fmod($result, $mod); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } +} diff --git a/src/RSA/Crypt/Crypt_RSA.php b/src/RSA/Crypt/Crypt_RSA.php new file mode 100644 index 0000000..327dc0f --- /dev/null +++ b/src/RSA/Crypt/Crypt_RSA.php @@ -0,0 +1,3116 @@ + + * createKey()); + * + * $plaintext = 'terrafrost'; + * + * $rsa->loadKey($privatekey); + * $ciphertext = $rsa->encrypt($plaintext); + * + * $rsa->loadKey($publickey); + * echo $rsa->decrypt($ciphertext); + * ?> + * + * + * Here's an example of how to create signatures and verify signatures with this library: + * + * createKey()); + * + * $plaintext = 'terrafrost'; + * + * $rsa->loadKey($privatekey); + * $signature = $rsa->sign($plaintext); + * + * $rsa->loadKey($publickey); + * echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified'; + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_RSA + * @author Jim Wigginton + * @copyright 2009 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Crypt_Random + */ +// the class_exists() will only be called if the crypt_random_string function hasn't been defined and +// will trigger a call to __autoload() if you're wanting to auto-load classes +// call function_exists() a second time to stop the include_once from being called outside +// of the auto loader +if (!function_exists('crypt_random_string')) { + include_once 'Random.php'; +} + +/** + * Include Crypt_Hash + */ +// if (!class_exists('Crypt_Hash')) { +// include_once 'Hash.php'; +// } + +/**#@+ + * @access public + * @see Crypt_RSA::encrypt() + * @see Crypt_RSA::decrypt() + */ +/** + * Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding} + * (OAEP) for encryption / decryption. + * + * Uses sha1 by default. + * + * @see Crypt_RSA::setHash() + * @see Crypt_RSA::setMGFHash() + */ +define('CRYPT_RSA_ENCRYPTION_OAEP', 1); +/** + * Use PKCS#1 padding. + * + * Although CRYPT_RSA_ENCRYPTION_OAEP offers more security, including PKCS#1 padding is necessary for purposes of backwards + * compatibility with protocols (like SSH-1) written before OAEP's introduction. + */ +define('CRYPT_RSA_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. + */ +define('CRYPT_RSA_ENCRYPTION_NONE', 3); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_RSA::sign() + * @see Crypt_RSA::verify() + * @see Crypt_RSA::setHash() + */ +/** + * Use the Probabilistic Signature Scheme for signing + * + * Uses sha1 by default. + * + * @see Crypt_RSA::setSaltLength() + * @see Crypt_RSA::setMGFHash() + */ +define('CRYPT_RSA_SIGNATURE_PSS', 1); +/** + * Use the PKCS#1 scheme by default. + * + * Although CRYPT_RSA_SIGNATURE_PSS offers more security, including PKCS#1 signing is necessary for purposes of backwards + * compatibility with protocols (like SSH-2) written before PSS's introduction. + */ +define('CRYPT_RSA_SIGNATURE_PKCS1', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_RSA::createKey() + */ +/** + * ASN1 Integer + */ +define('CRYPT_RSA_ASN1_INTEGER', 2); +/** + * ASN1 Bit String + */ +define('CRYPT_RSA_ASN1_BITSTRING', 3); +/** + * ASN1 Octet String + */ +define('CRYPT_RSA_ASN1_OCTETSTRING', 4); +/** + * ASN1 Object Identifier + */ +define('CRYPT_RSA_ASN1_OBJECT', 6); +/** + * ASN1 Sequence (with the constucted bit set) + */ +define('CRYPT_RSA_ASN1_SEQUENCE', 48); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_RSA::Crypt_RSA() + */ +/** + * To use the pure-PHP implementation + */ +define('CRYPT_RSA_MODE_INTERNAL', 1); +/** + * To use the OpenSSL library + * + * (if enabled; otherwise, the internal implementation will be used) + */ +define('CRYPT_RSA_MODE_OPENSSL', 2); +/**#@-*/ + +/** + * Default openSSL configuration file. + */ +define('CRYPT_RSA_OPENSSL_CONFIG', dirname(__FILE__) . '/../openssl.cnf'); + +/**#@+ + * @access public + * @see Crypt_RSA::createKey() + * @see Crypt_RSA::setPrivateKeyFormat() + */ +/** + * PKCS#1 formatted private key + * + * Used by OpenSSH + */ +define('CRYPT_RSA_PRIVATE_FORMAT_PKCS1', 0); +/** + * PuTTY formatted private key + */ +define('CRYPT_RSA_PRIVATE_FORMAT_PUTTY', 1); +/** + * XML formatted private key + */ +define('CRYPT_RSA_PRIVATE_FORMAT_XML', 2); +/** + * PKCS#8 formatted private key + */ +define('CRYPT_RSA_PRIVATE_FORMAT_PKCS8', 3); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_RSA::createKey() + * @see Crypt_RSA::setPublicKeyFormat() + */ +/** + * Raw public key + * + * An array containing two MathBigInteger objects. + * + * The exponent can be indexed with any of the following: + * + * 0, e, exponent, publicExponent + * + * The modulus can be indexed with any of the following: + * + * 1, n, modulo, modulus + */ +define('CRYPT_RSA_PUBLIC_FORMAT_RAW', 3); +/** + * PKCS#1 formatted public key (raw) + * + * Used by File/X509.php + * + * Has the following header: + * + * -----BEGIN RSA PUBLIC KEY----- + * + * Analogous to ssh-keygen's pem format (as specified by -m) + */ +define('CRYPT_RSA_PUBLIC_FORMAT_PKCS1', 4); +define('CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW', 4); +/** + * XML formatted public key + */ +define('CRYPT_RSA_PUBLIC_FORMAT_XML', 5); +/** + * OpenSSH formatted public key + * + * Place in $HOME/.ssh/authorized_keys + */ +define('CRYPT_RSA_PUBLIC_FORMAT_OPENSSH', 6); +/** + * PKCS#1 formatted public key (encapsulated) + * + * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set) + * + * Has the following header: + * + * -----BEGIN PUBLIC KEY----- + * + * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8 + * is specific to private keys it's basically creating a DER-encoded wrapper + * for keys. This just extends that same concept to public keys (much like ssh-keygen) + */ +define('CRYPT_RSA_PUBLIC_FORMAT_PKCS8', 7); +/**#@-*/ + +/** + * Pure-PHP PKCS#1 compliant implementation of RSA. + * + * @package Crypt_RSA + * @author Jim Wigginton + * @access public + */ +class Crypt_RSA +{ + /** + * Precomputed Zero + * + * @var Array + * @access private + */ + var $zero; + + /** + * Precomputed One + * + * @var Array + * @access private + */ + var $one; + + /** + * Private Key Format + * + * @var Integer + * @access private + */ + var $privateKeyFormat = CRYPT_RSA_PRIVATE_FORMAT_PKCS1; + + /** + * Public Key Format + * + * @var Integer + * @access public + */ + var $publicKeyFormat = CRYPT_RSA_PUBLIC_FORMAT_PKCS8; + + /** + * Modulus (ie. n) + * + * @var MathBigInteger + * @access private + */ + var $modulus; + + /** + * Modulus length + * + * @var MathBigInteger + * @access private + */ + var $k; + + /** + * Exponent (ie. e or d) + * + * @var MathBigInteger + * @access private + */ + var $exponent; + + /** + * Primes for Chinese Remainder Theorem (ie. p and q) + * + * @var Array + * @access private + */ + var $primes; + + /** + * Exponents for Chinese Remainder Theorem (ie. dP and dQ) + * + * @var Array + * @access private + */ + var $exponents; + + /** + * Coefficients for Chinese Remainder Theorem (ie. qInv) + * + * @var Array + * @access private + */ + var $coefficients; + + /** + * Hash name + * + * @var String + * @access private + */ + var $hashName; + + /** + * Hash function + * + * @var Crypt_Hash + * @access private + */ + var $hash; + + /** + * Length of hash function output + * + * @var Integer + * @access private + */ + var $hLen; + + /** + * Length of salt + * + * @var Integer + * @access private + */ + var $sLen; + + /** + * Hash function for the Mask Generation Function + * + * @var Crypt_Hash + * @access private + */ + var $mgfHash; + + /** + * Length of MGF hash function output + * + * @var Integer + * @access private + */ + var $mgfHLen; + + /** + * Encryption mode + * + * @var Integer + * @access private + */ + var $encryptionMode = CRYPT_RSA_ENCRYPTION_OAEP; + + /** + * Signature mode + * + * @var Integer + * @access private + */ + var $signatureMode = CRYPT_RSA_SIGNATURE_PSS; + + /** + * Public Exponent + * + * @var Mixed + * @access private + */ + var $publicExponent = false; + + /** + * Password + * + * @var String + * @access private + */ + var $password = false; + + /** + * Components + * + * For use with parsing XML formatted keys. PHP's XML Parser functions use utilized - instead of PHP's DOM functions - + * because PHP's XML Parser functions work on PHP4 whereas PHP's DOM functions - although surperior - don't. + * + * @see Crypt_RSA::_start_element_handler() + * @var Array + * @access private + */ + var $components = array(); + + /** + * Current String + * + * For use with parsing XML formatted keys. + * + * @see Crypt_RSA::_character_handler() + * @see Crypt_RSA::_stop_element_handler() + * @var Mixed + * @access private + */ + var $current; + + /** + * OpenSSL configuration file name. + * + * Set to null to use system configuration file. + * @see Crypt_RSA::createKey() + * @var Mixed + * @Access public + */ + var $configFile; + + /** + * Public key comment field. + * + * @var String + * @access private + */ + var $comment = 'phpseclib-generated-key'; + + /** + * The constructor + * + * If you want to make use of the openssl extension, you'll need to set the mode manually, yourself. The reason + * Crypt_RSA doesn't do it is because OpenSSL doesn't fail gracefully. openssl_pkey_new(), in particular, requires + * openssl.cnf be present somewhere and, unfortunately, the only real way to find out is too late. + * + * @return Crypt_RSA + * @access public + */ + public function __construct() + { + // if (!class_exists('MathBigInteger')) { + // include_once 'Math/MathBigInteger.php'; + // } + + $this->configFile = CRYPT_RSA_OPENSSL_CONFIG; + + if (!defined('CRYPT_RSA_MODE')) { + switch (true) { + // Math/BigInteger's openssl requirements are a little less stringent than Crypt/RSA's. in particular, + // Math/BigInteger doesn't require an openssl.cfg file whereas Crypt/RSA does. so if Math/BigInteger + // can't use OpenSSL it can be pretty trivially assumed, then, that Crypt/RSA can't either. + case defined('MATH_BIGINTEGER_OPENSSL_DISABLE'): + define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL); + break; + // openssl_pkey_get_details - which is used in the only place Crypt/RSA.php uses OpenSSL - was introduced in PHP 5.2.0 + case !function_exists('openssl_pkey_get_details'): + define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL); + break; + case extension_loaded('openssl') && version_compare(PHP_VERSION, '4.2.0', '>=') && file_exists($this->configFile): + // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work + ob_start(); + @phpinfo(); + $content = ob_get_contents(); + ob_end_clean(); + + preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches); + + $versions = array(); + if (!empty($matches[1])) { + for ($i = 0; $i < count($matches[1]); $i++) { + $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i]))); + + // Remove letter part in OpenSSL version + if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) { + $versions[$matches[1][$i]] = $fullVersion; + } else { + $versions[$matches[1][$i]] = $m[0]; + } + } + } + + // it doesn't appear that OpenSSL versions were reported upon until PHP 5.3+ + switch (true) { + case !isset($versions['Header']): + case !isset($versions['Library']): + case $versions['Header'] == $versions['Library']: + define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_OPENSSL); + break; + default: + define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL); + define('MATH_BIGINTEGER_OPENSSL_DISABLE', true); + } + break; + default: + define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL); + } + } + + $this->zero = new MathBigInteger(); + $this->one = new MathBigInteger(1); + + $this->hash = new Crypt_Hash('sha1'); + $this->hLen = $this->hash->getLength(); + $this->hashName = 'sha1'; + $this->mgfHash = new Crypt_Hash('sha1'); + $this->mgfHLen = $this->mgfHash->getLength(); + } + + /** + * Create public / private key pair + * + * Returns an array with the following three elements: + * - 'privatekey': The private key. + * - 'publickey': The public key. + * - 'partialkey': A partially computed key (if the execution time exceeded $timeout). + * Will need to be passed back to Crypt_RSA::createKey() as the third parameter for further processing. + * + * @access public + * @param optional Integer $bits + * @param optional Integer $timeout + * @param optional MathBigInteger $p + */ + function createKey($bits = 1024, $timeout = false, $partial = array()) + { + if (!defined('CRYPT_RSA_EXPONENT')) { + // http://en.wikipedia.org/wiki/65537_%28number%29 + define('CRYPT_RSA_EXPONENT', '65537'); + } + // 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 CRYPT_RSA_SMALLEST_PRIME + // to 384 bits then you're going to get a 384 bit prime and a 640 bit prime (384 + 1024 % 384). at least if + // CRYPT_RSA_MODE is set to CRYPT_RSA_MODE_INTERNAL. if CRYPT_RSA_MODE is set to CRYPT_RSA_MODE_OPENSSL then + // CRYPT_RSA_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) + if (!defined('CRYPT_RSA_SMALLEST_PRIME')) { + define('CRYPT_RSA_SMALLEST_PRIME', 4096); + } + + // OpenSSL uses 65537 as the exponent and requires RSA keys be 384 bits minimum + if (CRYPT_RSA_MODE == CRYPT_RSA_MODE_OPENSSL && $bits >= 384 && CRYPT_RSA_EXPONENT == 65537) { + $config = array(); + if (isset($this->configFile)) { + $config['config'] = $this->configFile; + } + $rsa = openssl_pkey_new(array('private_key_bits' => $bits) + $config); + openssl_pkey_export($rsa, $privatekey, null, $config); + $publickey = openssl_pkey_get_details($rsa); + $publickey = $publickey['key']; + + $privatekey = call_user_func_array(array($this, '_convertPrivateKey'), array_values($this->_parseKey($privatekey, CRYPT_RSA_PRIVATE_FORMAT_PKCS1))); + $publickey = call_user_func_array(array($this, '_convertPublicKey'), array_values($this->_parseKey($publickey, CRYPT_RSA_PUBLIC_FORMAT_PKCS1))); + + // clear the buffer of error strings stemming from a minimalistic openssl.cnf + while (openssl_error_string() !== false) { + } + + return array( + 'privatekey' => $privatekey, + 'publickey' => $publickey, + 'partialkey' => false + ); + } + + static $e; + if (!isset($e)) { + $e = new MathBigInteger(CRYPT_RSA_EXPONENT); + } + + extract($this->_generateMinMax($bits)); + $absoluteMin = $min; + $temp = $bits >> 1; // divide by two to see how many bits P and Q would be + if ($temp > CRYPT_RSA_SMALLEST_PRIME) { + $num_primes = floor($bits / CRYPT_RSA_SMALLEST_PRIME); + $temp = CRYPT_RSA_SMALLEST_PRIME; + } else { + $num_primes = 2; + } + extract($this->_generateMinMax($temp + $bits % $temp)); + $finalMax = $max; + extract($this->_generateMinMax($temp)); + + $generator = new MathBigInteger(); + + $n = $this->one->copy(); + if (!empty($partial)) { + extract(unserialize($partial)); + } else { + $exponents = $coefficients = $primes = array(); + $lcm = array( + 'top' => $this->one->copy(), + 'bottom' => false + ); + } + + $start = time(); + $i0 = count($primes) + 1; + + do { + for ($i = $i0; $i <= $num_primes; $i++) { + if ($timeout !== false) { + $timeout-= time() - $start; + $start = time(); + if ($timeout <= 0) { + return array( + 'privatekey' => '', + 'publickey' => '', + 'partialkey' => serialize(array( + 'primes' => $primes, + 'coefficients' => $coefficients, + 'lcm' => $lcm, + 'exponents' => $exponents + )) + ); + } + } + + if ($i == $num_primes) { + list($min, $temp) = $absoluteMin->divide($n); + if (!$temp->equals($this->zero)) { + $min = $min->add($this->one); // ie. ceil() + } + $primes[$i] = $generator->randomPrime($min, $finalMax, $timeout); + } else { + $primes[$i] = $generator->randomPrime($min, $max, $timeout); + } + + if ($primes[$i] === false) { // if we've reached the timeout + if (count($primes) > 1) { + $partialkey = ''; + } else { + array_pop($primes); + $partialkey = serialize(array( + 'primes' => $primes, + 'coefficients' => $coefficients, + 'lcm' => $lcm, + 'exponents' => $exponents + )); + } + + return array( + 'privatekey' => '', + 'publickey' => '', + 'partialkey' => $partialkey + ); + } + + // 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($this->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); + + $exponents[$i] = $e->modInverse($temp); + } + + list($temp) = $lcm['top']->divide($lcm['bottom']); + $gcd = $temp->gcd($e); + $i0 = 1; + } while (!$gcd->equals($this->one)); + + $d = $e->modInverse($temp); + + $coefficients[2] = $primes[2]->modInverse($primes[1]); + + // 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 + // } + + return array( + 'privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), + 'publickey' => $this->_convertPublicKey($n, $e), + 'partialkey' => false + ); + } + + /** + * Convert a private key to the appropriate format. + * + * @access private + * @see setPrivateKeyFormat() + * @param String $RSAPrivateKey + * @return String + */ + function _convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients) + { + $signed = $this->privateKeyFormat != CRYPT_RSA_PRIVATE_FORMAT_XML; + $num_primes = count($primes); + $raw = array( + 'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi + 'modulus' => $n->toBytes($signed), + 'publicExponent' => $e->toBytes($signed), + 'privateExponent' => $d->toBytes($signed), + 'prime1' => $primes[1]->toBytes($signed), + 'prime2' => $primes[2]->toBytes($signed), + 'exponent1' => $exponents[1]->toBytes($signed), + 'exponent2' => $exponents[2]->toBytes($signed), + 'coefficient' => $coefficients[2]->toBytes($signed) + ); + + // if the format in question does not support multi-prime rsa and multi-prime rsa was used, + // call _convertPublicKey() instead. + switch ($this->privateKeyFormat) { + case CRYPT_RSA_PRIVATE_FORMAT_XML: + if ($num_primes != 2) { + return false; + } + return "\r\n" . + ' ' . base64_encode($raw['modulus']) . "\r\n" . + ' ' . base64_encode($raw['publicExponent']) . "\r\n" . + '

' . base64_encode($raw['prime1']) . "

\r\n" . + ' ' . base64_encode($raw['prime2']) . "\r\n" . + ' ' . base64_encode($raw['exponent1']) . "\r\n" . + ' ' . base64_encode($raw['exponent2']) . "\r\n" . + ' ' . base64_encode($raw['coefficient']) . "\r\n" . + ' ' . base64_encode($raw['privateExponent']) . "\r\n" . + ''; + break; + case CRYPT_RSA_PRIVATE_FORMAT_PUTTY: + if ($num_primes != 2) { + return false; + } + $key = "PuTTY-User-Key-File-2: ssh-rsa\r\nEncryption: "; + $encryption = (!empty($this->password) || is_string($this->password)) ? 'aes256-cbc' : 'none'; + $key.= $encryption; + $key.= "\r\nComment: " . $this->comment . "\r\n"; + $public = pack( + 'Na*Na*Na*', + strlen('ssh-rsa'), + 'ssh-rsa', + strlen($raw['publicExponent']), + $raw['publicExponent'], + strlen($raw['modulus']), + $raw['modulus'] + ); + $source = pack( + 'Na*Na*Na*Na*', + strlen('ssh-rsa'), + 'ssh-rsa', + strlen($encryption), + $encryption, + strlen($this->comment), + $this->comment, + strlen($public), + $public + ); + $public = base64_encode($public); + $key.= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n"; + $key.= chunk_split($public, 64); + $private = pack( + 'Na*Na*Na*Na*', + strlen($raw['privateExponent']), + $raw['privateExponent'], + strlen($raw['prime1']), + $raw['prime1'], + strlen($raw['prime2']), + $raw['prime2'], + strlen($raw['coefficient']), + $raw['coefficient'] + ); + if (empty($this->password) && !is_string($this->password)) { + $source.= pack('Na*', strlen($private), $private); + $hashkey = 'putty-private-key-file-mac-key'; + } else { + $private.= crypt_random_string(16 - (strlen($private) & 15)); + $source.= pack('Na*', strlen($private), $private); + if (!class_exists('Crypt_AES')) { + include_once 'Crypt/AES.php'; + } + $sequence = 0; + $symkey = ''; + while (strlen($symkey) < 32) { + $temp = pack('Na*', $sequence++, $this->password); + $symkey.= pack('H*', sha1($temp)); + } + $symkey = substr($symkey, 0, 32); + $crypto = new Crypt_AES(); + + $crypto->setKey($symkey); + $crypto->disablePadding(); + $private = $crypto->encrypt($private); + $hashkey = 'putty-private-key-file-mac-key' . $this->password; + } + + $private = base64_encode($private); + $key.= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n"; + $key.= chunk_split($private, 64); + if (!class_exists('Crypt_Hash')) { + include_once 'Crypt/Hash.php'; + } + $hash = new Crypt_Hash('sha1'); + $hash->setKey(pack('H*', sha1($hashkey))); + $key.= 'Private-MAC: ' . bin2hex($hash->hash($source)) . "\r\n"; + + return $key; + default: // eg. CRYPT_RSA_PRIVATE_FORMAT_PKCS1 + $components = array(); + foreach ($raw as $name => $value) { + $components[$name] = pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($value)), $value); + } + + $RSAPrivateKey = implode('', $components); + + if ($num_primes > 2) { + $OtherPrimeInfos = ''; + for ($i = 3; $i <= $num_primes; $i++) { + // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo + // + // OtherPrimeInfo ::= SEQUENCE { + // prime INTEGER, -- ri + // exponent INTEGER, -- di + // coefficient INTEGER -- ti + // } + $OtherPrimeInfo = pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true)); + $OtherPrimeInfo.= pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true)); + $OtherPrimeInfo.= pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true)); + $OtherPrimeInfos.= pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo); + } + $RSAPrivateKey.= pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos); + } + + $RSAPrivateKey = pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); + + if ($this->privateKeyFormat == CRYPT_RSA_PRIVATE_FORMAT_PKCS8) { + $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA + $RSAPrivateKey = pack( + 'Ca*a*Ca*a*', + CRYPT_RSA_ASN1_INTEGER, + "\01\00", + $rsaOID, + 4, + $this->_encodeLength(strlen($RSAPrivateKey)), + $RSAPrivateKey + ); + $RSAPrivateKey = pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); + if (!empty($this->password) || is_string($this->password)) { + $salt = crypt_random_string(8); + $iterationCount = 2048; + + if (!class_exists('Crypt_DES')) { + include_once 'Crypt/DES.php'; + } + $crypto = new Crypt_DES(); + $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount); + $RSAPrivateKey = $crypto->encrypt($RSAPrivateKey); + + $parameters = pack( + 'Ca*a*Ca*N', + CRYPT_RSA_ASN1_OCTETSTRING, + $this->_encodeLength(strlen($salt)), + $salt, + CRYPT_RSA_ASN1_INTEGER, + $this->_encodeLength(4), + $iterationCount + ); + $pbeWithMD5AndDES_CBC = "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03"; + + $encryptionAlgorithm = pack( + 'Ca*a*Ca*a*', + CRYPT_RSA_ASN1_OBJECT, + $this->_encodeLength(strlen($pbeWithMD5AndDES_CBC)), + $pbeWithMD5AndDES_CBC, + CRYPT_RSA_ASN1_SEQUENCE, + $this->_encodeLength(strlen($parameters)), + $parameters + ); + + $RSAPrivateKey = pack( + 'Ca*a*Ca*a*', + CRYPT_RSA_ASN1_SEQUENCE, + $this->_encodeLength(strlen($encryptionAlgorithm)), + $encryptionAlgorithm, + CRYPT_RSA_ASN1_OCTETSTRING, + $this->_encodeLength(strlen($RSAPrivateKey)), + $RSAPrivateKey + ); + + $RSAPrivateKey = pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); + + $RSAPrivateKey = "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" . + chunk_split(base64_encode($RSAPrivateKey), 64) . + '-----END ENCRYPTED PRIVATE KEY-----'; + } else { + $RSAPrivateKey = "-----BEGIN PRIVATE KEY-----\r\n" . + chunk_split(base64_encode($RSAPrivateKey), 64) . + '-----END PRIVATE KEY-----'; + } + return $RSAPrivateKey; + } + + if (!empty($this->password) || is_string($this->password)) { + $iv = crypt_random_string(8); + $symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key + $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8); + if (!class_exists('Crypt_TripleDES')) { + include_once 'Crypt/TripleDES.php'; + } + $des = new Crypt_TripleDES(); + $des->setKey($symkey); + $des->setIV($iv); + $iv = strtoupper(bin2hex($iv)); + $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . + "Proc-Type: 4,ENCRYPTED\r\n" . + "DEK-Info: DES-EDE3-CBC,$iv\r\n" . + "\r\n" . + chunk_split(base64_encode($des->encrypt($RSAPrivateKey)), 64) . + '-----END RSA PRIVATE KEY-----'; + } else { + $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . + chunk_split(base64_encode($RSAPrivateKey), 64) . + '-----END RSA PRIVATE KEY-----'; + } + + return $RSAPrivateKey; + } + } + + /** + * Convert a public key to the appropriate format + * + * @access private + * @see setPublicKeyFormat() + * @param String $RSAPrivateKey + * @return String + */ + function _convertPublicKey($n, $e) + { + $signed = $this->publicKeyFormat != CRYPT_RSA_PUBLIC_FORMAT_XML; + + $modulus = $n->toBytes($signed); + $publicExponent = $e->toBytes($signed); + + switch ($this->publicKeyFormat) { + case CRYPT_RSA_PUBLIC_FORMAT_RAW: + return array('e' => $e->copy(), 'n' => $n->copy()); + case CRYPT_RSA_PUBLIC_FORMAT_XML: + return "\r\n" . + ' ' . base64_encode($modulus) . "\r\n" . + ' ' . base64_encode($publicExponent) . "\r\n" . + ''; + break; + case CRYPT_RSA_PUBLIC_FORMAT_OPENSSH: + // from : + // string "ssh-rsa" + // mpint e + // mpint n + $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus); + $RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $this->comment; + + return $RSAPublicKey; + default: // eg. CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW or CRYPT_RSA_PUBLIC_FORMAT_PKCS1 + // from : + // RSAPublicKey ::= SEQUENCE { + // modulus INTEGER, -- n + // publicExponent INTEGER -- e + // } + $components = array( + 'modulus' => pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($modulus)), $modulus), + 'publicExponent' => pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($publicExponent)), $publicExponent) + ); + + $RSAPublicKey = pack( + 'Ca*a*a*', + CRYPT_RSA_ASN1_SEQUENCE, + $this->_encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), + $components['modulus'], + $components['publicExponent'] + ); + + if ($this->publicKeyFormat == CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW) { + $RSAPublicKey = "-----BEGIN RSA PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($RSAPublicKey), 64) . + '-----END RSA PUBLIC KEY-----'; + } else { + // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. + $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA + $RSAPublicKey = chr(0) . $RSAPublicKey; + $RSAPublicKey = chr(3) . $this->_encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey; + + $RSAPublicKey = pack( + 'Ca*a*', + CRYPT_RSA_ASN1_SEQUENCE, + $this->_encodeLength(strlen($rsaOID . $RSAPublicKey)), + $rsaOID . $RSAPublicKey + ); + + $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($RSAPublicKey), 64) . + '-----END PUBLIC KEY-----'; + } + + return $RSAPublicKey; + } + } + + /** + * Break a public or private key down into its constituant components + * + * @access private + * @see _convertPublicKey() + * @see _convertPrivateKey() + * @param String $key + * @param Integer $type + * @return Array + */ + function _parseKey($key, $type) + { + if ($type != CRYPT_RSA_PUBLIC_FORMAT_RAW && !is_string($key)) { + return false; + } + + switch ($type) { + case CRYPT_RSA_PUBLIC_FORMAT_RAW: + if (!is_array($key)) { + return false; + } + $components = array(); + switch (true) { + case isset($key['e']): + $components['publicExponent'] = $key['e']->copy(); + break; + case isset($key['exponent']): + $components['publicExponent'] = $key['exponent']->copy(); + break; + case isset($key['publicExponent']): + $components['publicExponent'] = $key['publicExponent']->copy(); + break; + case isset($key[0]): + $components['publicExponent'] = $key[0]->copy(); + } + switch (true) { + case isset($key['n']): + $components['modulus'] = $key['n']->copy(); + break; + case isset($key['modulo']): + $components['modulus'] = $key['modulo']->copy(); + break; + case isset($key['modulus']): + $components['modulus'] = $key['modulus']->copy(); + break; + case isset($key[1]): + $components['modulus'] = $key[1]->copy(); + } + return isset($components['modulus']) && isset($components['publicExponent']) ? $components : false; + case CRYPT_RSA_PRIVATE_FORMAT_PKCS1: + case CRYPT_RSA_PRIVATE_FORMAT_PKCS8: + case CRYPT_RSA_PUBLIC_FORMAT_PKCS1: + /* 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 = pack('H*', trim($matches[2])); + $symkey = pack('H*', md5($this->password . substr($iv, 0, 8))); // symkey is short for symmetric key + $symkey.= pack('H*', md5($symkey . $this->password . substr($iv, 0, 8))); + // remove the Proc-Type / DEK-Info sections as they're no longer needed + $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key); + $ciphertext = $this->_extractBER($key); + if ($ciphertext === false) { + $ciphertext = $key; + } + switch ($matches[1]) { + case 'AES-256-CBC': + if (!class_exists('Crypt_AES')) { + include_once 'Crypt/AES.php'; + } + $crypto = new Crypt_AES(); + break; + case 'AES-128-CBC': + if (!class_exists('Crypt_AES')) { + include_once 'Crypt/AES.php'; + } + $symkey = substr($symkey, 0, 16); + $crypto = new Crypt_AES(); + break; + case 'DES-EDE3-CFB': + if (!class_exists('Crypt_TripleDES')) { + include_once 'Crypt/TripleDES.php'; + } + $crypto = new Crypt_TripleDES(CRYPT_DES_MODE_CFB); + break; + case 'DES-EDE3-CBC': + if (!class_exists('Crypt_TripleDES')) { + include_once 'Crypt/TripleDES.php'; + } + $symkey = substr($symkey, 0, 24); + $crypto = new Crypt_TripleDES(); + break; + case 'DES-CBC': + if (!class_exists('Crypt_DES')) { + include_once 'Crypt/DES.php'; + } + $crypto = new Crypt_DES(); + break; + default: + return false; + } + $crypto->setKey($symkey); + $crypto->setIV($iv); + $decoded = $crypto->decrypt($ciphertext); + } else { + $decoded = $this->_extractBER($key); + } + + if ($decoded !== false) { + $key = $decoded; + } + + $components = array(); + + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + if ($this->_decodeLength($key) != strlen($key)) { + return false; + } + + $tag = ord($this->_string_shift($key)); + /* intended for keys for which OpenSSL's asn1parse returns the following: + + 0:d=0 hl=4 l= 631 cons: SEQUENCE + 4:d=1 hl=2 l= 1 prim: INTEGER :00 + 7:d=1 hl=2 l= 13 cons: SEQUENCE + 9:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption + 20:d=2 hl=2 l= 0 prim: NULL + 22:d=1 hl=4 l= 609 prim: OCTET STRING + + ie. PKCS8 keys*/ + + if ($tag == CRYPT_RSA_ASN1_INTEGER && substr($key, 0, 3) == "\x01\x00\x30") { + $this->_string_shift($key, 3); + $tag = CRYPT_RSA_ASN1_SEQUENCE; + } + + if ($tag == CRYPT_RSA_ASN1_SEQUENCE) { + $temp = $this->_string_shift($key, $this->_decodeLength($key)); + if (ord($this->_string_shift($temp)) != CRYPT_RSA_ASN1_OBJECT) { + return false; + } + $length = $this->_decodeLength($temp); + switch ($this->_string_shift($temp, $length)) { + case "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01": // rsaEncryption + break; + case "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03": // pbeWithMD5AndDES-CBC + /* + PBEParameter ::= SEQUENCE { + salt OCTET STRING (SIZE(8)), + iterationCount INTEGER } + */ + if (ord($this->_string_shift($temp)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + if ($this->_decodeLength($temp) != strlen($temp)) { + return false; + } + $this->_string_shift($temp); // assume it's an octet string + $salt = $this->_string_shift($temp, $this->_decodeLength($temp)); + if (ord($this->_string_shift($temp)) != CRYPT_RSA_ASN1_INTEGER) { + return false; + } + $this->_decodeLength($temp); + list(, $iterationCount) = unpack('N', str_pad($temp, 4, chr(0), STR_PAD_LEFT)); + $this->_string_shift($key); // assume it's an octet string + $length = $this->_decodeLength($key); + if (strlen($key) != $length) { + return false; + } + + if (!class_exists('Crypt_DES')) { + include_once 'Crypt/DES.php'; + } + $crypto = new Crypt_DES(); + $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount); + $key = $crypto->decrypt($key); + if ($key === false) { + return false; + } + return $this->_parseKey($key, CRYPT_RSA_PRIVATE_FORMAT_PKCS1); + default: + return false; + } + /* intended for keys for which OpenSSL's asn1parse returns the following: + + 0:d=0 hl=4 l= 290 cons: SEQUENCE + 4:d=1 hl=2 l= 13 cons: SEQUENCE + 6:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption + 17:d=2 hl=2 l= 0 prim: NULL + 19:d=1 hl=4 l= 271 prim: BIT STRING */ + $tag = ord($this->_string_shift($key)); // skip over the BIT STRING / OCTET STRING tag + $this->_decodeLength($key); // skip over the BIT STRING / OCTET STRING length + // "The initial octet shall encode, as an unsigned binary integer wtih 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 (section 8.6.2.2) + if ($tag == CRYPT_RSA_ASN1_BITSTRING) { + $this->_string_shift($key); + } + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + if ($this->_decodeLength($key) != strlen($key)) { + return false; + } + $tag = ord($this->_string_shift($key)); + } + if ($tag != CRYPT_RSA_ASN1_INTEGER) { + return false; + } + + $length = $this->_decodeLength($key); + $temp = $this->_string_shift($key, $length); + if (strlen($temp) != 1 || ord($temp) > 2) { + $components['modulus'] = new MathBigInteger($temp, 256); + $this->_string_shift($key); // skip over CRYPT_RSA_ASN1_INTEGER + $length = $this->_decodeLength($key); + $components[$type == CRYPT_RSA_PUBLIC_FORMAT_PKCS1 ? 'publicExponent' : 'privateExponent'] = new MathBigInteger($this->_string_shift($key, $length), 256); + + return $components; + } + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_INTEGER) { + return false; + } + $length = $this->_decodeLength($key); + $components['modulus'] = new MathBigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['publicExponent'] = new MathBigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['privateExponent'] = new MathBigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['primes'] = array(1 => new MathBigInteger($this->_string_shift($key, $length), 256)); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['primes'][] = new MathBigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['exponents'] = array(1 => new MathBigInteger($this->_string_shift($key, $length), 256)); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['exponents'][] = new MathBigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['coefficients'] = array(2 => new MathBigInteger($this->_string_shift($key, $length), 256)); + + if (!empty($key)) { + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + $this->_decodeLength($key); + while (!empty($key)) { + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + $this->_decodeLength($key); + $key = substr($key, 1); + $length = $this->_decodeLength($key); + $components['primes'][] = new MathBigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['exponents'][] = new MathBigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['coefficients'][] = new MathBigInteger($this->_string_shift($key, $length), 256); + } + } + + return $components; + case CRYPT_RSA_PUBLIC_FORMAT_OPENSSH: + $parts = explode(' ', $key, 3); + + $key = isset($parts[1]) ? base64_decode($parts[1]) : false; + if ($key === false) { + return false; + } + + $comment = isset($parts[2]) ? $parts[2] : false; + + $cleanup = substr($key, 0, 11) == "\0\0\0\7ssh-rsa"; + + if (strlen($key) <= 4) { + return false; + } + extract(unpack('Nlength', $this->_string_shift($key, 4))); + $publicExponent = new MathBigInteger($this->_string_shift($key, $length), -256); + if (strlen($key) <= 4) { + return false; + } + extract(unpack('Nlength', $this->_string_shift($key, 4))); + $modulus = new MathBigInteger($this->_string_shift($key, $length), -256); + + if ($cleanup && strlen($key)) { + if (strlen($key) <= 4) { + return false; + } + extract(unpack('Nlength', $this->_string_shift($key, 4))); + $realModulus = new MathBigInteger($this->_string_shift($key, $length), -256); + return strlen($key) ? false : array( + 'modulus' => $realModulus, + 'publicExponent' => $modulus, + 'comment' => $comment + ); + } else { + return strlen($key) ? false : array( + 'modulus' => $modulus, + 'publicExponent' => $publicExponent, + 'comment' => $comment + ); + } + // http://www.w3.org/TR/xmldsig-core/#sec-RSAKeyValue + // http://en.wikipedia.org/wiki/XML_Signature + case CRYPT_RSA_PRIVATE_FORMAT_XML: + case CRYPT_RSA_PUBLIC_FORMAT_XML: + $this->components = array(); + + $xml = xml_parser_create('UTF-8'); + xml_set_object($xml, $this); + xml_set_element_handler($xml, '_start_element_handler', '_stop_element_handler'); + xml_set_character_data_handler($xml, '_data_handler'); + // add to account for "dangling" tags like ... that are sometimes added + if (!xml_parse($xml, '' . $key . '')) { + return false; + } + + return isset($this->components['modulus']) && isset($this->components['publicExponent']) ? $this->components : false; + // from PuTTY's SSHPUBK.C + case CRYPT_RSA_PRIVATE_FORMAT_PUTTY: + $components = array(); + $key = preg_split('#\r\n|\r|\n#', $key); + $type = trim(preg_replace('#PuTTY-User-Key-File-2: (.+)#', '$1', $key[0])); + if ($type != 'ssh-rsa') { + return false; + } + $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1])); + $comment = trim(preg_replace('#Comment: (.+)#', '$1', $key[2])); + + $publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3])); + $public = base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength)))); + $public = substr($public, 11); + extract(unpack('Nlength', $this->_string_shift($public, 4))); + $components['publicExponent'] = new MathBigInteger($this->_string_shift($public, $length), -256); + extract(unpack('Nlength', $this->_string_shift($public, 4))); + $components['modulus'] = new MathBigInteger($this->_string_shift($public, $length), -256); + + $privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$publicLength + 4])); + $private = base64_decode(implode('', array_map('trim', array_slice($key, $publicLength + 5, $privateLength)))); + + switch ($encryption) { + case 'aes256-cbc': + if (!class_exists('Crypt_AES')) { + include_once 'Crypt/AES.php'; + } + $symkey = ''; + $sequence = 0; + while (strlen($symkey) < 32) { + $temp = pack('Na*', $sequence++, $this->password); + $symkey.= pack('H*', sha1($temp)); + } + $symkey = substr($symkey, 0, 32); + $crypto = new Crypt_AES(); + } + + if ($encryption != 'none') { + $crypto->setKey($symkey); + $crypto->disablePadding(); + $private = $crypto->decrypt($private); + if ($private === false) { + return false; + } + } + + extract(unpack('Nlength', $this->_string_shift($private, 4))); + if (strlen($private) < $length) { + return false; + } + $components['privateExponent'] = new MathBigInteger($this->_string_shift($private, $length), -256); + extract(unpack('Nlength', $this->_string_shift($private, 4))); + if (strlen($private) < $length) { + return false; + } + $components['primes'] = array(1 => new MathBigInteger($this->_string_shift($private, $length), -256)); + extract(unpack('Nlength', $this->_string_shift($private, 4))); + if (strlen($private) < $length) { + return false; + } + $components['primes'][] = new MathBigInteger($this->_string_shift($private, $length), -256); + + $temp = $components['primes'][1]->subtract($this->one); + $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp)); + $temp = $components['primes'][2]->subtract($this->one); + $components['exponents'][] = $components['publicExponent']->modInverse($temp); + + extract(unpack('Nlength', $this->_string_shift($private, 4))); + if (strlen($private) < $length) { + return false; + } + $components['coefficients'] = array(2 => new MathBigInteger($this->_string_shift($private, $length), -256)); + + return $components; + } + } + + /** + * Returns the key size + * + * More specifically, this returns the size of the modulo in bits. + * + * @access public + * @return Integer + */ + function getSize() + { + return !isset($this->modulus) ? 0 : strlen($this->modulus->toBits()); + } + + /** + * Start Element Handler + * + * Called by xml_set_element_handler() + * + * @access private + * @param Resource $parser + * @param String $name + * @param Array $attribs + */ + function _start_element_handler($parser, $name, $attribs) + { + //$name = strtoupper($name); + switch ($name) { + case 'MODULUS': + $this->current = &$this->components['modulus']; + break; + case 'EXPONENT': + $this->current = &$this->components['publicExponent']; + break; + case 'P': + $this->current = &$this->components['primes'][1]; + break; + case 'Q': + $this->current = &$this->components['primes'][2]; + break; + case 'DP': + $this->current = &$this->components['exponents'][1]; + break; + case 'DQ': + $this->current = &$this->components['exponents'][2]; + break; + case 'INVERSEQ': + $this->current = &$this->components['coefficients'][2]; + break; + case 'D': + $this->current = &$this->components['privateExponent']; + } + $this->current = ''; + } + + /** + * Stop Element Handler + * + * Called by xml_set_element_handler() + * + * @access private + * @param Resource $parser + * @param String $name + */ + function _stop_element_handler($parser, $name) + { + if (isset($this->current)) { + $this->current = new MathBigInteger(base64_decode($this->current), 256); + unset($this->current); + } + } + + /** + * Data Handler + * + * Called by xml_set_character_data_handler() + * + * @access private + * @param Resource $parser + * @param String $data + */ + function _data_handler($parser, $data) + { + if (!isset($this->current) || is_object($this->current)) { + return; + } + $this->current.= trim($data); + } + + /** + * Loads a public or private key + * + * Returns true on success and false on failure (ie. an incorrect password was provided or the key was malformed) + * + * @access public + * @param String $key + * @param Integer $type optional + */ + function loadKey($key, $type = false) + { + if (is_object($key) && strtolower(get_class($key)) == 'crypt_rsa') { + $this->privateKeyFormat = $key->privateKeyFormat; + $this->publicKeyFormat = $key->publicKeyFormat; + $this->k = $key->k; + $this->hLen = $key->hLen; + $this->sLen = $key->sLen; + $this->mgfHLen = $key->mgfHLen; + $this->encryptionMode = $key->encryptionMode; + $this->signatureMode = $key->signatureMode; + $this->password = $key->password; + $this->configFile = $key->configFile; + $this->comment = $key->comment; + + if (is_object($key->hash)) { + $this->hash = new Crypt_Hash($key->hash->getHash()); + } + if (is_object($key->mgfHash)) { + $this->mgfHash = new Crypt_Hash($key->mgfHash->getHash()); + } + + if (is_object($key->modulus)) { + $this->modulus = $key->modulus->copy(); + } + if (is_object($key->exponent)) { + $this->exponent = $key->exponent->copy(); + } + if (is_object($key->publicExponent)) { + $this->publicExponent = $key->publicExponent->copy(); + } + + $this->primes = array(); + $this->exponents = array(); + $this->coefficients = array(); + + foreach ($this->primes as $prime) { + $this->primes[] = $prime->copy(); + } + foreach ($this->exponents as $exponent) { + $this->exponents[] = $exponent->copy(); + } + foreach ($this->coefficients as $coefficient) { + $this->coefficients[] = $coefficient->copy(); + } + + return true; + } + + if ($type === false) { + $types = array( + CRYPT_RSA_PUBLIC_FORMAT_RAW, + CRYPT_RSA_PRIVATE_FORMAT_PKCS1, + CRYPT_RSA_PRIVATE_FORMAT_XML, + CRYPT_RSA_PRIVATE_FORMAT_PUTTY, + CRYPT_RSA_PUBLIC_FORMAT_OPENSSH + ); + foreach ($types as $type) { + $components = $this->_parseKey($key, $type); + if ($components !== false) { + break; + } + } + + } else { + $components = $this->_parseKey($key, $type); + } + + if ($components === false) { + return false; + } + + if (isset($components['comment']) && $components['comment'] !== false) { + $this->comment = $components['comment']; + } + $this->modulus = $components['modulus']; + $this->k = strlen($this->modulus->toBytes()); + $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent']; + if (isset($components['primes'])) { + $this->primes = $components['primes']; + $this->exponents = $components['exponents']; + $this->coefficients = $components['coefficients']; + $this->publicExponent = $components['publicExponent']; + } else { + $this->primes = array(); + $this->exponents = array(); + $this->coefficients = array(); + $this->publicExponent = false; + } + + switch ($type) { + case CRYPT_RSA_PUBLIC_FORMAT_OPENSSH: + case CRYPT_RSA_PUBLIC_FORMAT_RAW: + $this->setPublicKey(); + break; + case CRYPT_RSA_PRIVATE_FORMAT_PKCS1: + switch (true) { + case strpos($key, '-BEGIN PUBLIC KEY-') !== false: + case strpos($key, '-BEGIN RSA PUBLIC KEY-') !== false: + $this->setPublicKey(); + } + } + + return true; + } + + /** + * 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 createKey() + * @see loadKey() + * @access public + * @param String $password + */ + function setPassword($password = false) + { + $this->password = $password; + } + + /** + * Defines the public key + * + * Some private key formats define the public exponent and some don't. Those that don't define it are problematic when + * used in certain contexts. For example, in SSH-2, RSA authentication works by sending the public key along with a + * message signed by the private key to the server. The SSH-2 server looks the public key up in an index of public keys + * and if it's present then proceeds to verify the signature. Problem is, if your private key doesn't include the public + * exponent this won't work unless you manually add the public exponent. phpseclib tries to guess if the key being used + * is the public key but in the event that it guesses incorrectly you might still want to explicitly set the key as being + * public. + * + * Do note that when a new key is loaded the index will be cleared. + * + * Returns true on success, false on failure + * + * @see getPublicKey() + * @access public + * @param String $key optional + * @param Integer $type optional + * @return Boolean + */ + function setPublicKey($key = false, $type = false) + { + // if a public key has already been loaded return false + if (!empty($this->publicExponent)) { + return false; + } + + if ($key === false && !empty($this->modulus)) { + $this->publicExponent = $this->exponent; + return true; + } + + if ($type === false) { + $types = array( + CRYPT_RSA_PUBLIC_FORMAT_RAW, + CRYPT_RSA_PUBLIC_FORMAT_PKCS1, + CRYPT_RSA_PUBLIC_FORMAT_XML, + CRYPT_RSA_PUBLIC_FORMAT_OPENSSH + ); + foreach ($types as $type) { + $components = $this->_parseKey($key, $type); + if ($components !== false) { + break; + } + } + } else { + $components = $this->_parseKey($key, $type); + } + + if ($components === false) { + return false; + } + + if (empty($this->modulus) || !$this->modulus->equals($components['modulus'])) { + $this->modulus = $components['modulus']; + $this->exponent = $this->publicExponent = $components['publicExponent']; + return true; + } + + $this->publicExponent = $components['publicExponent']; + + return true; + } + + /** + * Defines the private key + * + * If phpseclib guessed a private key was a public key and loaded it as such it might be desirable to force + * phpseclib to treat the key as a private key. This function will do that. + * + * Do note that when a new key is loaded the index will be cleared. + * + * Returns true on success, false on failure + * + * @see getPublicKey() + * @access public + * @param String $key optional + * @param Integer $type optional + * @return Boolean + */ + function setPrivateKey($key = false, $type = false) + { + if ($key === false && !empty($this->publicExponent)) { + unset($this->publicExponent); + return true; + } + + $rsa = new Crypt_RSA(); + if (!$rsa->loadKey($key, $type)) { + return false; + } + unset($rsa->publicExponent); + + // don't overwrite the old key if the new key is invalid + $this->loadKey($rsa); + return true; + } + + /** + * 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. + * + * @see getPublicKey() + * @access public + * @param String $key + * @param Integer $type optional + */ + function getPublicKey($type = CRYPT_RSA_PUBLIC_FORMAT_PKCS8) + { + if (empty($this->modulus) || empty($this->publicExponent)) { + return false; + } + + $oldFormat = $this->publicKeyFormat; + $this->publicKeyFormat = $type; + $temp = $this->_convertPublicKey($this->modulus, $this->publicExponent); + $this->publicKeyFormat = $oldFormat; + return $temp; + } + + /** + * 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) + * + * @access public + * @param String $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned + * for invalid values. + */ + public function getPublicKeyFingerprint($algorithm = 'md5') + { + if (empty($this->modulus) || empty($this->publicExponent)) { + return false; + } + + $modulus = $this->modulus->toBytes(true); + $publicExponent = $this->publicExponent->toBytes(true); + + $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus); + + switch ($algorithm) { + case 'sha256': + $hash = new Crypt_Hash('sha256'); + $base = base64_encode($hash->hash($RSAPublicKey)); + return substr($base, 0, strlen($base) - 1); + case 'md5': + return substr(chunk_split(md5($RSAPublicKey), 2, ':'), 0, -1); + default: + return false; + } + + } + + /** + * Returns the private key + * + * The private key is only returned if the currently loaded key contains the constituent prime numbers. + * + * @see getPublicKey() + * @access public + * @param String $key + * @param Integer $type optional + */ + function getPrivateKey($type = CRYPT_RSA_PUBLIC_FORMAT_PKCS1) + { + if (empty($this->primes)) { + return false; + } + + $oldFormat = $this->privateKeyFormat; + $this->privateKeyFormat = $type; + $temp = $this->_convertPrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients); + $this->privateKeyFormat = $oldFormat; + return $temp; + } + + /** + * Returns a minimalistic private key + * + * Returns the private key without the prime number constituants. Structurally identical to a public key that + * hasn't been set as the public key + * + * @see getPrivateKey() + * @access private + * @param String $key + * @param Integer $type optional + */ + function _getPrivatePublicKey($mode = CRYPT_RSA_PUBLIC_FORMAT_PKCS8) + { + if (empty($this->modulus) || empty($this->exponent)) { + return false; + } + + $oldFormat = $this->publicKeyFormat; + $this->publicKeyFormat = $mode; + $temp = $this->_convertPublicKey($this->modulus, $this->exponent); + $this->publicKeyFormat = $oldFormat; + return $temp; + } + + /** + * __toString() magic method + * + * @access public + */ + function __toString() + { + $key = $this->getPrivateKey($this->privateKeyFormat); + if ($key !== false) { + return $key; + } + $key = $this->_getPrivatePublicKey($this->publicKeyFormat); + return $key !== false ? $key : ''; + } + + /** + * __clone() magic method + * + * @access public + */ + function __clone() + { + $key = new Crypt_RSA(); + $key->loadKey($this); + return $key; + } + + /** + * Generates the smallest and largest numbers requiring $bits bits + * + * @access private + * @param Integer $bits + * @return Array + */ + function _generateMinMax($bits) + { + $bytes = $bits >> 3; + $min = str_repeat(chr(0), $bytes); + $max = str_repeat(chr(0xFF), $bytes); + $msb = $bits & 7; + if ($msb) { + $min = chr(1 << ($msb - 1)) . $min; + $max = chr((1 << $msb) - 1) . $max; + } else { + $min[0] = chr(0x80); + } + + return array( + 'min' => new MathBigInteger($min, 256), + 'max' => new MathBigInteger($max, 256) + ); + } + + /** + * 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. + * + * @access private + * @param String $string + * @return Integer + */ + function _decodeLength(&$string) + { + $length = ord($this->_string_shift($string)); + if ($length & 0x80) { // definite length, long form + $length&= 0x7F; + $temp = $this->_string_shift($string, $length); + list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)); + } + return $length; + } + + /** + * 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. + * + * @access private + * @param Integer $length + * @return String + */ + function _encodeLength($length) + { + if ($length <= 0x7F) { + return chr($length); + } + + $temp = ltrim(pack('N', $length), chr(0)); + return pack('Ca*', 0x80 | strlen($temp), $temp); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } + + /** + * Determines the private key format + * + * @see createKey() + * @access public + * @param Integer $format + */ + function setPrivateKeyFormat($format) + { + $this->privateKeyFormat = $format; + } + + /** + * Determines the public key format + * + * @see createKey() + * @access public + * @param Integer $format + */ + function setPublicKeyFormat($format) + { + $this->publicKeyFormat = $format; + } + + /** + * Determines which hashing function should be used + * + * Used with signature production / verification and (if the encryption mode is CRYPT_RSA_ENCRYPTION_OAEP) encryption and + * decryption. If $hash isn't supported, sha1 is used. + * + * @access public + * @param String $hash + */ + function setHash($hash) + { + // Crypt_Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. + switch ($hash) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha256': + case 'sha384': + case 'sha512': + $this->hash = new Crypt_Hash($hash); + $this->hashName = $hash; + break; + default: + $this->hash = new Crypt_Hash('sha1'); + $this->hashName = 'sha1'; + } + $this->hLen = $this->hash->getLength(); + } + + /** + * Determines which hashing function should be used for the mask generation function + * + * The mask generation function is used by CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_SIGNATURE_PSS and although it's + * best if Hash and MGFHash are set to the same thing this is not a requirement. + * + * @access public + * @param String $hash + */ + function setMGFHash($hash) + { + // Crypt_Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. + switch ($hash) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha256': + case 'sha384': + case 'sha512': + $this->mgfHash = new Crypt_Hash($hash); + break; + default: + $this->mgfHash = new Crypt_Hash('sha1'); + } + $this->mgfHLen = $this->mgfHash->getLength(); + } + + /** + * Determines the salt length + * + * 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. + * + * @access public + * @param Integer $format + */ + function setSaltLength($sLen) + { + $this->sLen = $sLen; + } + + /** + * Integer-to-Octet-String primitive + * + * See {@link http://tools.ietf.org/html/rfc3447#section-4.1 RFC3447#section-4.1}. + * + * @access private + * @param MathBigInteger $x + * @param Integer $xLen + * @return String + */ + function _i2osp($x, $xLen) + { + $x = $x->toBytes(); + if (strlen($x) > $xLen) { + user_error('Integer too large'); + return false; + } + 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}. + * + * @access private + * @param String $x + * @return MathBigInteger + */ + function _os2ip($x) + { + return new MathBigInteger($x, 256); + } + + /** + * Exponentiate with or without Chinese Remainder Theorem + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.2}. + * + * @access private + * @param MathBigInteger $x + * @return MathBigInteger + */ + function _exponentiate($x) + { + if (empty($this->primes) || empty($this->coefficients) || empty($this->exponents)) { + return $x->modPow($this->exponent, $this->modulus); + } + + $num_primes = count($this->primes); + + if (defined('CRYPT_RSA_DISABLE_BLINDING')) { + $m_i = array( + 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]; + } + } + + $one = new MathBigInteger(1); + + $r = $one->random($one, $smallest->subtract($one)); + + $m_i = array( + 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]) + * + * @access private + * @param MathBigInteger $x + * @param MathBigInteger $r + * @param Integer $i + * @return MathBigInteger + */ + function _blind($x, $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; + } + + /** + * Performs blinded RSA equality testing + * + * Protects against a particular type of timing attack described. + * + * See {@link http://codahale.com/a-lesson-in-timing-attacks/ A Lesson In Timing Attacks (or, Don't use MessageDigest.isEquals)} + * + * Thanks for the heads up singpolyma! + * + * @access private + * @param String $x + * @param String $y + * @return Boolean + */ + function _equals($x, $y) + { + if (strlen($x) != strlen($y)) { + return false; + } + + $result = 0; + for ($i = 0; $i < strlen($x); $i++) { + $result |= ord($x[$i]) ^ ord($y[$i]); + } + + return $result == 0; + } + + /** + * RSAEP + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}. + * + * @access private + * @param MathBigInteger $m + * @return MathBigInteger + */ + function _rsaep($m) + { + if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { + user_error('Message representative out of range'); + return false; + } + return $this->_exponentiate($m); + } + + /** + * RSADP + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}. + * + * @access private + * @param MathBigInteger $c + * @return MathBigInteger + */ + function _rsadp($c) + { + if ($c->compare($this->zero) < 0 || $c->compare($this->modulus) > 0) { + user_error('Ciphertext representative out of range'); + return false; + } + return $this->_exponentiate($c); + } + + /** + * RSASP1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}. + * + * @access private + * @param MathBigInteger $m + * @return MathBigInteger + */ + function _rsasp1($m) + { + if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { + user_error('Message representative out of range'); + return false; + } + return $this->_exponentiate($m); + } + + /** + * RSAVP1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}. + * + * @access private + * @param MathBigInteger $s + * @return MathBigInteger + */ + function _rsavp1($s) + { + if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) { + user_error('Signature representative out of range'); + return false; + } + return $this->_exponentiate($s); + } + + /** + * MGF1 + * + * See {@link http://tools.ietf.org/html/rfc3447#appendix-B.2.1 RFC3447#appendix-B.2.1}. + * + * @access private + * @param String $mgfSeed + * @param Integer $mgfLen + * @return String + */ + 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); + } + + /** + * 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}. + * + * @access private + * @param String $m + * @param String $l + * @return String + */ + function _rsaes_oaep_encrypt($m, $l = '') + { + $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) { + user_error('Message too long'); + return false; + } + + // EME-OAEP encoding + + $lHash = $this->hash->hash($l); + $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2); + $db = $lHash . $ps . chr(1) . $m; + $seed = crypt_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; + } + + /** + * 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]. + * + * As for $l... 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. + * + * @access private + * @param String $c + * @param String $l + * @return String + */ + function _rsaes_oaep_decrypt($c, $l = '') + { + // 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) { + user_error('Decryption error'); + return false; + } + + // RSA decryption + + $c = $this->_os2ip($c); + $m = $this->_rsadp($c); + if ($m === false) { + user_error('Decryption error'); + return false; + } + $em = $this->_i2osp($m, $this->k); + + // EME-OAEP decoding + + $lHash = $this->hash->hash($l); + $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); + if ($lHash != $lHash2) { + user_error('Decryption error'); + return false; + } + $m = ltrim($m, chr(0)); + if (ord($m[0]) != 1) { + user_error('Decryption error'); + return false; + } + + // Output the message M + + return substr($m, 1); + } + + /** + * Raw Encryption / Decryption + * + * Doesn't use padding and is not recommended. + * + * @access private + * @param String $m + * @return String + */ + function _raw_encrypt($m) + { + $temp = $this->_os2ip($m); + $temp = $this->_rsaep($temp); + return $this->_i2osp($temp, $this->k); + } + + /** + * RSAES-PKCS1-V1_5-ENCRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}. + * + * @access private + * @param String $m + * @return String + */ + function _rsaes_pkcs1_v1_5_encrypt($m) + { + $mLen = strlen($m); + + // Length checking + + if ($mLen > $this->k - 11) { + user_error('Message too long'); + return false; + } + + // EME-PKCS1-v1_5 encoding + + $psLen = $this->k - $mLen - 3; + $ps = ''; + while (strlen($ps) != $psLen) { + $temp = crypt_random_string($psLen - strlen($ps)); + $temp = str_replace("\x00", '', $temp); + $ps.= $temp; + } + $type = 2; + // see the comments of _rsaes_pkcs1_v1_5_decrypt() to understand why this is being done + if (defined('CRYPT_RSA_PKCS15_COMPAT') && (!isset($this->publicExponent) || $this->exponent !== $this->publicExponent)) { + $type = 1; + // "The padding string PS shall consist of k-3-||D|| octets. ... for block type 01, they shall have value FF" + $ps = str_repeat("\xFF", $psLen); + } + $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-PKCS1-V1_5-DECRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}. + * + * For compatibility purposes, this function departs slightly from the description given in RFC3447. + * The reason being that RFC2313#section-8.1 (PKCS#1 v1.5) states that ciphertext's encrypted by the + * private key should have the second byte set to either 0 or 1 and that ciphertext's encrypted by the + * public key should have the second byte set to 2. In RFC3447 (PKCS#1 v2.1), the second byte is supposed + * to be 2 regardless of which key is used. For compatibility purposes, we'll just check to make sure the + * second byte is 2 or less. If it is, we'll accept the decrypted string as valid. + * + * As a consequence of this, a private key encrypted ciphertext produced with Crypt_RSA may not decrypt + * with a strictly PKCS#1 v1.5 compliant RSA implementation. Public key encrypted ciphertext's should but + * not private key encrypted ciphertext's. + * + * @access private + * @param String $c + * @return String + */ + function _rsaes_pkcs1_v1_5_decrypt($c) + { + // Length checking + + if (strlen($c) != $this->k) { // or if k < 11 + user_error('Decryption error'); + return false; + } + + // RSA decryption + + $c = $this->_os2ip($c); + $m = $this->_rsadp($c); + + if ($m === false) { + user_error('Decryption error'); + return false; + } + $em = $this->_i2osp($m, $this->k); + + // EME-PKCS1-v1_5 decoding + + if (ord($em[0]) != 0 || ord($em[1]) > 2) { + user_error('Decryption error'); + return false; + } + + $ps = substr($em, 2, strpos($em, chr(0), 2) - 2); + $m = substr($em, strlen($ps) + 3); + + if (strlen($ps) < 8) { + user_error('Decryption error'); + return false; + } + + // Output M + + return $m; + } + + /** + * EMSA-PSS-ENCODE + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}. + * + * @access private + * @param String $m + * @param Integer $emBits + */ + 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 === false ? $this->hLen : $this->sLen; + + $mHash = $this->hash->hash($m); + if ($emLen < $this->hLen + $sLen + 2) { + user_error('Encoding error'); + return false; + } + + $salt = crypt_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); + $maskedDB = $db ^ $dbMask; + $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; + $em = $maskedDB . $h . chr(0xBC); + + return $em; + } + + /** + * EMSA-PSS-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}. + * + * @access private + * @param String $m + * @param String $em + * @param Integer $emBits + * @return String + */ + 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 + 1) >> 3; // ie. ceil($emBits / 8); + $sLen = $this->sLen === false ? $this->hLen : $this->sLen; + + $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 $this->_equals($h, $h2); + } + + /** + * RSASSA-PSS-SIGN + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}. + * + * @access private + * @param String $m + * @return String + */ + 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-PSS-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}. + * + * @access private + * @param String $m + * @param String $s + * @return String + */ + function _rsassa_pss_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + user_error('Invalid signature'); + return false; + } + + // RSA verification + + $modBits = 8 * $this->k; + + $s2 = $this->_os2ip($s); + $m2 = $this->_rsavp1($s2); + if ($m2 === false) { + user_error('Invalid signature'); + return false; + } + $em = $this->_i2osp($m2, $modBits >> 3); + if ($em === false) { + user_error('Invalid signature'); + return false; + } + + // EMSA-PSS verification + + return $this->_emsa_pss_verify($m, $em, $modBits - 1); + } + + /** + * EMSA-PKCS1-V1_5-ENCODE + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.2 RFC3447#section-9.2}. + * + * @access private + * @param String $m + * @param Integer $emLen + * @return String + */ + function _emsa_pkcs1_v1_5_encode($m, $emLen) + { + $h = $this->hash->hash($m); + if ($h === false) { + return false; + } + + // see http://tools.ietf.org/html/rfc3447#page-43 + switch ($this->hashName) { + case 'md2': + $t = pack('H*', '3020300c06082a864886f70d020205000410'); + break; + case 'md5': + $t = pack('H*', '3020300c06082a864886f70d020505000410'); + break; + case 'sha1': + $t = pack('H*', '3021300906052b0e03021a05000414'); + break; + case 'sha256': + $t = pack('H*', '3031300d060960864801650304020105000420'); + break; + case 'sha384': + $t = pack('H*', '3041300d060960864801650304020205000430'); + break; + case 'sha512': + $t = pack('H*', '3051300d060960864801650304020305000440'); + } + $t.= $h; + $tLen = strlen($t); + + if ($emLen < $tLen + 11) { + user_error('Intended encoded message length too short'); + return false; + } + + $ps = str_repeat(chr(0xFF), $emLen - $tLen - 3); + + $em = "\0\1$ps\0$t"; + + return $em; + } + + /** + * RSASSA-PKCS1-V1_5-SIGN + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}. + * + * @access private + * @param String $m + * @return String + */ + function _rsassa_pkcs1_v1_5_sign($m) + { + // EMSA-PKCS1-v1_5 encoding + + $em = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); + if ($em === false) { + user_error('RSA modulus too short'); + return false; + } + + // 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-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}. + * + * @access private + * @param String $m + * @return String + */ + function _rsassa_pkcs1_v1_5_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + user_error('Invalid signature'); + return false; + } + + // RSA verification + + $s = $this->_os2ip($s); + $m2 = $this->_rsavp1($s); + if ($m2 === false) { + user_error('Invalid signature'); + return false; + } + $em = $this->_i2osp($m2, $this->k); + if ($em === false) { + user_error('Invalid signature'); + return false; + } + + // EMSA-PKCS1-v1_5 encoding + + $em2 = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); + if ($em2 === false) { + user_error('RSA modulus too short'); + return false; + } + + // Compare + return $this->_equals($em, $em2); + } + + /** + * Set Encryption Mode + * + * Valid values include CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_ENCRYPTION_PKCS1. + * + * @access public + * @param Integer $mode + */ + function setEncryptionMode($mode) + { + $this->encryptionMode = $mode; + } + + /** + * Set Signature Mode + * + * Valid values include CRYPT_RSA_SIGNATURE_PSS and CRYPT_RSA_SIGNATURE_PKCS1 + * + * @access public + * @param Integer $mode + */ + function setSignatureMode($mode) + { + $this->signatureMode = $mode; + } + + /** + * Set public key comment. + * + * @access public + * @param String $comment + */ + function setComment($comment) + { + $this->comment = $comment; + } + + /** + * Get public key comment. + * + * @access public + * @return String + */ + function getComment() + { + return $this->comment; + } + + /** + * Encryption + * + * Both CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_ENCRYPTION_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 decrypt() + * @access public + * @param String $plaintext + * @return String + */ + function encrypt($plaintext) + { + switch ($this->encryptionMode) { + case CRYPT_RSA_ENCRYPTION_NONE: + $plaintext = str_split($plaintext, $this->k); + $ciphertext = ''; + foreach ($plaintext as $m) { + $ciphertext.= $this->_raw_encrypt($m); + } + return $ciphertext; + case CRYPT_RSA_ENCRYPTION_PKCS1: + $length = $this->k - 11; + if ($length <= 0) { + return false; + } + + $plaintext = str_split($plaintext, $length); + $ciphertext = ''; + foreach ($plaintext as $m) { + $ciphertext.= $this->_rsaes_pkcs1_v1_5_encrypt($m); + } + return $ciphertext; + //case CRYPT_RSA_ENCRYPTION_OAEP: + default: + $length = $this->k - 2 * $this->hLen - 2; + if ($length <= 0) { + return false; + } + + $plaintext = str_split($plaintext, $length); + $ciphertext = ''; + foreach ($plaintext as $m) { + $ciphertext.= $this->_rsaes_oaep_encrypt($m); + } + return $ciphertext; + } + } + + /** + * Decryption + * + * @see encrypt() + * @access public + * @param String $plaintext + * @return String + */ + function decrypt($ciphertext) + { + if ($this->k <= 0) { + return false; + } + + $ciphertext = str_split($ciphertext, $this->k); + $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $this->k, chr(0), STR_PAD_LEFT); + + $plaintext = ''; + + switch ($this->encryptionMode) { + case CRYPT_RSA_ENCRYPTION_NONE: + $decrypt = '_raw_encrypt'; + break; + case CRYPT_RSA_ENCRYPTION_PKCS1: + $decrypt = '_rsaes_pkcs1_v1_5_decrypt'; + break; + //case CRYPT_RSA_ENCRYPTION_OAEP: + default: + $decrypt = '_rsaes_oaep_decrypt'; + } + + foreach ($ciphertext as $c) { + $temp = $this->$decrypt($c); + if ($temp === false) { + return false; + } + $plaintext.= $temp; + } + + return $plaintext; + } + + /** + * Create a signature + * + * @see verify() + * @access public + * @param String $message + * @return String + */ + function sign($message) + { + if (empty($this->modulus) || empty($this->exponent)) { + return false; + } + + switch ($this->signatureMode) { + case CRYPT_RSA_SIGNATURE_PKCS1: + return $this->_rsassa_pkcs1_v1_5_sign($message); + //case CRYPT_RSA_SIGNATURE_PSS: + default: + return $this->_rsassa_pss_sign($message); + } + } + + /** + * Verifies a signature + * + * @see sign() + * @access public + * @param String $message + * @param String $signature + * @return Boolean + */ + function verify($message, $signature) + { + if (empty($this->modulus) || empty($this->exponent)) { + return false; + } + + switch ($this->signatureMode) { + case CRYPT_RSA_SIGNATURE_PKCS1: + return $this->_rsassa_pkcs1_v1_5_verify($message, $signature); + //case CRYPT_RSA_SIGNATURE_PSS: + default: + return $this->_rsassa_pss_verify($message, $signature); + } + } + + /** + * Extract raw BER from Base64 encoding + * + * @access private + * @param String $str + * @return String + */ + 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 + */ + $temp = preg_replace('#.*?^-+[^-]+-+#ms', '', $str, 1); + // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff + $temp = preg_replace('#-+[^-]+-+#', '', $temp); + // remove new lines + $temp = str_replace(array("\r", "\n", ' '), '', $temp); + $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; + return $temp != false ? $temp : $str; + } +} diff --git a/src/RSA/Crypt/Hash.php b/src/RSA/Crypt/Hash.php new file mode 100644 index 0000000..b314c82 --- /dev/null +++ b/src/RSA/Crypt/Hash.php @@ -0,0 +1,842 @@ + + * setKey('abcdefg'); + * + * echo base64_encode($hash->hash('abcdefg')); + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_Hash + * @author Jim Wigginton + * @copyright 2007 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access private + * @see Crypt_Hash::Crypt_Hash() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_HASH_MODE_INTERNAL', 1); +/** + * Toggles the mhash() implementation, which has been deprecated on PHP 5.3.0+. + */ +define('CRYPT_HASH_MODE_MHASH', 2); +/** + * Toggles the hash() implementation, which works on PHP 5.1.2+. + */ +define('CRYPT_HASH_MODE_HASH', 3); +/**#@-*/ + +/** + * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions. + * + * @package Crypt_Hash + * @author Jim Wigginton + * @access public + */ +class Crypt_Hash +{ + /** + * Hash Parameter + * + * @see Crypt_Hash::setHash() + * @var Integer + * @access private + */ + var $hashParam; + + /** + * Byte-length of compression blocks / key (Internal HMAC) + * + * @see Crypt_Hash::setAlgorithm() + * @var Integer + * @access private + */ + var $b; + + /** + * Byte-length of hash output (Internal HMAC) + * + * @see Crypt_Hash::setHash() + * @var Integer + * @access private + */ + var $l = false; + + /** + * Hash Algorithm + * + * @see Crypt_Hash::setHash() + * @var String + * @access private + */ + var $hash; + + /** + * Key + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $key = false; + + /** + * Outer XOR (Internal HMAC) + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $opad; + + /** + * Inner XOR (Internal HMAC) + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $ipad; + + /** + * Default Constructor. + * + * @param optional String $hash + * @return Crypt_Hash + * @access public + */ + public function __construct($hash = 'sha1') + { + if (!defined('CRYPT_HASH_MODE')) { + switch (true) { + case extension_loaded('hash'): + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_HASH); + break; + case extension_loaded('mhash'): + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_MHASH); + break; + default: + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_INTERNAL); + } + } + + $this->setHash($hash); + } + + /** + * Sets the key for HMACs + * + * Keys can be of any length. + * + * @access public + * @param optional String $key + */ + function setKey($key = false) + { + $this->key = $key; + } + + /** + * Gets the hash function. + * + * As set by the constructor or by the setHash() method. + * + * @access public + * @return String + */ + function getHash() + { + return $this->hashParam; + } + + /** + * Sets the hash function. + * + * @access public + * @param String $hash + */ + function setHash($hash) + { + $this->hashParam = $hash = strtolower($hash); + switch ($hash) { + case 'md5-96': + case 'sha1-96': + case 'sha256-96': + case 'sha512-96': + $hash = substr($hash, 0, -3); + $this->l = 12; // 96 / 8 = 12 + break; + case 'md2': + case 'md5': + $this->l = 16; + break; + case 'sha1': + $this->l = 20; + break; + case 'sha256': + $this->l = 32; + break; + case 'sha384': + $this->l = 48; + break; + case 'sha512': + $this->l = 64; + } + + switch ($hash) { + case 'md2': + $mode = CRYPT_HASH_MODE == CRYPT_HASH_MODE_HASH && in_array('md2', hash_algos()) ? + CRYPT_HASH_MODE_HASH : CRYPT_HASH_MODE_INTERNAL; + break; + case 'sha384': + case 'sha512': + $mode = CRYPT_HASH_MODE == CRYPT_HASH_MODE_MHASH ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; + break; + default: + $mode = CRYPT_HASH_MODE; + } + + switch ($mode) { + case CRYPT_HASH_MODE_MHASH: + switch ($hash) { + case 'md5': + $this->hash = MHASH_MD5; + break; + case 'sha256': + $this->hash = MHASH_SHA256; + break; + case 'sha1': + default: + $this->hash = MHASH_SHA1; + } + return; + case CRYPT_HASH_MODE_HASH: + switch ($hash) { + case 'md5': + $this->hash = 'md5'; + return; + case 'md2': + case 'sha256': + case 'sha384': + case 'sha512': + $this->hash = $hash; + return; + case 'sha1': + default: + $this->hash = 'sha1'; + } + return; + } + + switch ($hash) { + case 'md2': + $this->b = 16; + $this->hash = array($this, '_md2'); + break; + case 'md5': + $this->b = 64; + $this->hash = array($this, '_md5'); + break; + case 'sha256': + $this->b = 64; + $this->hash = array($this, '_sha256'); + break; + case 'sha384': + case 'sha512': + $this->b = 128; + $this->hash = array($this, '_sha512'); + break; + case 'sha1': + default: + $this->b = 64; + $this->hash = array($this, '_sha1'); + } + + $this->ipad = str_repeat(chr(0x36), $this->b); + $this->opad = str_repeat(chr(0x5C), $this->b); + } + + /** + * Compute the HMAC. + * + * @access public + * @param String $text + * @return String + */ + function hash($text) + { + $mode = is_array($this->hash) ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; + + if (!empty($this->key) || is_string($this->key)) { + switch ($mode) { + case CRYPT_HASH_MODE_MHASH: + $output = mhash($this->hash, $text, $this->key); + break; + case CRYPT_HASH_MODE_HASH: + $output = hash_hmac($this->hash, $text, $this->key, true); + break; + case CRYPT_HASH_MODE_INTERNAL: + /* "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." + + -- http://tools.ietf.org/html/rfc2104#section-2 */ + $key = strlen($this->key) > $this->b ? call_user_func($this->hash, $this->key) : $this->key; + + $key = str_pad($key, $this->b, chr(0)); // step 1 + $temp = $this->ipad ^ $key; // step 2 + $temp .= $text; // step 3 + $temp = call_user_func($this->hash, $temp); // step 4 + $output = $this->opad ^ $key; // step 5 + $output.= $temp; // step 6 + $output = call_user_func($this->hash, $output); // step 7 + } + } else { + switch ($mode) { + case CRYPT_HASH_MODE_MHASH: + $output = mhash($this->hash, $text); + break; + case CRYPT_HASH_MODE_HASH: + $output = hash($this->hash, $text, true); + break; + case CRYPT_HASH_MODE_INTERNAL: + $output = call_user_func($this->hash, $text); + } + } + + return substr($output, 0, $this->l); + } + + /** + * Returns the hash length (in bytes) + * + * @access public + * @return Integer + */ + function getLength() + { + return $this->l; + } + + /** + * Wrapper for MD5 + * + * @access private + * @param String $m + */ + function _md5($m) + { + return pack('H*', md5($m)); + } + + /** + * Wrapper for SHA1 + * + * @access private + * @param String $m + */ + function _sha1($m) + { + return pack('H*', sha1($m)); + } + + /** + * Pure-PHP implementation of MD2 + * + * See {@link http://tools.ietf.org/html/rfc1319 RFC1319}. + * + * @access private + * @param String $m + */ + function _md2($m) + { + static $s = array( + 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, + 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, + 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, + 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, + 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, + 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, + 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, + 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, + 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, + 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, + 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, + 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, + 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, + 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, + 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, + 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, + 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, + 31, 26, 219, 153, 141, 51, 159, 17, 131, 20 + ); + + // Step 1. Append Padding Bytes + $pad = 16 - (strlen($m) & 0xF); + $m.= str_repeat(chr($pad), $pad); + + $length = strlen($m); + + // Step 2. Append Checksum + $c = str_repeat(chr(0), 16); + $l = chr(0); + for ($i = 0; $i < $length; $i+= 16) { + for ($j = 0; $j < 16; $j++) { + // RFC1319 incorrectly states that C[j] should be set to S[c xor L] + //$c[$j] = chr($s[ord($m[$i + $j] ^ $l)]); + // per , however, C[j] should be set to S[c xor L] xor C[j] + $c[$j] = chr($s[ord($m[$i + $j] ^ $l)] ^ ord($c[$j])); + $l = $c[$j]; + } + } + $m.= $c; + + $length+= 16; + + // Step 3. Initialize MD Buffer + $x = str_repeat(chr(0), 48); + + // Step 4. Process Message in 16-Byte Blocks + for ($i = 0; $i < $length; $i+= 16) { + for ($j = 0; $j < 16; $j++) { + $x[$j + 16] = $m[$i + $j]; + $x[$j + 32] = $x[$j + 16] ^ $x[$j]; + } + $t = chr(0); + for ($j = 0; $j < 18; $j++) { + for ($k = 0; $k < 48; $k++) { + $x[$k] = $t = $x[$k] ^ chr($s[ord($t)]); + //$t = $x[$k] = $x[$k] ^ chr($s[ord($t)]); + } + $t = chr(ord($t) + $j); + } + } + + // Step 5. Output + return substr($x, 0, 16); + } + + /** + * Pure-PHP implementation of SHA256 + * + * See {@link http://en.wikipedia.org/wiki/SHA_hash_functions#SHA-256_.28a_SHA-2_variant.29_pseudocode SHA-256 (a SHA-2 variant) pseudocode - Wikipedia}. + * + * @access private + * @param String $m + */ + function _sha256($m) + { + if (extension_loaded('suhosin')) { + return pack('H*', sha256($m)); + } + + // Initialize variables + $hash = array( + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + ); + // Initialize table of round constants + // (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311) + static $k = array( + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ); + + // Pre-processing + $length = strlen($m); + // to round to nearest 56 mod 64, we'll add 64 - (length + (64 - 56)) % 64 + $m.= str_repeat(chr(0), 64 - (($length + 8) & 0x3F)); + $m[$length] = chr(0x80); + // we don't support hashing strings 512MB long + $m.= pack('N2', 0, $length << 3); + + // Process the message in successive 512-bit chunks + $chunks = str_split($m, 64); + foreach ($chunks as $chunk) { + $w = array(); + for ($i = 0; $i < 16; $i++) { + extract(unpack('Ntemp', $this->_string_shift($chunk, 4))); + $w[] = $temp; + } + + // Extend the sixteen 32-bit words into sixty-four 32-bit words + for ($i = 16; $i < 64; $i++) { + // @codingStandardsIgnoreStart + $s0 = $this->_rightRotate($w[$i - 15], 7) ^ + $this->_rightRotate($w[$i - 15], 18) ^ + $this->_rightShift( $w[$i - 15], 3); + $s1 = $this->_rightRotate($w[$i - 2], 17) ^ + $this->_rightRotate($w[$i - 2], 19) ^ + $this->_rightShift( $w[$i - 2], 10); + // @codingStandardsIgnoreEnd + $w[$i] = $this->_add($w[$i - 16], $s0, $w[$i - 7], $s1); + + } + + // Initialize hash value for this chunk + list($a, $b, $c, $d, $e, $f, $g, $h) = $hash; + + // Main loop + for ($i = 0; $i < 64; $i++) { + $s0 = $this->_rightRotate($a, 2) ^ + $this->_rightRotate($a, 13) ^ + $this->_rightRotate($a, 22); + $maj = ($a & $b) ^ + ($a & $c) ^ + ($b & $c); + $t2 = $this->_add($s0, $maj); + + $s1 = $this->_rightRotate($e, 6) ^ + $this->_rightRotate($e, 11) ^ + $this->_rightRotate($e, 25); + $ch = ($e & $f) ^ + ($this->_not($e) & $g); + $t1 = $this->_add($h, $s1, $ch, $k[$i], $w[$i]); + + $h = $g; + $g = $f; + $f = $e; + $e = $this->_add($d, $t1); + $d = $c; + $c = $b; + $b = $a; + $a = $this->_add($t1, $t2); + } + + // Add this chunk's hash to result so far + $hash = array( + $this->_add($hash[0], $a), + $this->_add($hash[1], $b), + $this->_add($hash[2], $c), + $this->_add($hash[3], $d), + $this->_add($hash[4], $e), + $this->_add($hash[5], $f), + $this->_add($hash[6], $g), + $this->_add($hash[7], $h) + ); + } + + // Produce the final hash value (big-endian) + return pack('N8', $hash[0], $hash[1], $hash[2], $hash[3], $hash[4], $hash[5], $hash[6], $hash[7]); + } + + /** + * Pure-PHP implementation of SHA384 and SHA512 + * + * @access private + * @param String $m + */ + function _sha512($m) + { + if (!class_exists('Math_BigInteger')) { + include_once 'Math/BigInteger.php'; + } + + static $init384, $init512, $k; + + if (!isset($k)) { + // Initialize variables + $init384 = array( // initial values for SHA384 + 'cbbb9d5dc1059ed8', '629a292a367cd507', '9159015a3070dd17', '152fecd8f70e5939', + '67332667ffc00b31', '8eb44a8768581511', 'db0c2e0d64f98fa7', '47b5481dbefa4fa4' + ); + $init512 = array( // initial values for SHA512 + '6a09e667f3bcc908', 'bb67ae8584caa73b', '3c6ef372fe94f82b', 'a54ff53a5f1d36f1', + '510e527fade682d1', '9b05688c2b3e6c1f', '1f83d9abfb41bd6b', '5be0cd19137e2179' + ); + + for ($i = 0; $i < 8; $i++) { + $init384[$i] = new Math_BigInteger($init384[$i], 16); + $init384[$i]->setPrecision(64); + $init512[$i] = new Math_BigInteger($init512[$i], 16); + $init512[$i]->setPrecision(64); + } + + // Initialize table of round constants + // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409) + $k = array( + '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 Math_BigInteger($k[$i], 16); + } + } + + $hash = $this->l == 48 ? $init384 : $init512; + + // 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 = array(); + for ($i = 0; $i < 16; $i++) { + $temp = new Math_BigInteger($this->_string_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 = array( + $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 = array( + $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] = $w[$i - 16]->copy(); + $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 = $hash[0]->copy(); + $b = $hash[1]->copy(); + $c = $hash[2]->copy(); + $d = $hash[3]->copy(); + $e = $hash[4]->copy(); + $f = $hash[5]->copy(); + $g = $hash[6]->copy(); + $h = $hash[7]->copy(); + + // Main loop + for ($i = 0; $i < 80; $i++) { + $temp = array( + $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 = array( + $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 = array( + $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 = array( + $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 = $g->copy(); + $g = $f->copy(); + $f = $e->copy(); + $e = $d->add($t1); + $d = $c->copy(); + $c = $b->copy(); + $b = $a->copy(); + $a = $t1->add($t2); + } + + // Add this chunk's hash to result so far + $hash = array( + $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) + // (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(); + if ($this->l != 48) { + $temp.= $hash[6]->toBytes() . $hash[7]->toBytes(); + } + + return $temp; + } + + /** + * Right Rotate + * + * @access private + * @param Integer $int + * @param Integer $amt + * @see _sha256() + * @return Integer + */ + function _rightRotate($int, $amt) + { + $invamt = 32 - $amt; + $mask = (1 << $invamt) - 1; + return (($int << $invamt) & 0xFFFFFFFF) | (($int >> $amt) & $mask); + } + + /** + * Right Shift + * + * @access private + * @param Integer $int + * @param Integer $amt + * @see _sha256() + * @return Integer + */ + function _rightShift($int, $amt) + { + $mask = (1 << (32 - $amt)) - 1; + return ($int >> $amt) & $mask; + } + + /** + * Not + * + * @access private + * @param Integer $int + * @see _sha256() + * @return Integer + */ + function _not($int) + { + return ~$int & 0xFFFFFFFF; + } + + /** + * Add + * + * _sha256() adds multiple unsigned 32-bit integers. Since PHP doesn't support unsigned integers and since the + * possibility of overflow exists, care has to be taken. Math_BigInteger() could be used but this should be faster. + * + * @param Integer $... + * @return Integer + * @see _sha256() + * @access private + */ + function _add() + { + static $mod; + if (!isset($mod)) { + $mod = pow(2, 32); + } + + $result = 0; + $arguments = func_get_args(); + foreach ($arguments as $argument) { + $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument; + } + + return fmod($result, $mod); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } +} diff --git a/src/RSA/Crypt/Random.php b/src/RSA/Crypt/Random.php new file mode 100644 index 0000000..bb96837 --- /dev/null +++ b/src/RSA/Crypt/Random.php @@ -0,0 +1,300 @@ + + * + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_Random + * @author Jim Wigginton + * @copyright 2007 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +// laravel is a PHP framework that utilizes phpseclib. laravel workbenches may, independently, +// have phpseclib as a requirement as well. if you're developing such a program you may encounter +// a "Cannot redeclare crypt_random_string()" error. +if (!function_exists('crypt_random_string')) { + /** + * "Is Windows" test + * + * @access private + */ + define('CRYPT_RANDOM_IS_WINDOWS', strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); + + /** + * 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 Integer $length + * @return String + * @access public + */ + function crypt_random_string($length) + { + if (CRYPT_RANDOM_IS_WINDOWS) { + // method 1. prior to PHP 5.3 this would call rand() on windows hence the function_exists('class_alias') call. + // ie. class_alias is a function that was introduced in PHP 5.3 + if (function_exists('mcrypt_create_iv') && function_exists('class_alias')) { + return mcrypt_create_iv($length); + } + // method 2. openssl_random_pseudo_bytes was introduced in PHP 5.3.0 but prior to PHP 5.3.4 there was, + // to quote , "possible blocking behavior". as of 5.3.4 + // openssl_random_pseudo_bytes and mcrypt_create_iv do the exact same thing on Windows. ie. they both + // call php_win32_get_random_bytes(): + // + // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/openssl/openssl.c#L5008 + // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1392 + // + // php_win32_get_random_bytes() is defined thusly: + // + // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/win32/winutil.c#L80 + // + // we're calling it, all the same, in the off chance that the mcrypt extension is not available + if (function_exists('openssl_random_pseudo_bytes') && version_compare(PHP_VERSION, '5.3.4', '>=')) { + return openssl_random_pseudo_bytes($length); + } + } else { + // method 1. the fastest + if (function_exists('openssl_random_pseudo_bytes')) { + return openssl_random_pseudo_bytes($length); + } + // method 2 + static $fp = true; + if ($fp === true) { + // warning's will be output unles the error suppression operator is used. errors such as + // "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc. + $fp = @fopen('/dev/urandom', 'rb'); + } + if ($fp !== true && $fp !== false) { // surprisingly faster than !is_bool() or is_resource() + return fread($fp, $length); + } + // method 3. pretty much does the same thing as method 2 per the following url: + // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1391 + // surprisingly slower than method 2. maybe that's because mcrypt_create_iv does a bunch of error checking that we're + // not doing. regardless, this'll only be called if this PHP script couldn't open /dev/urandom due to open_basedir + // restrictions or some such + if (function_exists('mcrypt_create_iv')) { + return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + } + } + // 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 + // soley 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 envirnment 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 = $seed = $_SESSION['seed'] = pack('H*', sha1( + serialize($_SERVER) . + serialize($_POST) . + serialize($_GET) . + serialize($_COOKIE) . + serialize($GLOBALS) . + serialize($_SESSION) . + serialize($_OLD_SESSION) + )); + 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 = pack('H*', sha1($seed . 'A')); + $iv = pack('H*', sha1($seed . 'C')); + + // 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 phpseclib_resolve_include_path('Crypt/AES.php'): + if (!class_exists('Crypt_AES')) { + include_once 'AES.php'; + } + $crypto = new Crypt_AES(CRYPT_AES_MODE_CTR); + break; + case phpseclib_resolve_include_path('Crypt/Twofish.php'): + if (!class_exists('Crypt_Twofish')) { + include_once 'Twofish.php'; + } + $crypto = new Crypt_Twofish(CRYPT_TWOFISH_MODE_CTR); + break; + case phpseclib_resolve_include_path('Crypt/Blowfish.php'): + if (!class_exists('Crypt_Blowfish')) { + include_once 'Blowfish.php'; + } + $crypto = new Crypt_Blowfish(CRYPT_BLOWFISH_MODE_CTR); + break; + case phpseclib_resolve_include_path('Crypt/TripleDES.php'): + if (!class_exists('Crypt_TripleDES')) { + include_once 'TripleDES.php'; + } + $crypto = new Crypt_TripleDES(CRYPT_DES_MODE_CTR); + break; + case phpseclib_resolve_include_path('Crypt/DES.php'): + if (!class_exists('Crypt_DES')) { + include_once 'DES.php'; + } + $crypto = new Crypt_DES(CRYPT_DES_MODE_CTR); + break; + case phpseclib_resolve_include_path('Crypt/RC4.php'): + if (!class_exists('Crypt_RC4')) { + include_once 'RC4.php'; + } + $crypto = new Crypt_RC4(); + break; + default: + user_error('crypt_random_string requires at least one symmetric cipher be loaded'); + return false; + } + + $crypto->setKey($key); + $crypto->setIV($iv); + $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); + } +} + +if (!function_exists('phpseclib_resolve_include_path')) { + /** + * Resolve filename against the include path. + * + * Wrapper around stream_resolve_include_path() (which was introduced in + * PHP 5.3.2) with fallback implementation for earlier PHP versions. + * + * @param string $filename + * @return mixed Filename (string) on success, false otherwise. + * @access public + */ + function phpseclib_resolve_include_path($filename) + { + if (function_exists('stream_resolve_include_path')) { + return stream_resolve_include_path($filename); + } + + // handle non-relative paths + if (file_exists($filename)) { + return realpath($filename); + } + + $paths = PATH_SEPARATOR == ':' ? + preg_split('#(?> and << cannot be used, nor can the modulo operator %, + * which only supports integers. Although this fact will slow this library down, the fact that such a high + * base is being used should more than compensate. + * + * Numbers are stored in {@link http://en.wikipedia.org/wiki/Endianness little endian} format. ie. + * (new MathBigInteger(pow(2, 26)))->value = array(0, 1) + * + * Useful resources are as follows: + * + * - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)} + * - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)} + * - Java's BigInteger classes. See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip + * + * Here's an example of how to use this library: + * + * add($b); + * + * echo $c->toString(); // outputs 5 + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Math + * @package MathBigInteger + * @author Jim Wigginton + * @copyright 2006 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/MathBigInteger + */ + +/**#@+ + * Reduction constants + * + * @access private + * @see MathBigInteger::_reduce() + */ +/** + * @see MathBigInteger::_montgomery() + * @see MathBigInteger::_prepMontgomery() + */ +define('MATH_BIGINTEGER_MONTGOMERY', 0); +/** + * @see MathBigInteger::_barrett() + */ +define('MATH_BIGINTEGER_BARRETT', 1); +/** + * @see MathBigInteger::_mod2() + */ +define('MATH_BIGINTEGER_POWEROF2', 2); +/** + * @see MathBigInteger::_remainder() + */ +define('MATH_BIGINTEGER_CLASSIC', 3); +/** + * @see MathBigInteger::__clone() + */ +define('MATH_BIGINTEGER_NONE', 4); +/**#@-*/ + +/**#@+ + * Array constants + * + * Rather than create a thousands and thousands of new MathBigInteger objects in repeated function calls to add() and + * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them. + * + * @access private + */ +/** + * $result[MATH_BIGINTEGER_VALUE] contains the value. + */ +define('MATH_BIGINTEGER_VALUE', 0); +/** + * $result[MATH_BIGINTEGER_SIGN] contains the sign. + */ +define('MATH_BIGINTEGER_SIGN', 1); +/**#@-*/ + +/**#@+ + * @access private + * @see MathBigInteger::_montgomery() + * @see MathBigInteger::_barrett() + */ +/** + * Cache constants + * + * $cache[MATH_BIGINTEGER_VARIABLE] tells us whether or not the cached data is still valid. + */ +define('MATH_BIGINTEGER_VARIABLE', 0); +/** + * $cache[MATH_BIGINTEGER_DATA] contains the cached data. + */ +define('MATH_BIGINTEGER_DATA', 1); +/**#@-*/ + +/**#@+ + * Mode constants. + * + * @access private + * @see MathBigInteger::MathBigInteger() + */ +/** + * To use the pure-PHP implementation + */ +define('MATH_BIGINTEGER_MODE_INTERNAL', 1); +/** + * To use the BCMath library + * + * (if enabled; otherwise, the internal implementation will be used) + */ +define('MATH_BIGINTEGER_MODE_BCMATH', 2); +/** + * To use the GMP library + * + * (if present; otherwise, either the BCMath or the internal implementation will be used) + */ +define('MATH_BIGINTEGER_MODE_GMP', 3); +/**#@-*/ + +/** + * Karatsuba Cutoff + * + * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication? + * + * @access private + */ +define('MATH_BIGINTEGER_KARATSUBA_CUTOFF', 25); + +/** + * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256 + * numbers. + * + * @package MathBigInteger + * @author Jim Wigginton + * @access public + */ +class MathBigInteger +{ + /** + * Holds the BigInteger's value. + * + * @var Array + * @access private + */ + var $value; + + /** + * Holds the BigInteger's magnitude. + * + * @var Boolean + * @access private + */ + var $is_negative = false; + + /** + * Random number generator function + * + * @see setRandomGenerator() + * @access private + */ + var $generator = 'mt_rand'; + + /** + * Precision + * + * @see setPrecision() + * @access private + */ + var $precision = -1; + + /** + * Precision Bitmask + * + * @see setPrecision() + * @access private + */ + var $bitmask = false; + + /** + * Mode independent value used for serialization. + * + * If the bcmath or gmp extensions are installed $this->value will be a non-serializable resource, hence the need for + * a variable that'll be serializable regardless of whether or not extensions are being used. Unlike $this->value, + * however, $this->hex is only calculated when $this->__sleep() is called. + * + * @see __sleep() + * @see __wakeup() + * @var String + * @access private + */ + var $hex; + + /** + * Converts base-2, base-10, base-16, and binary strings (base-256) to BigIntegers. + * + * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using + * two's compliment. The sole exception to this is -10, which is treated the same as 10 is. + * + * Here's an example: + * + * toString(); // outputs 50 + * ?> + * + * + * @param optional $x base-10 number or base-$base number if $base set. + * @param optional integer $base + * @return MathBigInteger + * @access public + */ + public function __construct($x = 0, $base = 10) + { + if (!defined('MATH_BIGINTEGER_MODE')) { + switch (true) { + case extension_loaded('gmp'): + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_GMP); + break; + case extension_loaded('bcmath'): + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_BCMATH); + break; + default: + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_INTERNAL); + } + } + + if (function_exists('openssl_public_encrypt') && !defined('MATH_BIGINTEGER_OPENSSL_DISABLE') && !defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { + // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work + ob_start(); + @phpinfo(); + $content = ob_get_contents(); + ob_end_clean(); + + preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches); + + $versions = array(); + if (!empty($matches[1])) { + for ($i = 0; $i < count($matches[1]); $i++) { + $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i]))); + + // Remove letter part in OpenSSL version + if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) { + $versions[$matches[1][$i]] = $fullVersion; + } else { + $versions[$matches[1][$i]] = $m[0]; + } + } + } + + // it doesn't appear that OpenSSL versions were reported upon until PHP 5.3+ + switch (true) { + case !isset($versions['Header']): + case !isset($versions['Library']): + case $versions['Header'] == $versions['Library']: + define('MATH_BIGINTEGER_OPENSSL_ENABLED', true); + break; + default: + define('MATH_BIGINTEGER_OPENSSL_DISABLE', true); + } + } + + if (!defined('PHP_INT_SIZE')) { + define('PHP_INT_SIZE', 4); + } + + if (!defined('MATH_BIGINTEGER_BASE') && MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_INTERNAL) { + switch (PHP_INT_SIZE) { + case 8: // use 64-bit integers if int size is 8 bytes + define('MATH_BIGINTEGER_BASE', 31); + define('MATH_BIGINTEGER_BASE_FULL', 0x80000000); + define('MATH_BIGINTEGER_MAX_DIGIT', 0x7FFFFFFF); + define('MATH_BIGINTEGER_MSB', 0x40000000); + // 10**9 is the closest we can get to 2**31 without passing it + define('MATH_BIGINTEGER_MAX10', 1000000000); + define('MATH_BIGINTEGER_MAX10_LEN', 9); + // the largest digit that may be used in addition / subtraction + define('MATH_BIGINTEGER_MAX_DIGIT2', pow(2, 62)); + break; + //case 4: // use 64-bit floats if int size is 4 bytes + default: + define('MATH_BIGINTEGER_BASE', 26); + define('MATH_BIGINTEGER_BASE_FULL', 0x4000000); + define('MATH_BIGINTEGER_MAX_DIGIT', 0x3FFFFFF); + define('MATH_BIGINTEGER_MSB', 0x2000000); + // 10**7 is the closest to 2**26 without passing it + define('MATH_BIGINTEGER_MAX10', 10000000); + define('MATH_BIGINTEGER_MAX10_LEN', 7); + // the largest digit that may be used in addition / subtraction + // we do pow(2, 52) instead of using 4503599627370496 directly because some + // PHP installations will truncate 4503599627370496. + define('MATH_BIGINTEGER_MAX_DIGIT2', pow(2, 52)); + } + } + + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + switch (true) { + case is_resource($x) && get_resource_type($x) == 'GMP integer': + // PHP 5.6 switched GMP from using resources to objects + case is_object($x) && get_class($x) == 'GMP': + $this->value = $x; + return; + } + $this->value = gmp_init(0); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $this->value = '0'; + break; + default: + $this->value = array(); + } + + // '0' counts as empty() but when the base is 256 '0' is equal to ord('0') or 48 + // '0' is the only value like this per http://php.net/empty + if (empty($x) && (abs($base) != 256 || $x !== '0')) { + return; + } + + switch ($base) { + case -256: + if (ord($x[0]) & 0x80) { + $x = ~$x; + $this->is_negative = true; + } + case 256: + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + $sign = $this->is_negative ? '-' : ''; + $this->value = gmp_init($sign . '0x' . bin2hex($x)); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + // round $len to the nearest 4 (thanks, DavidMJ!) + $len = (strlen($x) + 3) & 0xFFFFFFFC; + + $x = str_pad($x, $len, chr(0), STR_PAD_LEFT); + + for ($i = 0; $i < $len; $i+= 4) { + $this->value = bcmul($this->value, '4294967296', 0); // 4294967296 == 2**32 + $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3])), 0); + } + + if ($this->is_negative) { + $this->value = '-' . $this->value; + } + + break; + // converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb) + default: + while (strlen($x)) { + $this->value[] = $this->_bytes2int($this->_base256_rshift($x, MATH_BIGINTEGER_BASE)); + } + } + + if ($this->is_negative) { + if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL) { + $this->is_negative = false; + } + $temp = $this->add(new MathBigInteger('-1')); + $this->value = $temp->value; + } + break; + case 16: + case -16: + if ($base > 0 && $x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x); + + $is_negative = false; + if ($base < 0 && hexdec($x[0]) >= 8) { + $this->is_negative = $is_negative = true; + $x = bin2hex(~pack('H*', $x)); + } + + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = $this->is_negative ? '-0x' . $x : '0x' . $x; + $this->value = gmp_init($temp); + $this->is_negative = false; + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $x = ( strlen($x) & 1 ) ? '0' . $x : $x; + $temp = new MathBigInteger(pack('H*', $x), 256); + $this->value = $this->is_negative ? '-' . $temp->value : $temp->value; + $this->is_negative = false; + break; + default: + $x = ( strlen($x) & 1 ) ? '0' . $x : $x; + $temp = new MathBigInteger(pack('H*', $x), 256); + $this->value = $temp->value; + } + + if ($is_negative) { + $temp = $this->add(new MathBigInteger('-1')); + $this->value = $temp->value; + } + break; + case 10: + case -10: + // (?value = gmp_init($x); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different + // results then doing it on '-1' does (modInverse does $x[0]) + $this->value = $x === '-' ? '0' : (string) $x; + break; + default: + $temp = new MathBigInteger(); + + $multiplier = new MathBigInteger(); + $multiplier->value = array(MATH_BIGINTEGER_MAX10); + + if ($x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = str_pad($x, strlen($x) + ((MATH_BIGINTEGER_MAX10_LEN - 1) * strlen($x)) % MATH_BIGINTEGER_MAX10_LEN, 0, STR_PAD_LEFT); + while (strlen($x)) { + $temp = $temp->multiply($multiplier); + $temp = $temp->add(new MathBigInteger($this->_int2bytes(substr($x, 0, MATH_BIGINTEGER_MAX10_LEN)), 256)); + $x = substr($x, MATH_BIGINTEGER_MAX10_LEN); + } + + $this->value = $temp->value; + } + break; + case 2: // base-2 support originally implemented by Lluis Pamies - thanks! + case -2: + if ($base > 0 && $x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = preg_replace('#^([01]*).*#', '$1', $x); + $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT); + + $str = '0x'; + while (strlen($x)) { + $part = substr($x, 0, 4); + $str.= dechex(bindec($part)); + $x = substr($x, 4); + } + + if ($this->is_negative) { + $str = '-' . $str; + } + + $temp = new MathBigInteger($str, 8 * $base); // ie. either -16 or +16 + $this->value = $temp->value; + $this->is_negative = $temp->is_negative; + + break; + default: + // base not supported, so we'll let $this == 0 + } + } + + /** + * Converts a BigInteger to a byte string (eg. base-256). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * + * toBytes(); // outputs chr(65) + * ?> + * + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**8 + */ + function toBytes($twos_compliment = false) + { + if ($twos_compliment) { + $comparison = $this->compare(new MathBigInteger()); + if ($comparison == 0) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $temp = $comparison < 0 ? $this->add(new MathBigInteger(1)) : $this->copy(); + $bytes = $temp->toBytes(); + + if (empty($bytes)) { // eg. if the number we're trying to convert is -1 + $bytes = chr(0); + } + + if (ord($bytes[0]) & 0x80) { + $bytes = chr(0) . $bytes; + } + + return $comparison < 0 ? ~$bytes : $bytes; + } + + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + if (gmp_cmp($this->value, gmp_init(0)) == 0) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $temp = gmp_strval(gmp_abs($this->value), 16); + $temp = ( strlen($temp) & 1 ) ? '0' . $temp : $temp; + $temp = pack('H*', $temp); + + return $this->precision > 0 ? + substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + ltrim($temp, chr(0)); + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '0') { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $value = ''; + $current = $this->value; + + if ($current[0] == '-') { + $current = substr($current, 1); + } + + while (bccomp($current, '0', 0) > 0) { + $temp = bcmod($current, '16777216'); + $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value; + $current = bcdiv($current, '16777216', 0); + } + + return $this->precision > 0 ? + substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + ltrim($value, chr(0)); + } + + if (!count($this->value)) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + $result = $this->_int2bytes($this->value[count($this->value) - 1]); + + $temp = $this->copy(); + + for ($i = count($temp->value) - 2; $i >= 0; --$i) { + $temp->_base256_lshift($result, MATH_BIGINTEGER_BASE); + $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT); + } + + return $this->precision > 0 ? + str_pad(substr($result, -(($this->precision + 7) >> 3)), ($this->precision + 7) >> 3, chr(0), STR_PAD_LEFT) : + $result; + } + + /** + * Converts a BigInteger to a hex string (eg. base-16)). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * + * toHex(); // outputs '41' + * ?> + * + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**8 + */ + function toHex($twos_compliment = false) + { + return bin2hex($this->toBytes($twos_compliment)); + } + + /** + * Converts a BigInteger to a bit string (eg. base-2). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * + * toBits(); // outputs '1000001' + * ?> + * + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**2 + */ + function toBits($twos_compliment = false) + { + $hex = $this->toHex($twos_compliment); + $bits = ''; + for ($i = strlen($hex) - 8, $start = strlen($hex) & 7; $i >= $start; $i-=8) { + $bits = str_pad(decbin(hexdec(substr($hex, $i, 8))), 32, '0', STR_PAD_LEFT) . $bits; + } + if ($start) { // hexdec('') == 0 + $bits = str_pad(decbin(hexdec(substr($hex, 0, $start))), 8, '0', STR_PAD_LEFT) . $bits; + } + $result = $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0'); + + if ($twos_compliment && $this->compare(new MathBigInteger()) > 0 && $this->precision <= 0) { + return '0' . $result; + } + + return $result; + } + + /** + * Converts a BigInteger to a base-10 number. + * + * Here's an example: + * + * toString(); // outputs 50 + * ?> + * + * + * @return String + * @access public + * @internal Converts a base-2**26 number to base-10**7 (which is pretty much base-10) + */ + function toString() + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_strval($this->value); + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '0') { + return '0'; + } + + return ltrim($this->value, '0'); + } + + if (!count($this->value)) { + return '0'; + } + + $temp = $this->copy(); + $temp->is_negative = false; + + $divisor = new MathBigInteger(); + $divisor->value = array(MATH_BIGINTEGER_MAX10); + $result = ''; + while (count($temp->value)) { + list($temp, $mod) = $temp->divide($divisor); + $result = str_pad(isset($mod->value[0]) ? $mod->value[0] : '', MATH_BIGINTEGER_MAX10_LEN, '0', STR_PAD_LEFT) . $result; + } + $result = ltrim($result, '0'); + if (empty($result)) { + $result = '0'; + } + + if ($this->is_negative) { + $result = '-' . $result; + } + + return $result; + } + + /** + * Copy an object + * + * PHP5 passes objects by reference while PHP4 passes by value. As such, we need a function to guarantee + * that all objects are passed by value, when appropriate. More information can be found here: + * + * {@link http://php.net/language.oop5.basic#51624} + * + * @access public + * @see __clone() + * @return MathBigInteger + */ + function copy() + { + $temp = new MathBigInteger(); + $temp->value = $this->value; + $temp->is_negative = $this->is_negative; + $temp->generator = $this->generator; + $temp->precision = $this->precision; + $temp->bitmask = $this->bitmask; + return $temp; + } + + /** + * __toString() magic method + * + * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call + * toString(). + * + * @access public + * @internal Implemented per a suggestion by Techie-Michael - thanks! + */ + function __toString() + { + return $this->toString(); + } + + /** + * __clone() magic method + * + * Although you can call MathBigInteger::__toString() directly in PHP5, you cannot call MathBigInteger::__clone() + * directly in PHP5. You can in PHP4 since it's not a magic method, but in PHP5, you have to call it by using the PHP5 + * only syntax of $y = clone $x. As such, if you're trying to write an application that works on both PHP4 and PHP5, + * call MathBigInteger::copy(), instead. + * + * @access public + * @see copy() + * @return MathBigInteger + */ + function __clone() + { + return $this->copy(); + } + + /** + * __sleep() magic method + * + * Will be called, automatically, when serialize() is called on a MathBigInteger object. + * + * @see __wakeup() + * @access public + */ + function __sleep() + { + $this->hex = $this->toHex(true); + $vars = array('hex'); + if ($this->generator != 'mt_rand') { + $vars[] = 'generator'; + } + if ($this->precision > 0) { + $vars[] = 'precision'; + } + return $vars; + + } + + /** + * __wakeup() magic method + * + * Will be called, automatically, when unserialize() is called on a MathBigInteger object. + * + * @see __sleep() + * @access public + */ + function __wakeup() + { + $temp = new MathBigInteger($this->hex, -16); + $this->value = $temp->value; + $this->is_negative = $temp->is_negative; + $this->setRandomGenerator($this->generator); + if ($this->precision > 0) { + // recalculate $this->bitmask + $this->setPrecision($this->precision); + } + } + + /** + * Adds two BigIntegers. + * + * Here's an example: + * + * add($b); + * + * echo $c->toString(); // outputs 30 + * ?> + * + * + * @param MathBigInteger $y + * @return MathBigInteger + * @access public + * @internal Performs base-2**52 addition + */ + function add($y) + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new MathBigInteger(); + $temp->value = gmp_add($this->value, $y->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new MathBigInteger(); + $temp->value = bcadd($this->value, $y->value, 0); + + return $this->_normalize($temp); + } + + $temp = $this->_add($this->value, $this->is_negative, $y->value, $y->is_negative); + + $result = new MathBigInteger(); + $result->value = $temp[MATH_BIGINTEGER_VALUE]; + $result->is_negative = $temp[MATH_BIGINTEGER_SIGN]; + + return $this->_normalize($result); + } + + /** + * Performs addition. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _add($x_value, $x_negative, $y_value, $y_negative) + { + $x_size = count($x_value); + $y_size = count($y_value); + + if ($x_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $y_value, + MATH_BIGINTEGER_SIGN => $y_negative + ); + } elseif ($y_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $x_value, + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + // subtract, if appropriate + if ($x_negative != $y_negative) { + if ($x_value == $y_value) { + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + $temp = $this->_subtract($x_value, false, $y_value, false); + $temp[MATH_BIGINTEGER_SIGN] = $this->_compare($x_value, false, $y_value, false) > 0 ? + $x_negative : $y_negative; + + return $temp; + } + + if ($x_size < $y_size) { + $size = $x_size; + $value = $y_value; + } else { + $size = $y_size; + $value = $x_value; + } + + $value[count($value)] = 0; // just in case the carry adds an extra digit + + $carry = 0; + for ($i = 0, $j = 1; $j < $size; $i+=2, $j+=2) { + $sum = $x_value[$j] * MATH_BIGINTEGER_BASE_FULL + $x_value[$i] + $y_value[$j] * MATH_BIGINTEGER_BASE_FULL + $y_value[$i] + $carry; + $carry = $sum >= MATH_BIGINTEGER_MAX_DIGIT2; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 + $sum = $carry ? $sum - MATH_BIGINTEGER_MAX_DIGIT2 : $sum; + + $temp = MATH_BIGINTEGER_BASE === 26 ? intval($sum / 0x4000000) : ($sum >> 31); + + $value[$i] = (int) ($sum - MATH_BIGINTEGER_BASE_FULL * $temp); // eg. a faster alternative to fmod($sum, 0x4000000) + $value[$j] = $temp; + } + + if ($j == $size) { // ie. if $y_size is odd + $sum = $x_value[$i] + $y_value[$i] + $carry; + $carry = $sum >= MATH_BIGINTEGER_BASE_FULL; + $value[$i] = $carry ? $sum - MATH_BIGINTEGER_BASE_FULL : $sum; + ++$i; // ie. let $i = $j since we've just done $value[$i] + } + + if ($carry) { + for (; $value[$i] == MATH_BIGINTEGER_MAX_DIGIT; ++$i) { + $value[$i] = 0; + } + ++$value[$i]; + } + + return array( + MATH_BIGINTEGER_VALUE => $this->_trim($value), + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + /** + * Subtracts two BigIntegers. + * + * Here's an example: + * + * subtract($b); + * + * echo $c->toString(); // outputs -10 + * ?> + * + * + * @param MathBigInteger $y + * @return MathBigInteger + * @access public + * @internal Performs base-2**52 subtraction + */ + function subtract($y) + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new MathBigInteger(); + $temp->value = gmp_sub($this->value, $y->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new MathBigInteger(); + $temp->value = bcsub($this->value, $y->value, 0); + + return $this->_normalize($temp); + } + + $temp = $this->_subtract($this->value, $this->is_negative, $y->value, $y->is_negative); + + $result = new MathBigInteger(); + $result->value = $temp[MATH_BIGINTEGER_VALUE]; + $result->is_negative = $temp[MATH_BIGINTEGER_SIGN]; + + return $this->_normalize($result); + } + + /** + * Performs subtraction. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _subtract($x_value, $x_negative, $y_value, $y_negative) + { + $x_size = count($x_value); + $y_size = count($y_value); + + if ($x_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $y_value, + MATH_BIGINTEGER_SIGN => !$y_negative + ); + } elseif ($y_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $x_value, + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + // add, if appropriate (ie. -$x - +$y or +$x - -$y) + if ($x_negative != $y_negative) { + $temp = $this->_add($x_value, false, $y_value, false); + $temp[MATH_BIGINTEGER_SIGN] = $x_negative; + + return $temp; + } + + $diff = $this->_compare($x_value, $x_negative, $y_value, $y_negative); + + if (!$diff) { + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + // switch $x and $y around, if appropriate. + if ((!$x_negative && $diff < 0) || ($x_negative && $diff > 0)) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_negative = !$x_negative; + + $x_size = count($x_value); + $y_size = count($y_value); + } + + // at this point, $x_value should be at least as big as - if not bigger than - $y_value + + $carry = 0; + for ($i = 0, $j = 1; $j < $y_size; $i+=2, $j+=2) { + $sum = $x_value[$j] * MATH_BIGINTEGER_BASE_FULL + $x_value[$i] - $y_value[$j] * MATH_BIGINTEGER_BASE_FULL - $y_value[$i] - $carry; + $carry = $sum < 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 + $sum = $carry ? $sum + MATH_BIGINTEGER_MAX_DIGIT2 : $sum; + + $temp = MATH_BIGINTEGER_BASE === 26 ? intval($sum / 0x4000000) : ($sum >> 31); + + $x_value[$i] = (int) ($sum - MATH_BIGINTEGER_BASE_FULL * $temp); + $x_value[$j] = $temp; + } + + if ($j == $y_size) { // ie. if $y_size is odd + $sum = $x_value[$i] - $y_value[$i] - $carry; + $carry = $sum < 0; + $x_value[$i] = $carry ? $sum + MATH_BIGINTEGER_BASE_FULL : $sum; + ++$i; + } + + if ($carry) { + for (; !$x_value[$i]; ++$i) { + $x_value[$i] = MATH_BIGINTEGER_MAX_DIGIT; + } + --$x_value[$i]; + } + + return array( + MATH_BIGINTEGER_VALUE => $this->_trim($x_value), + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + /** + * Multiplies two BigIntegers + * + * Here's an example: + * + * multiply($b); + * + * echo $c->toString(); // outputs 200 + * ?> + * + * + * @param MathBigInteger $x + * @return MathBigInteger + * @access public + */ + function multiply($x) + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new MathBigInteger(); + $temp->value = gmp_mul($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new MathBigInteger(); + $temp->value = bcmul($this->value, $x->value, 0); + + return $this->_normalize($temp); + } + + $temp = $this->_multiply($this->value, $this->is_negative, $x->value, $x->is_negative); + + $product = new MathBigInteger(); + $product->value = $temp[MATH_BIGINTEGER_VALUE]; + $product->is_negative = $temp[MATH_BIGINTEGER_SIGN]; + + return $this->_normalize($product); + } + + /** + * Performs multiplication. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _multiply($x_value, $x_negative, $y_value, $y_negative) + { + //if ( $x_value == $y_value ) { + // return array( + // MATH_BIGINTEGER_VALUE => $this->_square($x_value), + // MATH_BIGINTEGER_SIGN => $x_sign != $y_value + // ); + //} + + $x_length = count($x_value); + $y_length = count($y_value); + + if (!$x_length || !$y_length) { // a 0 is being multiplied + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + return array( + MATH_BIGINTEGER_VALUE => min($x_length, $y_length) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ? + $this->_trim($this->_regularMultiply($x_value, $y_value)) : + $this->_trim($this->_karatsuba($x_value, $y_value)), + MATH_BIGINTEGER_SIGN => $x_negative != $y_negative + ); + } + + /** + * Performs long multiplication on two BigIntegers + * + * Modeled after 'multiply' in MutableBigInteger.java. + * + * @param Array $x_value + * @param Array $y_value + * @return Array + * @access private + */ + function _regularMultiply($x_value, $y_value) + { + $x_length = count($x_value); + $y_length = count($y_value); + + if (!$x_length || !$y_length) { // a 0 is being multiplied + return array(); + } + + if ($x_length < $y_length) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_length = count($x_value); + $y_length = count($y_value); + } + + $product_value = $this->_array_repeat(0, $x_length + $y_length); + + // the following for loop could be removed if the for loop following it + // (the one with nested for loops) initially set $i to 0, but + // doing so would also make the result in one set of unnecessary adds, + // since on the outermost loops first pass, $product->value[$k] is going + // to always be 0 + + $carry = 0; + + for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0 + $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 + $carry = MATH_BIGINTEGER_BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $product_value[$j] = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * $carry); + } + + $product_value[$j] = $carry; + + // the above for loop is what the previous comment was talking about. the + // following for loop is the "one with nested for loops" + for ($i = 1; $i < $y_length; ++$i) { + $carry = 0; + + for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k) { + $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; + $carry = MATH_BIGINTEGER_BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $product_value[$k] = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * $carry); + } + + $product_value[$k] = $carry; + } + + return $product_value; + } + + /** + * Performs Karatsuba multiplication on two BigIntegers + * + * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}. + * + * @param Array $x_value + * @param Array $y_value + * @return Array + * @access private + */ + function _karatsuba($x_value, $y_value) + { + $m = min(count($x_value) >> 1, count($y_value) >> 1); + + if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { + return $this->_regularMultiply($x_value, $y_value); + } + + $x1 = array_slice($x_value, $m); + $x0 = array_slice($x_value, 0, $m); + $y1 = array_slice($y_value, $m); + $y0 = array_slice($y_value, 0, $m); + + $z2 = $this->_karatsuba($x1, $y1); + $z0 = $this->_karatsuba($x0, $y0); + + $z1 = $this->_add($x1, false, $x0, false); + $temp = $this->_add($y1, false, $y0, false); + $z1 = $this->_karatsuba($z1[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_VALUE]); + $temp = $this->_add($z2, false, $z0, false); + $z1 = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false); + + $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); + $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]); + + $xy = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]); + $xy = $this->_add($xy[MATH_BIGINTEGER_VALUE], $xy[MATH_BIGINTEGER_SIGN], $z0, false); + + return $xy[MATH_BIGINTEGER_VALUE]; + } + + /** + * Performs squaring + * + * @param Array $x + * @return Array + * @access private + */ + function _square($x = false) + { + return count($x) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ? + $this->_trim($this->_baseSquare($x)) : + $this->_trim($this->_karatsubaSquare($x)); + } + + /** + * Performs traditional squaring on two BigIntegers + * + * Squaring can be done faster than multiplying a number by itself can be. See + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information. + * + * @param Array $value + * @return Array + * @access private + */ + function _baseSquare($value) + { + if (empty($value)) { + return array(); + } + $square_value = $this->_array_repeat(0, 2 * count($value)); + + for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i) { + $i2 = $i << 1; + + $temp = $square_value[$i2] + $value[$i] * $value[$i]; + $carry = MATH_BIGINTEGER_BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $square_value[$i2] = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * $carry); + + // note how we start from $i+1 instead of 0 as we do in multiplication. + for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k) { + $temp = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry; + $carry = MATH_BIGINTEGER_BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $square_value[$k] = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * $carry); + } + + // the following line can yield values larger 2**15. at this point, PHP should switch + // over to floats. + $square_value[$i + $max_index + 1] = $carry; + } + + return $square_value; + } + + /** + * Performs Karatsuba "squaring" on two BigIntegers + * + * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}. + * + * @param Array $value + * @return Array + * @access private + */ + function _karatsubaSquare($value) + { + $m = count($value) >> 1; + + if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { + return $this->_baseSquare($value); + } + + $x1 = array_slice($value, $m); + $x0 = array_slice($value, 0, $m); + + $z2 = $this->_karatsubaSquare($x1); + $z0 = $this->_karatsubaSquare($x0); + + $z1 = $this->_add($x1, false, $x0, false); + $z1 = $this->_karatsubaSquare($z1[MATH_BIGINTEGER_VALUE]); + $temp = $this->_add($z2, false, $z0, false); + $z1 = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false); + + $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); + $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]); + + $xx = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]); + $xx = $this->_add($xx[MATH_BIGINTEGER_VALUE], $xx[MATH_BIGINTEGER_SIGN], $z0, false); + + return $xx[MATH_BIGINTEGER_VALUE]; + } + + /** + * Divides two BigIntegers. + * + * Returns an array whose first element contains the quotient and whose second element contains the + * "common residue". If the remainder would be positive, the "common residue" and the remainder are the + * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder + * and the divisor (basically, the "common residue" is the first positive modulo). + * + * Here's an example: + * + * divide($b); + * + * echo $quotient->toString(); // outputs 0 + * echo "\r\n"; + * echo $remainder->toString(); // outputs 10 + * ?> + * + * + * @param MathBigInteger $y + * @return Array + * @access public + * @internal This function is based off of {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}. + */ + function divide($y) + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + $quotient = new MathBigInteger(); + $remainder = new MathBigInteger(); + + list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value); + + if (gmp_sign($remainder->value) < 0) { + $remainder->value = gmp_add($remainder->value, gmp_abs($y->value)); + } + + return array($this->_normalize($quotient), $this->_normalize($remainder)); + case MATH_BIGINTEGER_MODE_BCMATH: + $quotient = new MathBigInteger(); + $remainder = new MathBigInteger(); + + $quotient->value = bcdiv($this->value, $y->value, 0); + $remainder->value = bcmod($this->value, $y->value); + + if ($remainder->value[0] == '-') { + $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0); + } + + return array($this->_normalize($quotient), $this->_normalize($remainder)); + } + + if (count($y->value) == 1) { + list($q, $r) = $this->_divide_digit($this->value, $y->value[0]); + $quotient = new MathBigInteger(); + $remainder = new MathBigInteger(); + $quotient->value = $q; + $remainder->value = array($r); + $quotient->is_negative = $this->is_negative != $y->is_negative; + return array($this->_normalize($quotient), $this->_normalize($remainder)); + } + + static $zero; + if (!isset($zero)) { + $zero = new MathBigInteger(); + } + + $x = $this->copy(); + $y = $y->copy(); + + $x_sign = $x->is_negative; + $y_sign = $y->is_negative; + + $x->is_negative = $y->is_negative = false; + + $diff = $x->compare($y); + + if (!$diff) { + $temp = new MathBigInteger(); + $temp->value = array(1); + $temp->is_negative = $x_sign != $y_sign; + return array($this->_normalize($temp), $this->_normalize(new MathBigInteger())); + } + + if ($diff < 0) { + // if $x is negative, "add" $y. + if ($x_sign) { + $x = $y->subtract($x); + } + return array($this->_normalize(new MathBigInteger()), $this->_normalize($x)); + } + + // normalize $x and $y as described in HAC 14.23 / 14.24 + $msb = $y->value[count($y->value) - 1]; + for ($shift = 0; !($msb & MATH_BIGINTEGER_MSB); ++$shift) { + $msb <<= 1; + } + $x->_lshift($shift); + $y->_lshift($shift); + $y_value = &$y->value; + + $x_max = count($x->value) - 1; + $y_max = count($y->value) - 1; + + $quotient = new MathBigInteger(); + $quotient_value = &$quotient->value; + $quotient_value = $this->_array_repeat(0, $x_max - $y_max + 1); + + static $temp, $lhs, $rhs; + if (!isset($temp)) { + $temp = new MathBigInteger(); + $lhs = new MathBigInteger(); + $rhs = new MathBigInteger(); + } + $temp_value = &$temp->value; + $rhs_value = &$rhs->value; + + // $temp = $y << ($x_max - $y_max-1) in base 2**26 + $temp_value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y_value); + + while ($x->compare($temp) >= 0) { + // calculate the "common residue" + ++$quotient_value[$x_max - $y_max]; + $x = $x->subtract($temp); + $x_max = count($x->value) - 1; + } + + for ($i = $x_max; $i >= $y_max + 1; --$i) { + $x_value = &$x->value; + $x_window = array( + isset($x_value[$i]) ? $x_value[$i] : 0, + isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0, + isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0 + ); + $y_window = array( + $y_value[$y_max], + ( $y_max > 0 ) ? $y_value[$y_max - 1] : 0 + ); + + $q_index = $i - $y_max - 1; + if ($x_window[0] == $y_window[0]) { + $quotient_value[$q_index] = MATH_BIGINTEGER_MAX_DIGIT; + } else { + $quotient_value[$q_index] = $this->_safe_divide( + $x_window[0] * MATH_BIGINTEGER_BASE_FULL + $x_window[1], + $y_window[0] + ); + } + + $temp_value = array($y_window[1], $y_window[0]); + + $lhs->value = array($quotient_value[$q_index]); + $lhs = $lhs->multiply($temp); + + $rhs_value = array($x_window[2], $x_window[1], $x_window[0]); + + while ($lhs->compare($rhs) > 0) { + --$quotient_value[$q_index]; + + $lhs->value = array($quotient_value[$q_index]); + $lhs = $lhs->multiply($temp); + } + + $adjust = $this->_array_repeat(0, $q_index); + $temp_value = array($quotient_value[$q_index]); + $temp = $temp->multiply($y); + $temp_value = &$temp->value; + $temp_value = array_merge($adjust, $temp_value); + + $x = $x->subtract($temp); + + if ($x->compare($zero) < 0) { + $temp_value = array_merge($adjust, $y_value); + $x = $x->add($temp); + + --$quotient_value[$q_index]; + } + + $x_max = count($x_value) - 1; + } + + // unnormalize the remainder + $x->_rshift($shift); + + $quotient->is_negative = $x_sign != $y_sign; + + // calculate the "common residue", if appropriate + if ($x_sign) { + $y->_rshift($shift); + $x = $y->subtract($x); + } + + return array($this->_normalize($quotient), $this->_normalize($x)); + } + + /** + * Divides a BigInteger by a regular integer + * + * abc / x = a00 / x + b0 / x + c / x + * + * @param Array $dividend + * @param Array $divisor + * @return Array + * @access private + */ + function _divide_digit($dividend, $divisor) + { + $carry = 0; + $result = array(); + + for ($i = count($dividend) - 1; $i >= 0; --$i) { + $temp = MATH_BIGINTEGER_BASE_FULL * $carry + $dividend[$i]; + $result[$i] = $this->_safe_divide($temp, $divisor); + $carry = (int) ($temp - $divisor * $result[$i]); + } + + return array($result, $carry); + } + + /** + * Performs modular exponentiation. + * + * Here's an example: + * + * modPow($b, $c); + * + * echo $c->toString(); // outputs 10 + * ?> + * + * + * @param MathBigInteger $e + * @param MathBigInteger $n + * @return MathBigInteger + * @access public + * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and + * and although the approach involving repeated squaring does vastly better, it, too, is impractical + * for our purposes. The reason being that division - by far the most complicated and time-consuming + * of the basic operations (eg. +,-,*,/) - occurs multiple times within it. + * + * Modular reductions resolve this issue. Although an individual modular reduction takes more time + * then an individual division, when performed in succession (with the same modulo), they're a lot faster. + * + * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction, + * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the + * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because + * the product of two odd numbers is odd), but what about when RSA isn't used? + * + * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a + * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the + * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however, + * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and + * the other, a power of two - and recombine them, later. This is the method that this modPow function uses. + * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates. + */ + function modPow($e, $n) + { + $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs(); + + if ($e->compare(new MathBigInteger()) < 0) { + $e = $e->abs(); + + $temp = $this->modInverse($n); + if ($temp === false) { + return false; + } + + return $this->_normalize($temp->modPow($e, $n)); + } + + if (MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP) { + $temp = new MathBigInteger(); + $temp->value = gmp_powm($this->value, $e->value, $n->value); + + return $this->_normalize($temp); + } + + if ($this->compare(new MathBigInteger()) < 0 || $this->compare($n) > 0) { + list(, $temp) = $this->divide($n); + return $temp->modPow($e, $n); + } + + if (defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { + $components = array( + 'modulus' => $n->toBytes(true), + 'publicExponent' => $e->toBytes(true) + ); + + $components = array( + 'modulus' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['modulus'])), $components['modulus']), + 'publicExponent' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['publicExponent'])), $components['publicExponent']) + ); + + $RSAPublicKey = pack( + 'Ca*a*a*', + 48, + $this->_encodeASN1Length(strlen($components['modulus']) + strlen($components['publicExponent'])), + $components['modulus'], + $components['publicExponent'] + ); + + $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA + $RSAPublicKey = chr(0) . $RSAPublicKey; + $RSAPublicKey = chr(3) . $this->_encodeASN1Length(strlen($RSAPublicKey)) . $RSAPublicKey; + + $encapsulated = pack( + 'Ca*a*', + 48, + $this->_encodeASN1Length(strlen($rsaOID . $RSAPublicKey)), + $rsaOID . $RSAPublicKey + ); + + $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($encapsulated)) . + '-----END PUBLIC KEY-----'; + + $plaintext = str_pad($this->toBytes(), strlen($n->toBytes(true)) - 1, "\0", STR_PAD_LEFT); + + if (openssl_public_encrypt($plaintext, $result, $RSAPublicKey, OPENSSL_NO_PADDING)) { + return new MathBigInteger($result, 256); + } + } + + if (MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH) { + $temp = new MathBigInteger(); + $temp->value = bcpowmod($this->value, $e->value, $n->value, 0); + + return $this->_normalize($temp); + } + + if (empty($e->value)) { + $temp = new MathBigInteger(); + $temp->value = array(1); + return $this->_normalize($temp); + } + + if ($e->value == array(1)) { + list(, $temp) = $this->divide($n); + return $this->_normalize($temp); + } + + if ($e->value == array(2)) { + $temp = new MathBigInteger(); + $temp->value = $this->_square($this->value); + list(, $temp) = $temp->divide($n); + return $this->_normalize($temp); + } + + return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_BARRETT)); + + // the following code, although not callable, can be run independently of the above code + // although the above code performed better in my benchmarks the following could might + // perform better under different circumstances. in lieu of deleting it it's just been + // made uncallable + + // is the modulo odd? + if ($n->value[0] & 1) { + return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_MONTGOMERY)); + } + // if it's not, it's even + + // find the lowest set bit (eg. the max pow of 2 that divides $n) + for ($i = 0; $i < count($n->value); ++$i) { + if ($n->value[$i]) { + $temp = decbin($n->value[$i]); + $j = strlen($temp) - strrpos($temp, '1') - 1; + $j+= 26 * $i; + break; + } + } + // at this point, 2^$j * $n/(2^$j) == $n + + $mod1 = $n->copy(); + $mod1->_rshift($j); + $mod2 = new MathBigInteger(); + $mod2->value = array(1); + $mod2->_lshift($j); + + $part1 = ( $mod1->value != array(1) ) ? $this->_slidingWindow($e, $mod1, MATH_BIGINTEGER_MONTGOMERY) : new MathBigInteger(); + $part2 = $this->_slidingWindow($e, $mod2, MATH_BIGINTEGER_POWEROF2); + + $y1 = $mod2->modInverse($mod1); + $y2 = $mod1->modInverse($mod2); + + $result = $part1->multiply($mod2); + $result = $result->multiply($y1); + + $temp = $part2->multiply($mod1); + $temp = $temp->multiply($y2); + + $result = $result->add($temp); + list(, $result) = $result->divide($n); + + return $this->_normalize($result); + } + + /** + * Performs modular exponentiation. + * + * Alias for MathBigInteger::modPow() + * + * @param MathBigInteger $e + * @param MathBigInteger $n + * @return MathBigInteger + * @access public + */ + function powMod($e, $n) + { + return $this->modPow($e, $n); + } + + /** + * Sliding Window k-ary Modular Exponentiation + * + * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims, + * however, this function performs a modular reduction after every multiplication and squaring operation. + * As such, this function has the same preconditions that the reductions being used do. + * + * @param MathBigInteger $e + * @param MathBigInteger $n + * @param Integer $mode + * @return MathBigInteger + * @access private + */ + function _slidingWindow($e, $n, $mode) + { + static $window_ranges = array(7, 25, 81, 241, 673, 1793); // from BigInteger.java's oddModPow function + //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1 + + $e_value = $e->value; + $e_length = count($e_value) - 1; + $e_bits = decbin($e_value[$e_length]); + for ($i = $e_length - 1; $i >= 0; --$i) { + $e_bits.= str_pad(decbin($e_value[$i]), MATH_BIGINTEGER_BASE, '0', STR_PAD_LEFT); + } + + $e_length = strlen($e_bits); + + // calculate the appropriate window size. + // $window_size == 3 if $window_ranges is between 25 and 81, for example. + for ($i = 0, $window_size = 1; $e_length > $window_ranges[$i] && $i < count($window_ranges); ++$window_size, ++$i) { + } + + $n_value = $n->value; + + // precompute $this^0 through $this^$window_size + $powers = array(); + $powers[1] = $this->_prepareReduce($this->value, $n_value, $mode); + $powers[2] = $this->_squareReduce($powers[1], $n_value, $mode); + + // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end + // in a 1. ie. it's supposed to be odd. + $temp = 1 << ($window_size - 1); + for ($i = 1; $i < $temp; ++$i) { + $i2 = $i << 1; + $powers[$i2 + 1] = $this->_multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $mode); + } + + $result = array(1); + $result = $this->_prepareReduce($result, $n_value, $mode); + + for ($i = 0; $i < $e_length;) { + if (!$e_bits[$i]) { + $result = $this->_squareReduce($result, $n_value, $mode); + ++$i; + } else { + for ($j = $window_size - 1; $j > 0; --$j) { + if (!empty($e_bits[$i + $j])) { + break; + } + } + + for ($k = 0; $k <= $j; ++$k) {// eg. the length of substr($e_bits, $i, $j+1) + $result = $this->_squareReduce($result, $n_value, $mode); + } + + $result = $this->_multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $mode); + + $i+=$j + 1; + } + } + + $temp = new MathBigInteger(); + $temp->value = $this->_reduce($result, $n_value, $mode); + + return $temp; + } + + /** + * Modular reduction + * + * For most $modes this will return the remainder. + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _reduce($x, $n, $mode) + { + switch ($mode) { + case MATH_BIGINTEGER_MONTGOMERY: + return $this->_montgomery($x, $n); + case MATH_BIGINTEGER_BARRETT: + return $this->_barrett($x, $n); + case MATH_BIGINTEGER_POWEROF2: + $lhs = new MathBigInteger(); + $lhs->value = $x; + $rhs = new MathBigInteger(); + $rhs->value = $n; + return $x->_mod2($n); + case MATH_BIGINTEGER_CLASSIC: + $lhs = new MathBigInteger(); + $lhs->value = $x; + $rhs = new MathBigInteger(); + $rhs->value = $n; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + case MATH_BIGINTEGER_NONE: + return $x; + default: + // an invalid $mode was provided + } + } + + /** + * Modular reduction preperation + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _prepareReduce($x, $n, $mode) + { + if ($mode == MATH_BIGINTEGER_MONTGOMERY) { + return $this->_prepMontgomery($x, $n); + } + return $this->_reduce($x, $n, $mode); + } + + /** + * Modular multiply + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $y + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _multiplyReduce($x, $y, $n, $mode) + { + if ($mode == MATH_BIGINTEGER_MONTGOMERY) { + return $this->_montgomeryMultiply($x, $y, $n); + } + $temp = $this->_multiply($x, false, $y, false); + return $this->_reduce($temp[MATH_BIGINTEGER_VALUE], $n, $mode); + } + + /** + * Modular square + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _squareReduce($x, $n, $mode) + { + if ($mode == MATH_BIGINTEGER_MONTGOMERY) { + return $this->_montgomeryMultiply($x, $x, $n); + } + return $this->_reduce($this->_square($x), $n, $mode); + } + + /** + * Modulos for Powers of Two + * + * Calculates $x%$n, where $n = 2**$e, for some $e. Since this is basically the same as doing $x & ($n-1), + * we'll just use this function as a wrapper for doing that. + * + * @see _slidingWindow() + * @access private + * @param MathBigInteger + * @return MathBigInteger + */ + function _mod2($n) + { + $temp = new MathBigInteger(); + $temp->value = array(1); + return $this->bitwise_and($n->subtract($temp)); + } + + /** + * Barrett Modular Reduction + * + * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly, + * so as not to require negative numbers (initially, this script didn't support negative numbers). + * + * Employs "folding", as described at + * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}. To quote from + * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x." + * + * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that + * usable on account of (1) its not using reasonable radix points as discussed in + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable + * radix points, it only works when there are an even number of digits in the denominator. The reason for (2) is that + * (x >> 1) + (x >> 1) != x / 2 + x / 2. If x is even, they're the same, but if x is odd, they're not. See the in-line + * comments for details. + * + * @see _slidingWindow() + * @access private + * @param Array $n + * @param Array $m + * @return Array + */ + function _barrett($n, $m) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + $m_length = count($m); + + // if ($this->_compare($n, $this->_square($m)) >= 0) { + if (count($n) > 2 * $m_length) { + $lhs = new MathBigInteger(); + $rhs = new MathBigInteger(); + $lhs->value = $n; + $rhs->value = $m; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced + if ($m_length < 5) { + return $this->_regularBarrett($n, $m); + } + + // n = 2 * m.length + + if (($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $m; + + $lhs = new MathBigInteger(); + $lhs_value = &$lhs->value; + $lhs_value = $this->_array_repeat(0, $m_length + ($m_length >> 1)); + $lhs_value[] = 1; + $rhs = new MathBigInteger(); + $rhs->value = $m; + + list($u, $m1) = $lhs->divide($rhs); + $u = $u->value; + $m1 = $m1->value; + + $cache[MATH_BIGINTEGER_DATA][] = array( + 'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1) + 'm1'=> $m1 // m.length + ); + } else { + extract($cache[MATH_BIGINTEGER_DATA][$key]); + } + + $cutoff = $m_length + ($m_length >> 1); + $lsd = array_slice($n, 0, $cutoff); // m.length + (m.length >> 1) + $msd = array_slice($n, $cutoff); // m.length >> 1 + $lsd = $this->_trim($lsd); + $temp = $this->_multiply($msd, false, $m1, false); + $n = $this->_add($lsd, false, $temp[MATH_BIGINTEGER_VALUE], false); // m.length + (m.length >> 1) + 1 + + if ($m_length & 1) { + return $this->_regularBarrett($n[MATH_BIGINTEGER_VALUE], $m); + } + + // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2 + $temp = array_slice($n[MATH_BIGINTEGER_VALUE], $m_length - 1); + // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2 + // if odd: ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1 + $temp = $this->_multiply($temp, false, $u, false); + // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1 + // if odd: (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], ($m_length >> 1) + 1); + // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1 + // if odd: (m.length - (m.length >> 1)) + m.length = 2 * m.length - (m.length >> 1) + $temp = $this->_multiply($temp, false, $m, false); + + // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit + // number from a m.length + (m.length >> 1) + 1 digit number. ie. there'd be an extra digit and the while loop + // following this comment would loop a lot (hence our calling _regularBarrett() in that situation). + + $result = $this->_subtract($n[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false); + + while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false) >= 0) { + $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false); + } + + return $result[MATH_BIGINTEGER_VALUE]; + } + + /** + * (Regular) Barrett Modular Reduction + * + * For numbers with more than four digits MathBigInteger::_barrett() is faster. The difference between that and this + * is that this function does not fold the denominator into a smaller form. + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @return Array + */ + function _regularBarrett($x, $n) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + $n_length = count($n); + + if (count($x) > 2 * $n_length) { + $lhs = new MathBigInteger(); + $rhs = new MathBigInteger(); + $lhs->value = $x; + $rhs->value = $n; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + if (($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $n; + $lhs = new MathBigInteger(); + $lhs_value = &$lhs->value; + $lhs_value = $this->_array_repeat(0, 2 * $n_length); + $lhs_value[] = 1; + $rhs = new MathBigInteger(); + $rhs->value = $n; + list($temp, ) = $lhs->divide($rhs); // m.length + $cache[MATH_BIGINTEGER_DATA][] = $temp->value; + } + + // 2 * m.length - (m.length - 1) = m.length + 1 + $temp = array_slice($x, $n_length - 1); + // (m.length + 1) + m.length = 2 * m.length + 1 + $temp = $this->_multiply($temp, false, $cache[MATH_BIGINTEGER_DATA][$key], false); + // (2 * m.length + 1) - (m.length - 1) = m.length + 2 + $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], $n_length + 1); + + // m.length + 1 + $result = array_slice($x, 0, $n_length + 1); + // m.length + 1 + $temp = $this->_multiplyLower($temp, false, $n, false, $n_length + 1); + // $temp == array_slice($temp->_multiply($temp, false, $n, false)->value, 0, $n_length + 1) + + if ($this->_compare($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]) < 0) { + $corrector_value = $this->_array_repeat(0, $n_length + 1); + $corrector_value[count($corrector_value)] = 1; + $result = $this->_add($result, false, $corrector_value, false); + $result = $result[MATH_BIGINTEGER_VALUE]; + } + + // at this point, we're subtracting a number with m.length + 1 digits from another number with m.length + 1 digits + $result = $this->_subtract($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]); + while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false) > 0) { + $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false); + } + + return $result[MATH_BIGINTEGER_VALUE]; + } + + /** + * Performs long multiplication up to $stop digits + * + * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved. + * + * @see _regularBarrett() + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @param Integer $stop + * @return Array + * @access private + */ + function _multiplyLower($x_value, $x_negative, $y_value, $y_negative, $stop) + { + $x_length = count($x_value); + $y_length = count($y_value); + + if (!$x_length || !$y_length) { // a 0 is being multiplied + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + if ($x_length < $y_length) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_length = count($x_value); + $y_length = count($y_value); + } + + $product_value = $this->_array_repeat(0, $x_length + $y_length); + + // the following for loop could be removed if the for loop following it + // (the one with nested for loops) initially set $i to 0, but + // doing so would also make the result in one set of unnecessary adds, + // since on the outermost loops first pass, $product->value[$k] is going + // to always be 0 + + $carry = 0; + + for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0, $k = $i + $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 + $carry = MATH_BIGINTEGER_BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $product_value[$j] = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * $carry); + } + + if ($j < $stop) { + $product_value[$j] = $carry; + } + + // the above for loop is what the previous comment was talking about. the + // following for loop is the "one with nested for loops" + + for ($i = 1; $i < $y_length; ++$i) { + $carry = 0; + + for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k) { + $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; + $carry = MATH_BIGINTEGER_BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $product_value[$k] = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * $carry); + } + + if ($k < $stop) { + $product_value[$k] = $carry; + } + } + + return array( + MATH_BIGINTEGER_VALUE => $this->_trim($product_value), + MATH_BIGINTEGER_SIGN => $x_negative != $y_negative + ); + } + + /** + * Montgomery Modular Reduction + * + * ($x->_prepMontgomery($n))->_montgomery($n) yields $x % $n. + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=170 MPM 6.3} provides insights on how this can be + * improved upon (basically, by using the comba method). gcd($n, 2) must be equal to one for this function + * to work correctly. + * + * @see _prepMontgomery() + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @return Array + */ + function _montgomery($x, $n) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + if (($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $x; + $cache[MATH_BIGINTEGER_DATA][] = $this->_modInverse67108864($n); + } + + $k = count($n); + + $result = array(MATH_BIGINTEGER_VALUE => $x); + + for ($i = 0; $i < $k; ++$i) { + $temp = $result[MATH_BIGINTEGER_VALUE][$i] * $cache[MATH_BIGINTEGER_DATA][$key]; + $temp = $temp - MATH_BIGINTEGER_BASE_FULL * (MATH_BIGINTEGER_BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31)); + $temp = $this->_regularMultiply(array($temp), $n); + $temp = array_merge($this->_array_repeat(0, $i), $temp); + $result = $this->_add($result[MATH_BIGINTEGER_VALUE], false, $temp, false); + } + + $result[MATH_BIGINTEGER_VALUE] = array_slice($result[MATH_BIGINTEGER_VALUE], $k); + + if ($this->_compare($result, false, $n, false) >= 0) { + $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], false, $n, false); + } + + return $result[MATH_BIGINTEGER_VALUE]; + } + + /** + * Montgomery Multiply + * + * Interleaves the montgomery reduction and long multiplication algorithms together as described in + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36} + * + * @see _prepMontgomery() + * @see _montgomery() + * @access private + * @param Array $x + * @param Array $y + * @param Array $m + * @return Array + */ + function _montgomeryMultiply($x, $y, $m) + { + $temp = $this->_multiply($x, false, $y, false); + return $this->_montgomery($temp[MATH_BIGINTEGER_VALUE], $m); + + // the following code, although not callable, can be run independently of the above code + // although the above code performed better in my benchmarks the following could might + // perform better under different circumstances. in lieu of deleting it it's just been + // made uncallable + + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + if (($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $m; + $cache[MATH_BIGINTEGER_DATA][] = $this->_modInverse67108864($m); + } + + $n = max(count($x), count($y), count($m)); + $x = array_pad($x, $n, 0); + $y = array_pad($y, $n, 0); + $m = array_pad($m, $n, 0); + $a = array(MATH_BIGINTEGER_VALUE => $this->_array_repeat(0, $n + 1)); + for ($i = 0; $i < $n; ++$i) { + $temp = $a[MATH_BIGINTEGER_VALUE][0] + $x[$i] * $y[0]; + $temp = $temp - MATH_BIGINTEGER_BASE_FULL * (MATH_BIGINTEGER_BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31)); + $temp = $temp * $cache[MATH_BIGINTEGER_DATA][$key]; + $temp = $temp - MATH_BIGINTEGER_BASE_FULL * (MATH_BIGINTEGER_BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31)); + $temp = $this->_add($this->_regularMultiply(array($x[$i]), $y), false, $this->_regularMultiply(array($temp), $m), false); + $a = $this->_add($a[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false); + $a[MATH_BIGINTEGER_VALUE] = array_slice($a[MATH_BIGINTEGER_VALUE], 1); + } + if ($this->_compare($a[MATH_BIGINTEGER_VALUE], false, $m, false) >= 0) { + $a = $this->_subtract($a[MATH_BIGINTEGER_VALUE], false, $m, false); + } + return $a[MATH_BIGINTEGER_VALUE]; + } + + /** + * Prepare a number for use in Montgomery Modular Reductions + * + * @see _montgomery() + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @return Array + */ + function _prepMontgomery($x, $n) + { + $lhs = new MathBigInteger(); + $lhs->value = array_merge($this->_array_repeat(0, count($n)), $x); + $rhs = new MathBigInteger(); + $rhs->value = $n; + + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + /** + * Modular Inverse of a number mod 2**26 (eg. 67108864) + * + * Based off of the bnpInvDigit function implemented and justified in the following URL: + * + * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js} + * + * The following URL provides more info: + * + * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85} + * + * As for why we do all the bitmasking... strange things can happen when converting from floats to ints. For + * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields + * int(-2147483648). To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't + * auto-converted to floats. The outermost bitmask is present because without it, there's no guarantee that + * the "residue" returned would be the so-called "common residue". We use fmod, in the last step, because the + * maximum possible $x is 26 bits and the maximum $result is 16 bits. Thus, we have to be able to handle up to + * 40 bits, which only 64-bit floating points will support. + * + * Thanks to Pedro Gimeno Fortea for input! + * + * @see _montgomery() + * @access private + * @param Array $x + * @return Integer + */ + function _modInverse67108864($x) // 2**26 == 67,108,864 + { + $x = -$x[0]; + $result = $x & 0x3; // x**-1 mod 2**2 + $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4 + $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8 + $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16 + $result = fmod($result * (2 - fmod($x * $result, MATH_BIGINTEGER_BASE_FULL)), MATH_BIGINTEGER_BASE_FULL); // x**-1 mod 2**26 + return $result & MATH_BIGINTEGER_MAX_DIGIT; + } + + /** + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * + * Here's an example: + * + * modInverse($b); + * echo $c->toString(); // outputs 4 + * + * echo "\r\n"; + * + * $d = $a->multiply($c); + * list(, $d) = $d->divide($b); + * echo $d; // outputs 1 (as per the definition of modular inverse) + * ?> + * + * + * @param MathBigInteger $n + * @return mixed false, if no modular inverse exists, MathBigInteger, otherwise. + * @access public + * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information. + */ + function modInverse($n) + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new MathBigInteger(); + $temp->value = gmp_invert($this->value, $n->value); + + return ( $temp->value === false ) ? false : $this->_normalize($temp); + } + + static $zero, $one; + if (!isset($zero)) { + $zero = new MathBigInteger(); + $one = new MathBigInteger(1); + } + + // $x mod -$n == $x mod $n. + $n = $n->abs(); + + if ($this->compare($zero) < 0) { + $temp = $this->abs(); + $temp = $temp->modInverse($n); + return $this->_normalize($n->subtract($temp)); + } + + extract($this->extendedGCD($n)); + + if (!$gcd->equals($one)) { + return false; + } + + $x = $x->compare($zero) < 0 ? $x->add($n) : $x; + + return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x); + } + + /** + * Calculates the greatest common divisor and Bezout's identity. + * + * Say you have 693 and 609. The GCD is 21. Bezout's identity states that there exist integers x and y such that + * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which + * combination is returned is dependant upon which mode is in use. See + * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bezout's identity - Wikipedia} for more information. + * + * Here's an example: + * + * extendedGCD($b)); + * + * echo $gcd->toString() . "\r\n"; // outputs 21 + * echo $a->toString() * $x->toString() + $b->toString() * $y->toString(); // outputs 21 + * ?> + * + * + * @param MathBigInteger $n + * @return MathBigInteger + * @access public + * @internal Calculates the GCD using the binary xGCD algorithim described in + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=19 HAC 14.61}. As the text above 14.61 notes, + * the more traditional algorithim requires "relatively costly multiple-precision divisions". + */ + function extendedGCD($n) + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + extract(gmp_gcdext($this->value, $n->value)); + + return array( + 'gcd' => $this->_normalize(new MathBigInteger($g)), + 'x' => $this->_normalize(new MathBigInteger($s)), + 'y' => $this->_normalize(new MathBigInteger($t)) + ); + case MATH_BIGINTEGER_MODE_BCMATH: + // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works + // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway. as is, + // the basic extended euclidean algorithim is what we're using. + + $u = $this->value; + $v = $n->value; + + $a = '1'; + $b = '0'; + $c = '0'; + $d = '1'; + + while (bccomp($v, '0', 0) != 0) { + $q = bcdiv($u, $v, 0); + + $temp = $u; + $u = $v; + $v = bcsub($temp, bcmul($v, $q, 0), 0); + + $temp = $a; + $a = $c; + $c = bcsub($temp, bcmul($a, $q, 0), 0); + + $temp = $b; + $b = $d; + $d = bcsub($temp, bcmul($b, $q, 0), 0); + } + + return array( + 'gcd' => $this->_normalize(new MathBigInteger($u)), + 'x' => $this->_normalize(new MathBigInteger($a)), + 'y' => $this->_normalize(new MathBigInteger($b)) + ); + } + + $y = $n->copy(); + $x = $this->copy(); + $g = new MathBigInteger(); + $g->value = array(1); + + while (!(($x->value[0] & 1)|| ($y->value[0] & 1))) { + $x->_rshift(1); + $y->_rshift(1); + $g->_lshift(1); + } + + $u = $x->copy(); + $v = $y->copy(); + + $a = new MathBigInteger(); + $b = new MathBigInteger(); + $c = new MathBigInteger(); + $d = new MathBigInteger(); + + $a->value = $d->value = $g->value = array(1); + $b->value = $c->value = array(); + + while (!empty($u->value)) { + while (!($u->value[0] & 1)) { + $u->_rshift(1); + if ((!empty($a->value) && ($a->value[0] & 1)) || (!empty($b->value) && ($b->value[0] & 1))) { + $a = $a->add($y); + $b = $b->subtract($x); + } + $a->_rshift(1); + $b->_rshift(1); + } + + while (!($v->value[0] & 1)) { + $v->_rshift(1); + if ((!empty($d->value) && ($d->value[0] & 1)) || (!empty($c->value) && ($c->value[0] & 1))) { + $c = $c->add($y); + $d = $d->subtract($x); + } + $c->_rshift(1); + $d->_rshift(1); + } + + if ($u->compare($v) >= 0) { + $u = $u->subtract($v); + $a = $a->subtract($c); + $b = $b->subtract($d); + } else { + $v = $v->subtract($u); + $c = $c->subtract($a); + $d = $d->subtract($b); + } + } + + return array( + 'gcd' => $this->_normalize($g->multiply($v)), + 'x' => $this->_normalize($c), + 'y' => $this->_normalize($d) + ); + } + + /** + * Calculates the greatest common divisor + * + * Say you have 693 and 609. The GCD is 21. + * + * Here's an example: + * + * extendedGCD($b); + * + * echo $gcd->toString() . "\r\n"; // outputs 21 + * ?> + * + * + * @param MathBigInteger $n + * @return MathBigInteger + * @access public + */ + function gcd($n) + { + extract($this->extendedGCD($n)); + return $gcd; + } + + /** + * Absolute value. + * + * @return MathBigInteger + * @access public + */ + function abs() + { + $temp = new MathBigInteger(); + + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + $temp->value = gmp_abs($this->value); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = (bccomp($this->value, '0', 0) < 0) ? substr($this->value, 1) : $this->value; + break; + default: + $temp->value = $this->value; + } + + return $temp; + } + + /** + * Compares two numbers. + * + * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is + * demonstrated thusly: + * + * $x > $y: $x->compare($y) > 0 + * $x < $y: $x->compare($y) < 0 + * $x == $y: $x->compare($y) == 0 + * + * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). + * + * @param MathBigInteger $y + * @return Integer < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal. + * @access public + * @see equals() + * @internal Could return $this->subtract($x), but that's not as fast as what we do do. + */ + function compare($y) + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_cmp($this->value, $y->value); + case MATH_BIGINTEGER_MODE_BCMATH: + return bccomp($this->value, $y->value, 0); + } + + return $this->_compare($this->value, $this->is_negative, $y->value, $y->is_negative); + } + + /** + * Compares two numbers. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Integer + * @see compare() + * @access private + */ + function _compare($x_value, $x_negative, $y_value, $y_negative) + { + if ($x_negative != $y_negative) { + return ( !$x_negative && $y_negative ) ? 1 : -1; + } + + $result = $x_negative ? -1 : 1; + + if (count($x_value) != count($y_value)) { + return ( count($x_value) > count($y_value) ) ? $result : -$result; + } + $size = max(count($x_value), count($y_value)); + + $x_value = array_pad($x_value, $size, 0); + $y_value = array_pad($y_value, $size, 0); + + for ($i = count($x_value) - 1; $i >= 0; --$i) { + if ($x_value[$i] != $y_value[$i]) { + return ( $x_value[$i] > $y_value[$i] ) ? $result : -$result; + } + } + + return 0; + } + + /** + * Tests the equality of two numbers. + * + * If you need to see if one number is greater than or less than another number, use MathBigInteger::compare() + * + * @param MathBigInteger $x + * @return Boolean + * @access public + * @see compare() + */ + function equals($x) + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_cmp($this->value, $x->value) == 0; + default: + return $this->value === $x->value && $this->is_negative == $x->is_negative; + } + } + + /** + * Set Precision + * + * Some bitwise operations give different results depending on the precision being used. Examples include left + * shift, not, and rotates. + * + * @param Integer $bits + * @access public + */ + function setPrecision($bits) + { + $this->precision = $bits; + if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH) { + $this->bitmask = new MathBigInteger(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256); + } else { + $this->bitmask = new MathBigInteger(bcpow('2', $bits, 0)); + } + + $temp = $this->_normalize($this); + $this->value = $temp->value; + } + + /** + * Logical And + * + * @param MathBigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return MathBigInteger + */ + function bitwise_and($x) + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new MathBigInteger(); + $temp->value = gmp_and($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new MathBigInteger($left & $right, 256)); + } + + $result = $this->copy(); + + $length = min(count($x->value), count($this->value)); + + $result->value = array_slice($result->value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $result->value[$i]&= $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Or + * + * @param MathBigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return MathBigInteger + */ + function bitwise_or($x) + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new MathBigInteger(); + $temp->value = gmp_or($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new MathBigInteger($left | $right, 256)); + } + + $length = max(count($this->value), count($x->value)); + $result = $this->copy(); + $result->value = array_pad($result->value, $length, 0); + $x->value = array_pad($x->value, $length, 0); + + for ($i = 0; $i < $length; ++$i) { + $result->value[$i]|= $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Exclusive-Or + * + * @param MathBigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return MathBigInteger + */ + function bitwise_xor($x) + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new MathBigInteger(); + $temp->value = gmp_xor($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new MathBigInteger($left ^ $right, 256)); + } + + $length = max(count($this->value), count($x->value)); + $result = $this->copy(); + $result->value = array_pad($result->value, $length, 0); + $x->value = array_pad($x->value, $length, 0); + + for ($i = 0; $i < $length; ++$i) { + $result->value[$i]^= $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Not + * + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return MathBigInteger + */ + function bitwise_not() + { + // calculuate "not" without regard to $this->precision + // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0) + $temp = $this->toBytes(); + $pre_msb = decbin(ord($temp[0])); + $temp = ~$temp; + $msb = decbin(ord($temp[0])); + if (strlen($msb) == 8) { + $msb = substr($msb, strpos($msb, '0')); + } + $temp[0] = chr(bindec($msb)); + + // see if we need to add extra leading 1's + $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8; + $new_bits = $this->precision - $current_bits; + if ($new_bits <= 0) { + return $this->_normalize(new MathBigInteger($temp, 256)); + } + + // generate as many leading 1's as we need to. + $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3); + $this->_base256_lshift($leading_ones, $current_bits); + + $temp = str_pad($temp, strlen($leading_ones), chr(0), STR_PAD_LEFT); + + return $this->_normalize(new MathBigInteger($leading_ones | $temp, 256)); + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift. + * + * @param Integer $shift + * @return MathBigInteger + * @access public + * @internal The only version that yields any speed increases is the internal version. + */ + function bitwise_rightShift($shift) + { + $temp = new MathBigInteger(); + + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + static $two; + + if (!isset($two)) { + $two = gmp_init('2'); + } + + $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift)); + + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0); + + break; + default: // could just replace _lshift with this, but then all _lshift() calls would need to be rewritten + // and I don't want to do that... + $temp->value = $this->value; + $temp->_rshift($shift); + } + + return $this->_normalize($temp); + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift. + * + * @param Integer $shift + * @return MathBigInteger + * @access public + * @internal The only version that yields any speed increases is the internal version. + */ + function bitwise_leftShift($shift) + { + $temp = new MathBigInteger(); + + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + static $two; + + if (!isset($two)) { + $two = gmp_init('2'); + } + + $temp->value = gmp_mul($this->value, gmp_pow($two, $shift)); + + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0); + + break; + default: // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten + // and I don't want to do that... + $temp->value = $this->value; + $temp->_lshift($shift); + } + + return $this->_normalize($temp); + } + + /** + * Logical Left Rotate + * + * Instead of the top x bits being dropped they're appended to the shifted bit string. + * + * @param Integer $shift + * @return MathBigInteger + * @access public + */ + function bitwise_leftRotate($shift) + { + $bits = $this->toBytes(); + + if ($this->precision > 0) { + $precision = $this->precision; + if (MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH) { + $mask = $this->bitmask->subtract(new MathBigInteger(1)); + $mask = $mask->toBytes(); + } else { + $mask = $this->bitmask->toBytes(); + } + } else { + $temp = ord($bits[0]); + for ($i = 0; $temp >> $i; ++$i) { + } + $precision = 8 * strlen($bits) - 8 + $i; + $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3); + } + + if ($shift < 0) { + $shift+= $precision; + } + $shift%= $precision; + + if (!$shift) { + return $this->copy(); + } + + $left = $this->bitwise_leftShift($shift); + $left = $left->bitwise_and(new MathBigInteger($mask, 256)); + $right = $this->bitwise_rightShift($precision - $shift); + $result = MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right); + return $this->_normalize($result); + } + + /** + * Logical Right Rotate + * + * Instead of the bottom x bits being dropped they're prepended to the shifted bit string. + * + * @param Integer $shift + * @return MathBigInteger + * @access public + */ + function bitwise_rightRotate($shift) + { + return $this->bitwise_leftRotate(-$shift); + } + + /** + * Set random number generator function + * + * This function is deprecated. + * + * @param String $generator + * @access public + */ + function setRandomGenerator($generator) + { + } + + /** + * Generates a random BigInteger + * + * Byte length is equal to $length. Uses crypt_random if it's loaded and mt_rand if it's not. + * + * @param Integer $length + * @return MathBigInteger + * @access private + */ + function _random_number_helper($size) + { + if (function_exists('crypt_random_string')) { + $random = crypt_random_string($size); + } else { + $random = ''; + + if ($size & 1) { + $random.= chr(mt_rand(0, 255)); + } + + $blocks = $size >> 1; + for ($i = 0; $i < $blocks; ++$i) { + // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems + $random.= pack('n', mt_rand(0, 0xFFFF)); + } + } + + return new MathBigInteger($random, 256); + } + + /** + * Generate a random number + * + * Returns a random number between $min and $max where $min and $max + * can be defined using one of the two methods: + * + * $min->random($max) + * $max->random($min) + * + * @param MathBigInteger $arg1 + * @param optional MathBigInteger $arg2 + * @return MathBigInteger + * @access public + * @internal The API for creating random numbers used to be $a->random($min, $max), where $a was a MathBigInteger object. + * That method is still supported for BC purposes. + */ + function random($arg1, $arg2 = false) + { + if ($arg1 === false) { + return false; + } + + if ($arg2 === false) { + $max = $arg1; + $min = $this; + } else { + $min = $arg1; + $max = $arg2; + } + + $compare = $max->compare($min); + + if (!$compare) { + return $this->_normalize($min); + } elseif ($compare < 0) { + // if $min is bigger then $max, swap $min and $max + $temp = $max; + $max = $min; + $min = $temp; + } + + static $one; + if (!isset($one)) { + $one = new MathBigInteger(1); + } + + $max = $max->subtract($min->subtract($one)); + $size = strlen(ltrim($max->toBytes(), chr(0))); + + /* + doing $random % $max doesn't work because some numbers will be more likely to occur than others. + eg. if $max is 140 and $random's max is 255 then that'd mean both $random = 5 and $random = 145 + would produce 5 whereas the only value of random that could produce 139 would be 139. ie. + not all numbers would be equally likely. some would be more likely than others. + + creating a whole new random number until you find one that is within the range doesn't work + because, for sufficiently small ranges, the likelihood that you'd get a number within that range + would be pretty small. eg. with $random's max being 255 and if your $max being 1 the probability + would be pretty high that $random would be greater than $max. + + phpseclib works around this using the technique described here: + + http://crypto.stackexchange.com/questions/5708/creating-a-small-number-from-a-cryptographically-secure-random-string + */ + $random_max = new MathBigInteger(chr(1) . str_repeat("\0", $size), 256); + $random = $this->_random_number_helper($size); + + list($max_multiple) = $random_max->divide($max); + $max_multiple = $max_multiple->multiply($max); + + while ($random->compare($max_multiple) >= 0) { + $random = $random->subtract($max_multiple); + $random_max = $random_max->subtract($max_multiple); + $random = $random->bitwise_leftShift(8); + $random = $random->add($this->_random_number_helper(1)); + $random_max = $random_max->bitwise_leftShift(8); + list($max_multiple) = $random_max->divide($max); + $max_multiple = $max_multiple->multiply($max); + } + list(, $random) = $random->divide($max); + + return $this->_normalize($random->add($min)); + } + + /** + * Generate a random prime number. + * + * If there's not a prime within the given range, false will be returned. If more than $timeout seconds have elapsed, + * give up and return false. + * + * @param MathBigInteger $arg1 + * @param optional MathBigInteger $arg2 + * @param optional Integer $timeout + * @return Mixed + * @access public + * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}. + */ + function randomPrime($arg1, $arg2 = false, $timeout = false) + { + if ($arg1 === false) { + return false; + } + + if ($arg2 === false) { + $max = $arg1; + $min = $this; + } else { + $min = $arg1; + $max = $arg2; + } + + $compare = $max->compare($min); + + if (!$compare) { + return $min->isPrime() ? $min : false; + } elseif ($compare < 0) { + // if $min is bigger then $max, swap $min and $max + $temp = $max; + $max = $min; + $min = $temp; + } + + static $one, $two; + if (!isset($one)) { + $one = new MathBigInteger(1); + $two = new MathBigInteger(2); + } + + $start = time(); + + $x = $this->random($min, $max); + + // gmp_nextprime() requires PHP 5 >= 5.2.0 per . + if (MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP && function_exists('gmp_nextprime')) { + $p = new MathBigInteger(); + $p->value = gmp_nextprime($x->value); + + if ($p->compare($max) <= 0) { + return $p; + } + + if (!$min->equals($x)) { + $x = $x->subtract($one); + } + + return $x->randomPrime($min, $x); + } + + if ($x->equals($two)) { + return $x; + } + + $x->_make_odd(); + if ($x->compare($max) > 0) { + // if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range + if ($min->equals($max)) { + return false; + } + $x = $min->copy(); + $x->_make_odd(); + } + + $initial_x = $x->copy(); + + while (true) { + if ($timeout !== false && time() - $start > $timeout) { + return false; + } + + if ($x->isPrime()) { + return $x; + } + + $x = $x->add($two); + + if ($x->compare($max) > 0) { + $x = $min->copy(); + if ($x->equals($two)) { + return $x; + } + $x->_make_odd(); + } + + if ($x->equals($initial_x)) { + return false; + } + } + } + + /** + * Make the current number odd + * + * If the current number is odd it'll be unchanged. If it's even, one will be added to it. + * + * @see randomPrime() + * @access private + */ + function _make_odd() + { + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + gmp_setbit($this->value, 0); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value[strlen($this->value) - 1] % 2 == 0) { + $this->value = bcadd($this->value, '1'); + } + break; + default: + $this->value[0] |= 1; + } + } + + /** + * Checks a numer to see if it's prime + * + * Assuming the $t parameter is not set, this function has an error rate of 2**-80. The main motivation for the + * $t parameter is distributability. MathBigInteger::randomPrime() can be distributed across multiple pageloads + * on a website instead of just one. + * + * @param optional MathBigInteger $t + * @return Boolean + * @access public + * @internal Uses the + * {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}. See + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24}. + */ + function isPrime($t = false) + { + $length = strlen($this->toBytes()); + + if (!$t) { + // see HAC 4.49 "Note (controlling the error probability)" + // @codingStandardsIgnoreStart + if ($length >= 163) { $t = 2; } // floor(1300 / 8) + else if ($length >= 106) { $t = 3; } // floor( 850 / 8) + else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8) + else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8) + else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8) + else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8) + else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8) + else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8) + else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8) + else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8) + else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8) + else { $t = 27; } + // @codingStandardsIgnoreEnd + } + + // ie. gmp_testbit($this, 0) + // ie. isEven() or !isOdd() + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_prob_prime($this->value, $t) != 0; + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '2') { + return true; + } + if ($this->value[strlen($this->value) - 1] % 2 == 0) { + return false; + } + break; + default: + if ($this->value == array(2)) { + return true; + } + if (~$this->value[0] & 1) { + return false; + } + } + + static $primes, $zero, $one, $two; + + if (!isset($primes)) { + $primes = array( + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, + 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, + 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, + 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, + 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, + 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, + 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, + 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, + 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, + 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, + 953, 967, 971, 977, 983, 991, 997 + ); + + if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL) { + for ($i = 0; $i < count($primes); ++$i) { + $primes[$i] = new MathBigInteger($primes[$i]); + } + } + + $zero = new MathBigInteger(); + $one = new MathBigInteger(1); + $two = new MathBigInteger(2); + } + + if ($this->equals($one)) { + return false; + } + + // see HAC 4.4.1 "Random search for probable primes" + if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL) { + foreach ($primes as $prime) { + list(, $r) = $this->divide($prime); + if ($r->equals($zero)) { + return $this->equals($prime); + } + } + } else { + $value = $this->value; + foreach ($primes as $prime) { + list(, $r) = $this->_divide_digit($value, $prime); + if (!$r) { + return count($value) == 1 && $value[0] == $prime; + } + } + } + + $n = $this->copy(); + $n_1 = $n->subtract($one); + $n_2 = $n->subtract($two); + + $r = $n_1->copy(); + $r_value = $r->value; + // ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); + if (MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH) { + $s = 0; + // if $n was 1, $r would be 0 and this would be an infinite loop, hence our $this->equals($one) check earlier + while ($r->value[strlen($r->value) - 1] % 2 == 0) { + $r->value = bcdiv($r->value, '2', 0); + ++$s; + } + } else { + for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i) { + $temp = ~$r_value[$i] & 0xFFFFFF; + for ($j = 1; ($temp >> $j) & 1; ++$j) { + } + if ($j != 25) { + break; + } + } + $s = 26 * $i + $j - 1; + $r->_rshift($s); + } + + for ($i = 0; $i < $t; ++$i) { + $a = $this->random($two, $n_2); + $y = $a->modPow($r, $n); + + if (!$y->equals($one) && !$y->equals($n_1)) { + for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) { + $y = $y->modPow($two, $n); + if ($y->equals($one)) { + return false; + } + } + + if (!$y->equals($n_1)) { + return false; + } + } + } + return true; + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits. + * + * @param Integer $shift + * @access private + */ + function _lshift($shift) + { + if ($shift == 0) { + return; + } + + $num_digits = (int) ($shift / MATH_BIGINTEGER_BASE); + $shift %= MATH_BIGINTEGER_BASE; + $shift = 1 << $shift; + + $carry = 0; + + for ($i = 0; $i < count($this->value); ++$i) { + $temp = $this->value[$i] * $shift + $carry; + $carry = MATH_BIGINTEGER_BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31); + $this->value[$i] = (int) ($temp - $carry * MATH_BIGINTEGER_BASE_FULL); + } + + if ($carry) { + $this->value[count($this->value)] = $carry; + } + + while ($num_digits--) { + array_unshift($this->value, 0); + } + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits. + * + * @param Integer $shift + * @access private + */ + function _rshift($shift) + { + if ($shift == 0) { + return; + } + + $num_digits = (int) ($shift / MATH_BIGINTEGER_BASE); + $shift %= MATH_BIGINTEGER_BASE; + $carry_shift = MATH_BIGINTEGER_BASE - $shift; + $carry_mask = (1 << $shift) - 1; + + if ($num_digits) { + $this->value = array_slice($this->value, $num_digits); + } + + $carry = 0; + + for ($i = count($this->value) - 1; $i >= 0; --$i) { + $temp = $this->value[$i] >> $shift | $carry; + $carry = ($this->value[$i] & $carry_mask) << $carry_shift; + $this->value[$i] = $temp; + } + + $this->value = $this->_trim($this->value); + } + + /** + * Normalize + * + * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision + * + * @param MathBigInteger + * @return MathBigInteger + * @see _trim() + * @access private + */ + function _normalize($result) + { + $result->precision = $this->precision; + $result->bitmask = $this->bitmask; + + switch (MATH_BIGINTEGER_MODE) { + case MATH_BIGINTEGER_MODE_GMP: + if (!empty($result->bitmask->value)) { + $result->value = gmp_and($result->value, $result->bitmask->value); + } + + return $result; + case MATH_BIGINTEGER_MODE_BCMATH: + if (!empty($result->bitmask->value)) { + $result->value = bcmod($result->value, $result->bitmask->value); + } + + return $result; + } + + $value = &$result->value; + + if (!count($value)) { + return $result; + } + + $value = $this->_trim($value); + + if (!empty($result->bitmask->value)) { + $length = min(count($value), count($this->bitmask->value)); + $value = array_slice($value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $value[$i] = $value[$i] & $this->bitmask->value[$i]; + } + } + + return $result; + } + + /** + * Trim + * + * Removes leading zeros + * + * @param Array $value + * @return MathBigInteger + * @access private + */ + function _trim($value) + { + for ($i = count($value) - 1; $i >= 0; --$i) { + if ($value[$i]) { + break; + } + unset($value[$i]); + } + + return $value; + } + + /** + * Array Repeat + * + * @param $input Array + * @param $multiplier mixed + * @return Array + * @access private + */ + function _array_repeat($input, $multiplier) + { + return ($multiplier) ? array_fill(0, $multiplier, $input) : array(); + } + + /** + * Logical Left Shift + * + * Shifts binary strings $shift bits, essentially multiplying by 2**$shift. + * + * @param $x String + * @param $shift Integer + * @return String + * @access private + */ + function _base256_lshift(&$x, $shift) + { + if ($shift == 0) { + return; + } + + $num_bytes = $shift >> 3; // eg. floor($shift/8) + $shift &= 7; // eg. $shift % 8 + + $carry = 0; + for ($i = strlen($x) - 1; $i >= 0; --$i) { + $temp = ord($x[$i]) << $shift | $carry; + $x[$i] = chr($temp); + $carry = $temp >> 8; + } + $carry = ($carry != 0) ? chr($carry) : ''; + $x = $carry . $x . str_repeat(chr(0), $num_bytes); + } + + /** + * Logical Right Shift + * + * Shifts binary strings $shift bits, essentially dividing by 2**$shift and returning the remainder. + * + * @param $x String + * @param $shift Integer + * @return String + * @access private + */ + function _base256_rshift(&$x, $shift) + { + if ($shift == 0) { + $x = ltrim($x, chr(0)); + return ''; + } + + $num_bytes = $shift >> 3; // eg. floor($shift/8) + $shift &= 7; // eg. $shift % 8 + + $remainder = ''; + if ($num_bytes) { + $start = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes; + $remainder = substr($x, $start); + $x = substr($x, 0, -$num_bytes); + } + + $carry = 0; + $carry_shift = 8 - $shift; + for ($i = 0; $i < strlen($x); ++$i) { + $temp = (ord($x[$i]) >> $shift) | $carry; + $carry = (ord($x[$i]) << $carry_shift) & 0xFF; + $x[$i] = chr($temp); + } + $x = ltrim($x, chr(0)); + + $remainder = chr($carry >> $carry_shift) . $remainder; + + return ltrim($remainder, chr(0)); + } + + // one quirk about how the following functions are implemented is that PHP defines N to be an unsigned long + // at 32-bits, while java's longs are 64-bits. + + /** + * Converts 32-bit integers to bytes. + * + * @param Integer $x + * @return String + * @access private + */ + function _int2bytes($x) + { + return ltrim(pack('N', $x), chr(0)); + } + + /** + * Converts bytes to 32-bit integers + * + * @param String $x + * @return Integer + * @access private + */ + function _bytes2int($x) + { + $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT)); + return $temp['int']; + } + + /** + * DER-encode an integer + * + * The ability to DER-encode integers is needed to create RSA public keys for use with OpenSSL + * + * @see modPow() + * @access private + * @param Integer $length + * @return String + */ + function _encodeASN1Length($length) + { + if ($length <= 0x7F) { + return chr($length); + } + + $temp = ltrim(pack('N', $length), chr(0)); + return pack('Ca*', 0x80 | strlen($temp), $temp); + } + + /** + * Single digit division + * + * Even if int64 is being used the division operator will return a float64 value + * if the dividend is not evenly divisible by the divisor. Since a float64 doesn't + * have the precision of int64 this is a problem so, when int64 is being used, + * we'll guarantee that the dividend is divisible by first subtracting the remainder. + * + * @access private + * @param Integer $x + * @param Integer $y + * @return Integer + */ + function _safe_divide($x, $y) + { + if (MATH_BIGINTEGER_BASE === 26) { + return (int) ($x / $y); + } + + // MATH_BIGINTEGER_BASE === 31 + return ($x - ($x % $y)) / $y; + } +} diff --git a/src/SteamAuth.php b/src/SteamAuth.php new file mode 100644 index 0000000..98f478a --- /dev/null +++ b/src/SteamAuth.php @@ -0,0 +1,454 @@ + "https://steamcommunity.com", + 'Referer' => "https://steamcommunity.com/" + ]; + + const MAIN_HOST = "steamcommunity.com"; + + const DOMAINS = [ + "https://steamcommunity.com", + "https://store.steampowered.com", + "https://help.steampowered.com", + "https://steam.tv" + ]; + + + public function __construct($login, $password, $sharedSecret = null, $cookieStorage = []) + { + $this->login = $login; + $this->password = $password; + $this->sharedSecret = $sharedSecret; + + $this->cookieStorage = $cookieStorage; + } + + /** + * @return bool + * @throws SteamResponseException + * @throws SteamErrorException + */ + public function login(): bool + { + if (self::isAuthorized()) + return true; + else if (!array_key_exists('sessionid', $this->cookieStorage)) + self::getStartupCookies(); + + $keys = self::getRSAKey(); + $encryptedPassword = self::encryptPassword($keys); + + $authSession = self::beginAuthSession($encryptedPassword, $keys->getTimestamp()); + + if ($authSession->getAllowedConfirmations()) { + if (self::isTwoFactorRequired($authSession->getAllowedConfirmations()[0])) { + $twoFactorCode = SteamTotp::getAuthCode($this->sharedSecret); + + self::updateAuthSession($authSession->getClientId(), $authSession->getSteamid(), $twoFactorCode, + EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceCode); + } + } + + $session = self::pollAuthSessionStatus($authSession->getClientId(), $authSession->getRequestId()); + + $tokens = self::finalizeLogin($session->getRefreshToken(), self::getCookiesByHost()['sessionid']); + + foreach ($tokens['transfer_info'] as $token) { + self::setToken($token['url'], $token['params']['nonce'], $token['params']['auth'], $authSession->getSteamid()); + } + + foreach (self::DOMAINS as $domain) { + self::getAdditionalCookies($domain); + } + + return true; + } + + /** + * @return CAuthentication_GetPasswordRSAPublicKey_Response + * @throws SteamResponseException + * @throws SteamErrorException + */ + private function getRSAKey(): CAuthentication_GetPasswordRSAPublicKey_Response + { + $curl = new Curl();; +// $curl->setHeaders(self::HEADERS); + + $message = new CAuthentication_GetPasswordRSAPublicKey_Request(); + $message->setAccountName($this->login); + + $curl->get('https://api.steampowered.com/IAuthenticationService/GetPasswordRSAPublicKey/v1', + [ + 'origin' => 'https://steamcommunity.com', + 'input_protobuf_encoded' => base64_encode($message->serializeToString()) + ] + ); + + $rsaResponse = new CAuthentication_GetPasswordRSAPublicKey_Response(); + + if (!$curl->error) + $rsaResponse->parseFromString($curl->response); + else + throw new SteamResponseException($curl->errorMessage); + + self::checkSteamError($curl->responseHeaders['x-eresult']); + + return $rsaResponse; + } + + /** + * @param $keys + * @return string + */ + private function encryptPassword($keys): string + { + $rsa = new Crypt_RSA(); + + $key = [ + 'modulus' => new MathBigInteger($keys->getPublickeyMod(), 16), + 'publicExponent' => new MathBigInteger($keys->getPublickeyExp(), 16) + ]; + + $rsa->loadKey($key, CRYPT_RSA_PUBLIC_FORMAT_RAW); + $rsa->setPublicKey($key); + $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1); + + return base64_encode($rsa->encrypt($this->password)); + } + + /** + * @param $encryptedPassword + * @param $rsaTimestamp + * @return CAuthentication_BeginAuthSessionViaCredentials_Response + * @throws SteamResponseException + * @throws SteamErrorException + */ + private function beginAuthSession($encryptedPassword, $rsaTimestamp): CAuthentication_BeginAuthSessionViaCredentials_Response + { + $curl = new Curl(); +// $curl->setHeaders(self::HEADERS); + + $message = new CAuthentication_BeginAuthSessionViaCredentials_Request(); + + $message->setAccountName($this->login); + $message->setEncryptedPassword($encryptedPassword); + $message->setEncryptionTimestamp($rsaTimestamp); + $message->setRememberLogin(true); + $message->setPlatformType(EAuthTokenPlatformType::k_EAuthTokenPlatformType_WebBrowser); + $message->setWebsiteId('Community'); + $message->setPersistence(ESessionPersistence::k_ESessionPersistence_Persistent); + $message->setDeviceFriendlyName('Mozilla/5.0 (X11; Linux x86_64; rv:1.9.5.20) Gecko/2812-12-10 04:56:28 Firefox/3.8'); + + $curl->post('https://api.steampowered.com/IAuthenticationService/BeginAuthSessionViaCredentials/v1', + [ + 'input_protobuf_encoded' => base64_encode($message->serializeToString()) + ] + ); + + $sessionResponse = new CAuthentication_BeginAuthSessionViaCredentials_Response(); + + if (!$curl->error) + $sessionResponse->parseFromString($curl->response); + else + throw new SteamResponseException($curl->errorMessage); + + self::checkSteamError($curl->responseHeaders['x-eresult']); + + return $sessionResponse; + } + + /** + * @param $confirmation + * @return bool + */ + private function isTwoFactorRequired($confirmation): bool + { + return $confirmation->getConfirmationType() == EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceCode; + } + + /** + * @param $clientId + * @param $steamId + * @param $twoFactoryCode + * @param $codeType + * @throws SteamResponseException + * @throws SteamErrorException + */ + private function updateAuthSession($clientId, $steamId, $twoFactoryCode, $codeType) + { + $curl = new Curl(); +// $curl->setHeaders(self::HEADERS); + + $message = new CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request(); + + $message->setClientId($clientId); + $message->setSteamid($steamId); + $message->setCode($twoFactoryCode); + $message->setCodeType($codeType); + + $curl->post('https://api.steampowered.com/IAuthenticationService/UpdateAuthSessionWithSteamGuardCode/v1', + [ + 'input_protobuf_encoded' => base64_encode($message->serializeToString()) + ] + ); + + if ($curl->error) + throw new SteamResponseException($curl->errorMessage); + + self::checkSteamError($curl->responseHeaders['x-eresult']); + } + + /** + * @param $clientId + * @param $requestId + * @return CAuthentication_PollAuthSessionStatus_Response + * @throws SteamResponseException + * @throws SteamErrorException + */ + private function pollAuthSessionStatus($clientId, $requestId): CAuthentication_PollAuthSessionStatus_Response + { + $curl = new Curl(); + + $message = new CAuthentication_PollAuthSessionStatus_Request(); + + $message->setClientId($clientId); + $message->setRequestId($requestId); + + $curl->post('https://api.steampowered.com/IAuthenticationService/PollAuthSessionStatus/v1', + [ + 'input_protobuf_encoded' => base64_encode($message->serializeToString()) + ] + ); + + $pollAuthResponse = new CAuthentication_PollAuthSessionStatus_Response(); + + if (!$curl->error) + $pollAuthResponse->parseFromString($curl->response); + else + throw new SteamResponseException($curl->errorMessage); + + self::checkSteamError($curl->responseHeaders['x-eresult']); + + return $pollAuthResponse; + } + + /** + * @throws SteamResponseException + */ + public function getStartupCookies() + { + $curl = new Curl(); + $curl->setDefaultJsonDecoder($assoc = true); + $curl->setConnectTimeout(30); + $curl->setTimeout(60); + + $curl->get('https://steamcommunity.com/'); + + if ($curl->error) + throw new SteamResponseException($curl->errorMessage); + + self::updateCookieStorage($curl->responseCookies); + } + + /** + * @param $refreshToken + * @param $sessionId + * @return array + * @throws SteamResponseException + * @throws SteamErrorException + */ + private function finalizeLogin($refreshToken, $sessionId): array + { + $curl = new Curl(); + $curl->setDefaultJsonDecoder($assoc = true); + $curl->setConnectTimeout(30); + $curl->setTimeout(60); + + $curl->post('https://login.steampowered.com/jwt/finalizelogin', + [ + 'nonce' => $refreshToken, + 'sessionid' => $sessionId, + 'redir' => 'https://steamcommunity.com/login/home/?goto=' + ] + ); + + if ($curl->error) + throw new SteamResponseException($curl->errorMessage); + + self::checkSteamError($curl->responseHeaders['x-eresult']); + + return $curl->response; + } + + /** + * @param string $url + * @param string $nonce + * @param $auth + * @param $steamId + * @throws SteamResponseException + */ + private function setToken(string $url, string $nonce, $auth, $steamId) + { + $curl = new Curl(); + $curl->setDefaultJsonDecoder($assoc = true); + $curl->setConnectTimeout(30); + $curl->setTimeout(60); + $curl->setCookies(self::getCookiesByHost(self::getHostFromUrl($url))); + + $curl->post($url, + [ + 'nonce' => $nonce, + 'auth' => $auth, + 'steamID' => $steamId + ] + ); + + if ($curl->error) + throw new SteamResponseException($curl->errorMessage); + + self::updateCookieStorage($curl->responseCookies, self::getHostFromUrl($url)); + } + + /** + * @return null + * @throws SteamResponseException + */ + public function isAuthorized() + { + $curl = new Curl(); + $curl->setDefaultJsonDecoder($assoc = true); + $curl->setConnectTimeout(30); + $curl->setTimeout(60); + $curl->setCookies(self::getCookiesByHost()); + + $curl->get('https://steamcommunity.com/chat/clientjstoken'); + + if ($curl->error) + throw new SteamResponseException($curl->errorMessage); + + return $curl->response['logged_in']; + } + + /** + * @param $url + * @throws SteamResponseException + */ + private function getAdditionalCookies($url) + { + $curl = new Curl(); + $curl->setDefaultJsonDecoder($assoc = true); + $curl->setConnectTimeout(30); + $curl->setTimeout(60); + $curl->setCookies(self::getCookiesByHost(self::getHostFromUrl($url))); + + $curl->get($url); + + if ($curl->error) + throw new SteamResponseException($curl->errorMessage); + + self::updateCookieStorage($curl->responseCookies, self::getHostFromUrl($url)); + } + + + /** + * @return array|mixed + */ + public function getCookies() + { + return $this->cookieStorage; + } + + /** + * @param string $host + * @return array|mixed + */ + public function getCookiesByHost(string $host = self::MAIN_HOST) + { + if (array_key_exists($host, $this->cookieStorage)) + return $this->cookieStorage[$host]; + + return []; + } + + /** + * @param $cookies + * @param string $host + */ + public function updateCookieStorage($cookies, string $host = self::MAIN_HOST) + { + foreach ($cookies as $key => $value) { + $this->cookieStorage[$host][$key] = $value; + } + } + + /** + * @param string $url + * @return array|int|string + */ + public function getHostFromUrl(string $url) + { + $parsedUrl = parse_url($url, PHP_URL_HOST); + + if ($parsedUrl) return $parsedUrl; + else return ""; + } + + /** + * @param $timezone + * @return false|int + * @throws Exception + */ + public function getTimezoneOffset($timezone) + { + $thisTz = new DateTimeZone($timezone); + $now = new DateTime("now", $thisTz); + + return $thisTz->getOffset($now); + } + + /** + * @param $errorCode + * @throws SteamErrorException + */ + private function checkSteamError($errorCode) + { + if ($errorCode) { + if ($errorCode == 1) + return; + else if (array_key_exists($errorCode, SteamConfig::CODES)) + throw new SteamErrorException(SteamConfig::CODES[$errorCode]); + } + } +} \ No newline at end of file diff --git a/src/pb2/CAuthentication_AllowedConfirmation.php b/src/pb2/CAuthentication_AllowedConfirmation.php new file mode 100644 index 0000000..24d40df --- /dev/null +++ b/src/pb2/CAuthentication_AllowedConfirmation.php @@ -0,0 +1,126 @@ + array( + 'default' => EAuthSessionGuardType::k_EAuthSessionGuardType_Unknown, + 'name' => 'confirmation_type', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + self::ASSOCIATED_MESSAGE => array( + 'name' => 'associated_message', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + ); + + /** + * Constructs new message container and clears its internal state + */ + public function __construct() + { + $this->reset(); + } + + /** + * Clears message values and sets default ones + * + * @return null + */ + public function reset() + { + $this->values[self::CONFIRMATION_TYPE] = self::$fields[self::CONFIRMATION_TYPE]['default']; + $this->values[self::ASSOCIATED_MESSAGE] = null; + } + + /** + * Returns field descriptors + * + * @return array + */ + public function fields() + { + return self::$fields; + } + + /** + * Sets value of 'confirmation_type' property + * + * @param integer $value Property value + * + * @return null + */ + public function setConfirmationType($value) + { + return $this->set(self::CONFIRMATION_TYPE, $value); + } + + /** + * Returns value of 'confirmation_type' property + * + * @return integer + */ + public function getConfirmationType() + { + $value = $this->get(self::CONFIRMATION_TYPE); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'confirmation_type' property is set, false otherwise + * + * @return boolean + */ + public function hasConfirmationType() + { + return $this->get(self::CONFIRMATION_TYPE) !== null; + } + + /** + * Sets value of 'associated_message' property + * + * @param string $value Property value + * + * @return null + */ + public function setAssociatedMessage($value) + { + return $this->set(self::ASSOCIATED_MESSAGE, $value); + } + + /** + * Returns value of 'associated_message' property + * + * @return string + */ + public function getAssociatedMessage() + { + $value = $this->get(self::ASSOCIATED_MESSAGE); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'associated_message' property is set, false otherwise + * + * @return boolean + */ + public function hasAssociatedMessage() + { + return $this->get(self::ASSOCIATED_MESSAGE) !== null; + } +} +} \ No newline at end of file diff --git a/src/pb2/CAuthentication_BeginAuthSessionViaCredentials_Request.php b/src/pb2/CAuthentication_BeginAuthSessionViaCredentials_Request.php new file mode 100644 index 0000000..c105344 --- /dev/null +++ b/src/pb2/CAuthentication_BeginAuthSessionViaCredentials_Request.php @@ -0,0 +1,447 @@ + array( + 'name' => 'device_friendly_name', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::ACCOUNT_NAME => array( + 'name' => 'account_name', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::ENCRYPTED_PASSWORD => array( + 'name' => 'encrypted_password', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::ENCRYPTION_TIMESTAMP => array( + 'name' => 'encryption_timestamp', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + self::REMEMBER_LOGIN => array( + 'name' => 'remember_login', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_BOOL, + ), + self::PLATFORM_TYPE => array( + 'default' => EAuthTokenPlatformType::k_EAuthTokenPlatformType_Unknown, + 'name' => 'platform_type', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + self::PERSISTENCE => array( + 'default' => ESessionPersistence::k_ESessionPersistence_Persistent, + 'name' => 'persistence', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + self::WEBSITE_ID => array( + 'default' => 'Unknown', + 'name' => 'website_id', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::DEVICE_DETAILS => array( + 'name' => 'device_details', + 'required' => false, + 'type' => CAuthentication_DeviceDetails::class + ), + self::GUARD_DATA => array( + 'name' => 'guard_data', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + ); + + /** + * Constructs new message container and clears its internal state + */ + public function __construct() + { + $this->reset(); + } + + /** + * Clears message values and sets default ones + * + * @return null + */ + public function reset() + { + $this->values[self::DEVICE_FRIENDLY_NAME] = null; + $this->values[self::ACCOUNT_NAME] = null; + $this->values[self::ENCRYPTED_PASSWORD] = null; + $this->values[self::ENCRYPTION_TIMESTAMP] = null; + $this->values[self::REMEMBER_LOGIN] = null; + $this->values[self::PLATFORM_TYPE] = self::$fields[self::PLATFORM_TYPE]['default']; + $this->values[self::PERSISTENCE] = self::$fields[self::PERSISTENCE]['default']; + $this->values[self::WEBSITE_ID] = self::$fields[self::WEBSITE_ID]['default']; + $this->values[self::DEVICE_DETAILS] = null; + $this->values[self::GUARD_DATA] = null; + } + + /** + * Returns field descriptors + * + * @return array + */ + public function fields() + { + return self::$fields; + } + + /** + * Sets value of 'device_friendly_name' property + * + * @param string $value Property value + * + * @return null + */ + public function setDeviceFriendlyName($value) + { + return $this->set(self::DEVICE_FRIENDLY_NAME, $value); + } + + /** + * Returns value of 'device_friendly_name' property + * + * @return string + */ + public function getDeviceFriendlyName() + { + $value = $this->get(self::DEVICE_FRIENDLY_NAME); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'device_friendly_name' property is set, false otherwise + * + * @return boolean + */ + public function hasDeviceFriendlyName() + { + return $this->get(self::DEVICE_FRIENDLY_NAME) !== null; + } + + /** + * Sets value of 'account_name' property + * + * @param string $value Property value + * + * @return null + */ + public function setAccountName($value) + { + return $this->set(self::ACCOUNT_NAME, $value); + } + + /** + * Returns value of 'account_name' property + * + * @return string + */ + public function getAccountName() + { + $value = $this->get(self::ACCOUNT_NAME); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'account_name' property is set, false otherwise + * + * @return boolean + */ + public function hasAccountName() + { + return $this->get(self::ACCOUNT_NAME) !== null; + } + + /** + * Sets value of 'encrypted_password' property + * + * @param string $value Property value + * + * @return null + */ + public function setEncryptedPassword($value) + { + return $this->set(self::ENCRYPTED_PASSWORD, $value); + } + + /** + * Returns value of 'encrypted_password' property + * + * @return string + */ + public function getEncryptedPassword() + { + $value = $this->get(self::ENCRYPTED_PASSWORD); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'encrypted_password' property is set, false otherwise + * + * @return boolean + */ + public function hasEncryptedPassword() + { + return $this->get(self::ENCRYPTED_PASSWORD) !== null; + } + + /** + * Sets value of 'encryption_timestamp' property + * + * @param integer $value Property value + * + * @return null + */ + public function setEncryptionTimestamp($value) + { + return $this->set(self::ENCRYPTION_TIMESTAMP, $value); + } + + /** + * Returns value of 'encryption_timestamp' property + * + * @return integer + */ + public function getEncryptionTimestamp() + { + $value = $this->get(self::ENCRYPTION_TIMESTAMP); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'encryption_timestamp' property is set, false otherwise + * + * @return boolean + */ + public function hasEncryptionTimestamp() + { + return $this->get(self::ENCRYPTION_TIMESTAMP) !== null; + } + + /** + * Sets value of 'remember_login' property + * + * @param boolean $value Property value + * + * @return null + */ + public function setRememberLogin($value) + { + return $this->set(self::REMEMBER_LOGIN, $value); + } + + /** + * Returns value of 'remember_login' property + * + * @return boolean + */ + public function getRememberLogin() + { + $value = $this->get(self::REMEMBER_LOGIN); + return $value === null ? (boolean)$value : $value; + } + + /** + * Returns true if 'remember_login' property is set, false otherwise + * + * @return boolean + */ + public function hasRememberLogin() + { + return $this->get(self::REMEMBER_LOGIN) !== null; + } + + /** + * Sets value of 'platform_type' property + * + * @param integer $value Property value + * + * @return null + */ + public function setPlatformType($value) + { + return $this->set(self::PLATFORM_TYPE, $value); + } + + /** + * Returns value of 'platform_type' property + * + * @return integer + */ + public function getPlatformType() + { + $value = $this->get(self::PLATFORM_TYPE); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'platform_type' property is set, false otherwise + * + * @return boolean + */ + public function hasPlatformType() + { + return $this->get(self::PLATFORM_TYPE) !== null; + } + + /** + * Sets value of 'persistence' property + * + * @param integer $value Property value + * + * @return null + */ + public function setPersistence($value) + { + return $this->set(self::PERSISTENCE, $value); + } + + /** + * Returns value of 'persistence' property + * + * @return integer + */ + public function getPersistence() + { + $value = $this->get(self::PERSISTENCE); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'persistence' property is set, false otherwise + * + * @return boolean + */ + public function hasPersistence() + { + return $this->get(self::PERSISTENCE) !== null; + } + + /** + * Sets value of 'website_id' property + * + * @param string $value Property value + * + * @return null + */ + public function setWebsiteId($value) + { + return $this->set(self::WEBSITE_ID, $value); + } + + /** + * Returns value of 'website_id' property + * + * @return string + */ + public function getWebsiteId() + { + $value = $this->get(self::WEBSITE_ID); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'website_id' property is set, false otherwise + * + * @return boolean + */ + public function hasWebsiteId() + { + return $this->get(self::WEBSITE_ID) !== null; + } + + /** + * Sets value of 'device_details' property + * + * @param \CAuthentication_DeviceDetails $value Property value + * + * @return null + */ + public function setDeviceDetails(\CAuthentication_DeviceDetails $value=null) + { + return $this->set(self::DEVICE_DETAILS, $value); + } + + /** + * Returns value of 'device_details' property + * + * @return \CAuthentication_DeviceDetails + */ + public function getDeviceDetails() + { + return $this->get(self::DEVICE_DETAILS); + } + + /** + * Returns true if 'device_details' property is set, false otherwise + * + * @return boolean + */ + public function hasDeviceDetails() + { + return $this->get(self::DEVICE_DETAILS) !== null; + } + + /** + * Sets value of 'guard_data' property + * + * @param string $value Property value + * + * @return null + */ + public function setGuardData($value) + { + return $this->set(self::GUARD_DATA, $value); + } + + /** + * Returns value of 'guard_data' property + * + * @return string + */ + public function getGuardData() + { + $value = $this->get(self::GUARD_DATA); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'guard_data' property is set, false otherwise + * + * @return boolean + */ + public function hasGuardData() + { + return $this->get(self::GUARD_DATA) !== null; + } +} +} \ No newline at end of file diff --git a/src/pb2/CAuthentication_BeginAuthSessionViaCredentials_Response.php b/src/pb2/CAuthentication_BeginAuthSessionViaCredentials_Response.php new file mode 100644 index 0000000..72b471d --- /dev/null +++ b/src/pb2/CAuthentication_BeginAuthSessionViaCredentials_Response.php @@ -0,0 +1,326 @@ + array( + 'name' => 'client_id', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + self::REQUEST_ID => array( + 'name' => 'request_id', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::INTERVAL => array( + 'name' => 'interval', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_FLOAT, + ), + self::ALLOWED_CONFIRMATIONS => array( + 'name' => 'allowed_confirmations', + 'repeated' => true, + 'type' => CAuthentication_AllowedConfirmation::class + ), + self::STEAMID => array( + 'name' => 'steamid', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + self::WEAK_TOKEN => array( + 'name' => 'weak_token', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + ); + + /** + * Constructs new message container and clears its internal state + */ + public function __construct() + { + $this->reset(); + } + + /** + * Clears message values and sets default ones + * + * @return null + */ + public function reset() + { + $this->values[self::CLIENT_ID] = null; + $this->values[self::REQUEST_ID] = null; + $this->values[self::INTERVAL] = null; + $this->values[self::ALLOWED_CONFIRMATIONS] = array(); + $this->values[self::STEAMID] = null; + $this->values[self::WEAK_TOKEN] = null; + } + + /** + * Returns field descriptors + * + * @return array + */ + public function fields() + { + return self::$fields; + } + + /** + * Sets value of 'client_id' property + * + * @param integer $value Property value + * + * @return null + */ + public function setClientId($value) + { + return $this->set(self::CLIENT_ID, $value); + } + + /** + * Returns value of 'client_id' property + * + * @return integer + */ + public function getClientId() + { + $value = $this->get(self::CLIENT_ID); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'client_id' property is set, false otherwise + * + * @return boolean + */ + public function hasClientId() + { + return $this->get(self::CLIENT_ID) !== null; + } + + /** + * Sets value of 'request_id' property + * + * @param string $value Property value + * + * @return null + */ + public function setRequestId($value) + { + return $this->set(self::REQUEST_ID, $value); + } + + /** + * Returns value of 'request_id' property + * + * @return string + */ + public function getRequestId() + { + $value = $this->get(self::REQUEST_ID); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'request_id' property is set, false otherwise + * + * @return boolean + */ + public function hasRequestId() + { + return $this->get(self::REQUEST_ID) !== null; + } + + /** + * Sets value of 'interval' property + * + * @param double $value Property value + * + * @return null + */ + public function setInterval($value) + { + return $this->set(self::INTERVAL, $value); + } + + /** + * Returns value of 'interval' property + * + * @return double + */ + public function getInterval() + { + $value = $this->get(self::INTERVAL); + return $value === null ? (double)$value : $value; + } + + /** + * Returns true if 'interval' property is set, false otherwise + * + * @return boolean + */ + public function hasInterval() + { + return $this->get(self::INTERVAL) !== null; + } + + /** + * Appends value to 'allowed_confirmations' list + * + * @param \CAuthentication_AllowedConfirmation $value Value to append + * + * @return null + */ + public function appendAllowedConfirmations(\CAuthentication_AllowedConfirmation $value) + { + return $this->append(self::ALLOWED_CONFIRMATIONS, $value); + } + + /** + * Clears 'allowed_confirmations' list + * + * @return null + */ + public function clearAllowedConfirmations() + { + return $this->clear(self::ALLOWED_CONFIRMATIONS); + } + + /** + * Returns 'allowed_confirmations' list + * + * @return \CAuthentication_AllowedConfirmation[] + */ + public function getAllowedConfirmations() + { + return $this->get(self::ALLOWED_CONFIRMATIONS); + } + + /** + * Returns true if 'allowed_confirmations' property is set, false otherwise + * + * @return boolean + */ + public function hasAllowedConfirmations() + { + return count($this->get(self::ALLOWED_CONFIRMATIONS)) !== 0; + } + + /** + * Returns 'allowed_confirmations' iterator + * + * @return \ArrayIterator + */ + public function getAllowedConfirmationsIterator() + { + return new \ArrayIterator($this->get(self::ALLOWED_CONFIRMATIONS)); + } + + /** + * Returns element from 'allowed_confirmations' list at given offset + * + * @param int $offset Position in list + * + * @return \CAuthentication_AllowedConfirmation + */ + public function getAllowedConfirmationsAt($offset) + { + return $this->get(self::ALLOWED_CONFIRMATIONS, $offset); + } + + /** + * Returns count of 'allowed_confirmations' list + * + * @return int + */ + public function getAllowedConfirmationsCount() + { + return $this->count(self::ALLOWED_CONFIRMATIONS); + } + + /** + * Sets value of 'steamid' property + * + * @param integer $value Property value + * + * @return null + */ + public function setSteamid($value) + { + return $this->set(self::STEAMID, $value); + } + + /** + * Returns value of 'steamid' property + * + * @return integer + */ + public function getSteamid() + { + $value = $this->get(self::STEAMID); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'steamid' property is set, false otherwise + * + * @return boolean + */ + public function hasSteamid() + { + return $this->get(self::STEAMID) !== null; + } + + /** + * Sets value of 'weak_token' property + * + * @param string $value Property value + * + * @return null + */ + public function setWeakToken($value) + { + return $this->set(self::WEAK_TOKEN, $value); + } + + /** + * Returns value of 'weak_token' property + * + * @return string + */ + public function getWeakToken() + { + $value = $this->get(self::WEAK_TOKEN); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'weak_token' property is set, false otherwise + * + * @return boolean + */ + public function hasWeakToken() + { + return $this->get(self::WEAK_TOKEN) !== null; + } +} +} \ No newline at end of file diff --git a/src/pb2/CAuthentication_DeviceDetails.php b/src/pb2/CAuthentication_DeviceDetails.php new file mode 100644 index 0000000..6d33712 --- /dev/null +++ b/src/pb2/CAuthentication_DeviceDetails.php @@ -0,0 +1,206 @@ + array( + 'name' => 'device_friendly_name', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::PLATFORM_TYPE => array( + 'default' => EAuthTokenPlatformType::k_EAuthTokenPlatformType_Unknown, + 'name' => 'platform_type', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + self::OS_TYPE => array( + 'name' => 'os_type', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + self::GAMING_DEVICE_TYPE => array( + 'name' => 'gaming_device_type', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + ); + + /** + * Constructs new message container and clears its internal state + */ + public function __construct() + { + $this->reset(); + } + + /** + * Clears message values and sets default ones + * + * @return null + */ + public function reset() + { + $this->values[self::DEVICE_FRIENDLY_NAME] = null; + $this->values[self::PLATFORM_TYPE] = self::$fields[self::PLATFORM_TYPE]['default']; + $this->values[self::OS_TYPE] = null; + $this->values[self::GAMING_DEVICE_TYPE] = null; + } + + /** + * Returns field descriptors + * + * @return array + */ + public function fields() + { + return self::$fields; + } + + /** + * Sets value of 'device_friendly_name' property + * + * @param string $value Property value + * + * @return null + */ + public function setDeviceFriendlyName($value) + { + return $this->set(self::DEVICE_FRIENDLY_NAME, $value); + } + + /** + * Returns value of 'device_friendly_name' property + * + * @return string + */ + public function getDeviceFriendlyName() + { + $value = $this->get(self::DEVICE_FRIENDLY_NAME); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'device_friendly_name' property is set, false otherwise + * + * @return boolean + */ + public function hasDeviceFriendlyName() + { + return $this->get(self::DEVICE_FRIENDLY_NAME) !== null; + } + + /** + * Sets value of 'platform_type' property + * + * @param integer $value Property value + * + * @return null + */ + public function setPlatformType($value) + { + return $this->set(self::PLATFORM_TYPE, $value); + } + + /** + * Returns value of 'platform_type' property + * + * @return integer + */ + public function getPlatformType() + { + $value = $this->get(self::PLATFORM_TYPE); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'platform_type' property is set, false otherwise + * + * @return boolean + */ + public function hasPlatformType() + { + return $this->get(self::PLATFORM_TYPE) !== null; + } + + /** + * Sets value of 'os_type' property + * + * @param integer $value Property value + * + * @return null + */ + public function setOsType($value) + { + return $this->set(self::OS_TYPE, $value); + } + + /** + * Returns value of 'os_type' property + * + * @return integer + */ + public function getOsType() + { + $value = $this->get(self::OS_TYPE); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'os_type' property is set, false otherwise + * + * @return boolean + */ + public function hasOsType() + { + return $this->get(self::OS_TYPE) !== null; + } + + /** + * Sets value of 'gaming_device_type' property + * + * @param integer $value Property value + * + * @return null + */ + public function setGamingDeviceType($value) + { + return $this->set(self::GAMING_DEVICE_TYPE, $value); + } + + /** + * Returns value of 'gaming_device_type' property + * + * @return integer + */ + public function getGamingDeviceType() + { + $value = $this->get(self::GAMING_DEVICE_TYPE); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'gaming_device_type' property is set, false otherwise + * + * @return boolean + */ + public function hasGamingDeviceType() + { + return $this->get(self::GAMING_DEVICE_TYPE) !== null; + } +} +} \ No newline at end of file diff --git a/src/pb2/CAuthentication_GetPasswordRSAPublicKey_Request.php b/src/pb2/CAuthentication_GetPasswordRSAPublicKey_Request.php new file mode 100644 index 0000000..d9b4f4d --- /dev/null +++ b/src/pb2/CAuthentication_GetPasswordRSAPublicKey_Request.php @@ -0,0 +1,85 @@ + array( + 'name' => 'account_name', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + ); + + /** + * Constructs new message container and clears its internal state + */ + public function __construct() + { + $this->reset(); + } + + /** + * Clears message values and sets default ones + * + * @return null + */ + public function reset() + { + $this->values[self::ACCOUNT_NAME] = null; + } + + /** + * Returns field descriptors + * + * @return array + */ + public function fields() + { + return self::$fields; + } + + /** + * Sets value of 'account_name' property + * + * @param string $value Property value + * + * @return null + */ + public function setAccountName($value) + { + return $this->set(self::ACCOUNT_NAME, $value); + } + + /** + * Returns value of 'account_name' property + * + * @return string + */ + public function getAccountName() + { + $value = $this->get(self::ACCOUNT_NAME); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'account_name' property is set, false otherwise + * + * @return boolean + */ + public function hasAccountName() + { + return $this->get(self::ACCOUNT_NAME) !== null; + } +} +} \ No newline at end of file diff --git a/src/pb2/CAuthentication_GetPasswordRSAPublicKey_Response.php b/src/pb2/CAuthentication_GetPasswordRSAPublicKey_Response.php new file mode 100644 index 0000000..c9751f7 --- /dev/null +++ b/src/pb2/CAuthentication_GetPasswordRSAPublicKey_Response.php @@ -0,0 +1,165 @@ + array( + 'name' => 'publickey_mod', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::PUBLICKEY_EXP => array( + 'name' => 'publickey_exp', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::TIMESTAMP => array( + 'name' => 'timestamp', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + ); + + /** + * Constructs new message container and clears its internal state + */ + public function __construct() + { + $this->reset(); + } + + /** + * Clears message values and sets default ones + * + * @return null + */ + public function reset() + { + $this->values[self::PUBLICKEY_MOD] = null; + $this->values[self::PUBLICKEY_EXP] = null; + $this->values[self::TIMESTAMP] = null; + } + + /** + * Returns field descriptors + * + * @return array + */ + public function fields() + { + return self::$fields; + } + + /** + * Sets value of 'publickey_mod' property + * + * @param string $value Property value + * + * @return null + */ + public function setPublickeyMod($value) + { + return $this->set(self::PUBLICKEY_MOD, $value); + } + + /** + * Returns value of 'publickey_mod' property + * + * @return string + */ + public function getPublickeyMod() + { + $value = $this->get(self::PUBLICKEY_MOD); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'publickey_mod' property is set, false otherwise + * + * @return boolean + */ + public function hasPublickeyMod() + { + return $this->get(self::PUBLICKEY_MOD) !== null; + } + + /** + * Sets value of 'publickey_exp' property + * + * @param string $value Property value + * + * @return null + */ + public function setPublickeyExp($value) + { + return $this->set(self::PUBLICKEY_EXP, $value); + } + + /** + * Returns value of 'publickey_exp' property + * + * @return string + */ + public function getPublickeyExp() + { + $value = $this->get(self::PUBLICKEY_EXP); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'publickey_exp' property is set, false otherwise + * + * @return boolean + */ + public function hasPublickeyExp() + { + return $this->get(self::PUBLICKEY_EXP) !== null; + } + + /** + * Sets value of 'timestamp' property + * + * @param integer $value Property value + * + * @return null + */ + public function setTimestamp($value) + { + return $this->set(self::TIMESTAMP, $value); + } + + /** + * Returns value of 'timestamp' property + * + * @return integer + */ + public function getTimestamp() + { + $value = $this->get(self::TIMESTAMP); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'timestamp' property is set, false otherwise + * + * @return boolean + */ + public function hasTimestamp() + { + return $this->get(self::TIMESTAMP) !== null; + } +} +} \ No newline at end of file diff --git a/src/pb2/CAuthentication_PollAuthSessionStatus_Request.php b/src/pb2/CAuthentication_PollAuthSessionStatus_Request.php new file mode 100644 index 0000000..839f117 --- /dev/null +++ b/src/pb2/CAuthentication_PollAuthSessionStatus_Request.php @@ -0,0 +1,165 @@ + array( + 'name' => 'client_id', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + self::REQUEST_ID => array( + 'name' => 'request_id', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::TOKEN_TO_REVOKE => array( + 'name' => 'token_to_revoke', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_FIXED64, + ), + ); + + /** + * Constructs new message container and clears its internal state + */ + public function __construct() + { + $this->reset(); + } + + /** + * Clears message values and sets default ones + * + * @return null + */ + public function reset() + { + $this->values[self::CLIENT_ID] = null; + $this->values[self::REQUEST_ID] = null; + $this->values[self::TOKEN_TO_REVOKE] = null; + } + + /** + * Returns field descriptors + * + * @return array + */ + public function fields() + { + return self::$fields; + } + + /** + * Sets value of 'client_id' property + * + * @param integer $value Property value + * + * @return null + */ + public function setClientId($value) + { + return $this->set(self::CLIENT_ID, $value); + } + + /** + * Returns value of 'client_id' property + * + * @return integer + */ + public function getClientId() + { + $value = $this->get(self::CLIENT_ID); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'client_id' property is set, false otherwise + * + * @return boolean + */ + public function hasClientId() + { + return $this->get(self::CLIENT_ID) !== null; + } + + /** + * Sets value of 'request_id' property + * + * @param string $value Property value + * + * @return null + */ + public function setRequestId($value) + { + return $this->set(self::REQUEST_ID, $value); + } + + /** + * Returns value of 'request_id' property + * + * @return string + */ + public function getRequestId() + { + $value = $this->get(self::REQUEST_ID); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'request_id' property is set, false otherwise + * + * @return boolean + */ + public function hasRequestId() + { + return $this->get(self::REQUEST_ID) !== null; + } + + /** + * Sets value of 'token_to_revoke' property + * + * @param integer $value Property value + * + * @return null + */ + public function setTokenToRevoke($value) + { + return $this->set(self::TOKEN_TO_REVOKE, $value); + } + + /** + * Returns value of 'token_to_revoke' property + * + * @return integer + */ + public function getTokenToRevoke() + { + $value = $this->get(self::TOKEN_TO_REVOKE); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'token_to_revoke' property is set, false otherwise + * + * @return boolean + */ + public function hasTokenToRevoke() + { + return $this->get(self::TOKEN_TO_REVOKE) !== null; + } +} +} \ No newline at end of file diff --git a/src/pb2/CAuthentication_PollAuthSessionStatus_Response.php b/src/pb2/CAuthentication_PollAuthSessionStatus_Response.php new file mode 100644 index 0000000..fb0213f --- /dev/null +++ b/src/pb2/CAuthentication_PollAuthSessionStatus_Response.php @@ -0,0 +1,325 @@ + array( + 'name' => 'new_client_id', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + self::NEW_CHALLENGE_URL => array( + 'name' => 'new_challenge_url', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::REFRESH_TOKEN => array( + 'name' => 'refresh_token', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::ACCESS_TOKEN => array( + 'name' => 'access_token', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::HAD_REMOTE_INTERACTION => array( + 'name' => 'had_remote_interaction', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_BOOL, + ), + self::ACCOUNT_NAME => array( + 'name' => 'account_name', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::NEW_GUARD_DATA => array( + 'name' => 'new_guard_data', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + ); + + /** + * Constructs new message container and clears its internal state + */ + public function __construct() + { + $this->reset(); + } + + /** + * Clears message values and sets default ones + * + * @return null + */ + public function reset() + { + $this->values[self::NEW_CLIENT_ID] = null; + $this->values[self::NEW_CHALLENGE_URL] = null; + $this->values[self::REFRESH_TOKEN] = null; + $this->values[self::ACCESS_TOKEN] = null; + $this->values[self::HAD_REMOTE_INTERACTION] = null; + $this->values[self::ACCOUNT_NAME] = null; + $this->values[self::NEW_GUARD_DATA] = null; + } + + /** + * Returns field descriptors + * + * @return array + */ + public function fields() + { + return self::$fields; + } + + /** + * Sets value of 'new_client_id' property + * + * @param integer $value Property value + * + * @return null + */ + public function setNewClientId($value) + { + return $this->set(self::NEW_CLIENT_ID, $value); + } + + /** + * Returns value of 'new_client_id' property + * + * @return integer + */ + public function getNewClientId() + { + $value = $this->get(self::NEW_CLIENT_ID); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'new_client_id' property is set, false otherwise + * + * @return boolean + */ + public function hasNewClientId() + { + return $this->get(self::NEW_CLIENT_ID) !== null; + } + + /** + * Sets value of 'new_challenge_url' property + * + * @param string $value Property value + * + * @return null + */ + public function setNewChallengeUrl($value) + { + return $this->set(self::NEW_CHALLENGE_URL, $value); + } + + /** + * Returns value of 'new_challenge_url' property + * + * @return string + */ + public function getNewChallengeUrl() + { + $value = $this->get(self::NEW_CHALLENGE_URL); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'new_challenge_url' property is set, false otherwise + * + * @return boolean + */ + public function hasNewChallengeUrl() + { + return $this->get(self::NEW_CHALLENGE_URL) !== null; + } + + /** + * Sets value of 'refresh_token' property + * + * @param string $value Property value + * + * @return null + */ + public function setRefreshToken($value) + { + return $this->set(self::REFRESH_TOKEN, $value); + } + + /** + * Returns value of 'refresh_token' property + * + * @return string + */ + public function getRefreshToken() + { + $value = $this->get(self::REFRESH_TOKEN); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'refresh_token' property is set, false otherwise + * + * @return boolean + */ + public function hasRefreshToken() + { + return $this->get(self::REFRESH_TOKEN) !== null; + } + + /** + * Sets value of 'access_token' property + * + * @param string $value Property value + * + * @return null + */ + public function setAccessToken($value) + { + return $this->set(self::ACCESS_TOKEN, $value); + } + + /** + * Returns value of 'access_token' property + * + * @return string + */ + public function getAccessToken() + { + $value = $this->get(self::ACCESS_TOKEN); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'access_token' property is set, false otherwise + * + * @return boolean + */ + public function hasAccessToken() + { + return $this->get(self::ACCESS_TOKEN) !== null; + } + + /** + * Sets value of 'had_remote_interaction' property + * + * @param boolean $value Property value + * + * @return null + */ + public function setHadRemoteInteraction($value) + { + return $this->set(self::HAD_REMOTE_INTERACTION, $value); + } + + /** + * Returns value of 'had_remote_interaction' property + * + * @return boolean + */ + public function getHadRemoteInteraction() + { + $value = $this->get(self::HAD_REMOTE_INTERACTION); + return $value === null ? (boolean)$value : $value; + } + + /** + * Returns true if 'had_remote_interaction' property is set, false otherwise + * + * @return boolean + */ + public function hasHadRemoteInteraction() + { + return $this->get(self::HAD_REMOTE_INTERACTION) !== null; + } + + /** + * Sets value of 'account_name' property + * + * @param string $value Property value + * + * @return null + */ + public function setAccountName($value) + { + return $this->set(self::ACCOUNT_NAME, $value); + } + + /** + * Returns value of 'account_name' property + * + * @return string + */ + public function getAccountName() + { + $value = $this->get(self::ACCOUNT_NAME); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'account_name' property is set, false otherwise + * + * @return boolean + */ + public function hasAccountName() + { + return $this->get(self::ACCOUNT_NAME) !== null; + } + + /** + * Sets value of 'new_guard_data' property + * + * @param string $value Property value + * + * @return null + */ + public function setNewGuardData($value) + { + return $this->set(self::NEW_GUARD_DATA, $value); + } + + /** + * Returns value of 'new_guard_data' property + * + * @return string + */ + public function getNewGuardData() + { + $value = $this->get(self::NEW_GUARD_DATA); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'new_guard_data' property is set, false otherwise + * + * @return boolean + */ + public function hasNewGuardData() + { + return $this->get(self::NEW_GUARD_DATA) !== null; + } +} +} \ No newline at end of file diff --git a/src/pb2/CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request.php b/src/pb2/CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request.php new file mode 100644 index 0000000..08036b8 --- /dev/null +++ b/src/pb2/CAuthentication_UpdateAuthSessionWithSteamGuardCode_Request.php @@ -0,0 +1,206 @@ + array( + 'name' => 'client_id', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + self::STEAMID => array( + 'name' => 'steamid', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_FIXED64, + ), + self::CODE => array( + 'name' => 'code', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_STRING, + ), + self::CODE_TYPE => array( + 'default' => EAuthSessionGuardType::k_EAuthSessionGuardType_Unknown, + 'name' => 'code_type', + 'required' => false, + 'type' => \ProtobufMessage::PB_TYPE_INT, + ), + ); + + /** + * Constructs new message container and clears its internal state + */ + public function __construct() + { + $this->reset(); + } + + /** + * Clears message values and sets default ones + * + * @return null + */ + public function reset() + { + $this->values[self::CLIENT_ID] = null; + $this->values[self::STEAMID] = null; + $this->values[self::CODE] = null; + $this->values[self::CODE_TYPE] = self::$fields[self::CODE_TYPE]['default']; + } + + /** + * Returns field descriptors + * + * @return array + */ + public function fields() + { + return self::$fields; + } + + /** + * Sets value of 'client_id' property + * + * @param integer $value Property value + * + * @return null + */ + public function setClientId($value) + { + return $this->set(self::CLIENT_ID, $value); + } + + /** + * Returns value of 'client_id' property + * + * @return integer + */ + public function getClientId() + { + $value = $this->get(self::CLIENT_ID); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'client_id' property is set, false otherwise + * + * @return boolean + */ + public function hasClientId() + { + return $this->get(self::CLIENT_ID) !== null; + } + + /** + * Sets value of 'steamid' property + * + * @param integer $value Property value + * + * @return null + */ + public function setSteamid($value) + { + return $this->set(self::STEAMID, $value); + } + + /** + * Returns value of 'steamid' property + * + * @return integer + */ + public function getSteamid() + { + $value = $this->get(self::STEAMID); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'steamid' property is set, false otherwise + * + * @return boolean + */ + public function hasSteamid() + { + return $this->get(self::STEAMID) !== null; + } + + /** + * Sets value of 'code' property + * + * @param string $value Property value + * + * @return null + */ + public function setCode($value) + { + return $this->set(self::CODE, $value); + } + + /** + * Returns value of 'code' property + * + * @return string + */ + public function getCode() + { + $value = $this->get(self::CODE); + return $value === null ? (string)$value : $value; + } + + /** + * Returns true if 'code' property is set, false otherwise + * + * @return boolean + */ + public function hasCode() + { + return $this->get(self::CODE) !== null; + } + + /** + * Sets value of 'code_type' property + * + * @param integer $value Property value + * + * @return null + */ + public function setCodeType($value) + { + return $this->set(self::CODE_TYPE, $value); + } + + /** + * Returns value of 'code_type' property + * + * @return integer + */ + public function getCodeType() + { + $value = $this->get(self::CODE_TYPE); + return $value === null ? (integer)$value : $value; + } + + /** + * Returns true if 'code_type' property is set, false otherwise + * + * @return boolean + */ + public function hasCodeType() + { + return $this->get(self::CODE_TYPE) !== null; + } +} +} \ No newline at end of file diff --git a/src/pb2/CAuthentication_UpdateAuthSessionWithSteamGuardCode_Response.php b/src/pb2/CAuthentication_UpdateAuthSessionWithSteamGuardCode_Response.php new file mode 100644 index 0000000..bfa34c9 --- /dev/null +++ b/src/pb2/CAuthentication_UpdateAuthSessionWithSteamGuardCode_Response.php @@ -0,0 +1,45 @@ +reset(); + } + + /** + * Clears message values and sets default ones + * + * @return null + */ + public function reset() + { + } + + /** + * Returns field descriptors + * + * @return array + */ + public function fields() + { + return self::$fields; + } +} +} \ No newline at end of file diff --git a/src/pb2/EAuthSessionGuardType.php b/src/pb2/EAuthSessionGuardType.php new file mode 100644 index 0000000..0c30a03 --- /dev/null +++ b/src/pb2/EAuthSessionGuardType.php @@ -0,0 +1,38 @@ + self::k_EAuthSessionGuardType_Unknown, + 'k_EAuthSessionGuardType_None' => self::k_EAuthSessionGuardType_None, + 'k_EAuthSessionGuardType_EmailCode' => self::k_EAuthSessionGuardType_EmailCode, + 'k_EAuthSessionGuardType_DeviceCode' => self::k_EAuthSessionGuardType_DeviceCode, + 'k_EAuthSessionGuardType_DeviceConfirmation' => self::k_EAuthSessionGuardType_DeviceConfirmation, + 'k_EAuthSessionGuardType_EmailConfirmation' => self::k_EAuthSessionGuardType_EmailConfirmation, + 'k_EAuthSessionGuardType_MachineToken' => self::k_EAuthSessionGuardType_MachineToken, + ); + } +} +} \ No newline at end of file diff --git a/src/pb2/EAuthTokenPlatformType.php b/src/pb2/EAuthTokenPlatformType.php new file mode 100644 index 0000000..38f33c9 --- /dev/null +++ b/src/pb2/EAuthTokenPlatformType.php @@ -0,0 +1,32 @@ + self::k_EAuthTokenPlatformType_Unknown, + 'k_EAuthTokenPlatformType_SteamClient' => self::k_EAuthTokenPlatformType_SteamClient, + 'k_EAuthTokenPlatformType_WebBrowser' => self::k_EAuthTokenPlatformType_WebBrowser, + 'k_EAuthTokenPlatformType_MobileApp' => self::k_EAuthTokenPlatformType_MobileApp, + ); + } +} +} \ No newline at end of file diff --git a/src/pb2/ESessionPersistence.php b/src/pb2/ESessionPersistence.php new file mode 100644 index 0000000..c037288 --- /dev/null +++ b/src/pb2/ESessionPersistence.php @@ -0,0 +1,30 @@ + self::k_ESessionPersistence_Invalid, + 'k_ESessionPersistence_Ephemeral' => self::k_ESessionPersistence_Ephemeral, + 'k_ESessionPersistence_Persistent' => self::k_ESessionPersistence_Persistent, + ); + } +} +} \ No newline at end of file