diff --git a/composer.json b/composer.json index 2d6b46574d..fad962e414 100644 --- a/composer.json +++ b/composer.json @@ -63,7 +63,8 @@ "npm-asset/moment": "^2.24", "npm-asset/perfect-scrollbar": "0.6.16", "npm-asset/textcomplete": "^0.18.2", - "npm-asset/typeahead.js": "^0.11.1" + "npm-asset/typeahead.js": "^0.11.1", + "phpseclib/phpseclib": "^2.0" }, "repositories": [ { diff --git a/composer.lock b/composer.lock index 9f6f78d000..ebd434e39e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7d1fe40c28d815b56d0b5cb323860b26", + "content-hash": "ffe94190e166cebf80601fc3d6d26be0", "packages": [ { "name": "asika/simple-console", @@ -2474,6 +2474,111 @@ "homepage": "http://pear.php.net/package/Text_LanguageDetect", "time": "2020-05-17T12:19:40+00:00" }, + { + "name": "phpseclib/phpseclib", + "version": "2.0.29", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "497856a8d997f640b4a516062f84228a772a48a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/497856a8d997f640b4a516062f84228a772a48a8", + "reference": "497856a8d997f640b4a516062f84228a772a48a8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phing/phing": "~2.7", + "phpunit/phpunit": "^4.8.35|^5.7|^6.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "suggest": { + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2020-09-08T04:24:43+00:00" + }, { "name": "pragmarx/google2fa", "version": "v5.0.0", diff --git a/library/asn1.php b/library/asn1.php deleted file mode 100644 index cda96b6c8a..0000000000 --- a/library/asn1.php +++ /dev/null @@ -1,292 +0,0 @@ - 0x00, - 'ASN_APPLICATION' => 0x40, - 'ASN_CONTEXT' => 0x80, - 'ASN_PRIVATE' => 0xC0, - - 'ASN_PRIMITIVE' => 0x00, - 'ASN_CONSTRUCTOR' => 0x20, - - 'ASN_LONG_LEN' => 0x80, - 'ASN_EXTENSION_ID' => 0x1F, - 'ASN_BIT' => 0x80, - ); - - public static $ASN_TYPES = array( - 1 => 'ASN_BOOLEAN', - 2 => 'ASN_INTEGER', - 3 => 'ASN_BIT_STR', - 4 => 'ASN_OCTET_STR', - 5 => 'ASN_NULL', - 6 => 'ASN_OBJECT_ID', - 9 => 'ASN_REAL', - 10 => 'ASN_ENUMERATED', - 13 => 'ASN_RELATIVE_OID', - 48 => 'ASN_SEQUENCE', - 49 => 'ASN_SET', - 19 => 'ASN_PRINT_STR', - 22 => 'ASN_IA5_STR', - 23 => 'ASN_UTC_TIME', - 24 => 'ASN_GENERAL_TIME', - ); - - function __construct($v = false) - { - if (false !== $v) { - $this->asnData = $v; - if (is_array($this->asnData)) { - foreach ($this->asnData as $key => $value) { - if (is_object($value)) { - $this->asnData[$key]->setParent($this); - } - } - } else { - if (is_object($this->asnData)) { - $this->asnData->setParent($this); - } - } - } - } - - public function setParent($parent) - { - if (false !== $parent) { - $this->parent = $parent; - } - } - - /** - * This function will take the markers and types arrays and - * dynamically generate classes that extend this class for each one, - * and also define constants for them. - */ - public static function generateSubclasses() - { - define('ASN_BASE', 0); - foreach (self::$ASN_MARKERS as $name => $bit) - self::makeSubclass($name, $bit); - foreach (self::$ASN_TYPES as $bit => $name) - self::makeSubclass($name, $bit); - } - - /** - * Helper function for generateSubclasses() - */ - public static function makeSubclass($name, $bit) - { - define($name, $bit); - eval("class ".$name." extends ASN_BASE {}"); - } - - /** - * This function reset's the internal cursor used for value iteration. - */ - public function reset() - { - $this->cursor = 0; - } - - /** - * This function catches calls to get the value for the type, typeName, value, values, and data - * from the object. For type calls we just return the class name or the value of the constant that - * is named the same as the class. - */ - public function __get($name) - { - if ('type' == $name) { - // int flag of the data type - return constant(get_class($this)); - } elseif ('typeName' == $name) { - // name of the data type - return get_class($this); - } elseif ('value' == $name) { - // will always return one value and can be iterated over with: - // while ($v = $obj->value) { ... - // because $this->asnData["invalid key"] will return false - return is_array($this->asnData) ? $this->asnData[$this->cursor++] : $this->asnData; - } elseif ('values' == $name) { - // will always return an array - return is_array($this->asnData) ? $this->asnData : array($this->asnData); - } elseif ('data' == $name) { - // will always return the raw data - return $this->asnData; - } - } - - /** - * Parse an ASN.1 binary string. - * - * This function takes a binary ASN.1 string and parses it into it's respective - * pieces and returns it. It can optionally stop at any depth. - * - * @param string $string The binary ASN.1 String - * @param int $level The current parsing depth level - * @param int $maxLevel The max parsing depth level - * @return ASN_BASE The array representation of the ASN.1 data contained in $string - */ - public static function parseASNString($string=false, $level=1, $maxLevels=false){ - if (!class_exists('ASN_UNIVERSAL')) - self::generateSubclasses(); - if ($level>$maxLevels && $maxLevels) - return array(new ASN_BASE($string)); - $parsed = array(); - $endLength = strlen($string); - $bigLength = $length = $type = $dtype = $p = 0; - while ($p<$endLength){ - $type = ord($string[$p++]); - $dtype = ($type & 192) >> 6; - if ($type==0){ // if we are type 0, just continue - } else { - $length = ord($string[$p++]); - if (($length & ASN_LONG_LEN)==ASN_LONG_LEN){ - $tempLength = 0; - for ($x=0; $x<($length & (ASN_LONG_LEN-1)); $x++){ - $tempLength = @ord($string[$p++]) + ($tempLength * 256); - } - $length = $tempLength; - } - $data = substr($string, $p, intval($length)); - $parsed[] = self::parseASNData($type, $data, $level, $maxLevels); - $p = $p + $length; - } - } - return $parsed; - } - - /** - * Parse an ASN.1 field value. - * - * This function takes a binary ASN.1 value and parses it according to it's specified type - * - * @param int $type The type of data being provided - * @param string $data The raw binary data string - * @param int $level The current parsing depth - * @param int $maxLevels The max parsing depth - * @return mixed The data that was parsed from the raw binary data string - */ - public static function parseASNData($type, $data, $level, $maxLevels){ - $type = $type%50; // strip out context - switch ($type){ - default: - return new ASN_BASE($data); - case ASN_BOOLEAN: - return new ASN_BOOLEAN((bool)$data); - case ASN_INTEGER: - return new ASN_INTEGER(strtr(base64_encode($data),'+/','-_')); - case ASN_BIT_STR: - return new ASN_BIT_STR(self::parseASNString($data, $level+1, $maxLevels)); - case ASN_OCTET_STR: - return new ASN_OCTET_STR($data); - case ASN_NULL: - return new ASN_NULL(null); - case ASN_REAL: - return new ASN_REAL($data); - case ASN_ENUMERATED: - return new ASN_ENUMERATED(self::parseASNString($data, $level+1, $maxLevels)); - case ASN_RELATIVE_OID: // I don't really know how this works and don't have an example :-) - // so, lets just return it ... - return new ASN_RELATIVE_OID($data); - case ASN_SEQUENCE: - return new ASN_SEQUENCE(self::parseASNString($data, $level+1, $maxLevels)); - case ASN_SET: - return new ASN_SET(self::parseASNString($data, $level+1, $maxLevels)); - case ASN_PRINT_STR: - return new ASN_PRINT_STR($data); - case ASN_IA5_STR: - return new ASN_IA5_STR($data); - case ASN_UTC_TIME: - return new ASN_UTC_TIME($data); - case ASN_GENERAL_TIME: - return new ASN_GENERAL_TIME($data); - case ASN_OBJECT_ID: - return new ASN_OBJECT_ID(self::parseOID($data)); - } - } - - /** - * Parse an ASN.1 OID value. - * - * This takes the raw binary string that represents an OID value and parses it into its - * dot notation form. example - 1.2.840.113549.1.1.5 - * look up OID's here: http://www.oid-info.com/ - * (the multi-byte OID section can be done in a more efficient way, I will fix it later) - * - * @param string $data The raw binary data string - * @return string The OID contained in $data - */ - public static function parseOID($string){ - $ret = floor(ord($string[0])/40)."."; - $ret .= (ord($string[0]) % 40); - $build = array(); - $cs = 0; - - for ($i=1; $i127){ - $build[] = ord($string[$i])-ASN_BIT; - } elseif ($build){ - // do the build here for multibyte values - $build[] = ord($string[$i])-ASN_BIT; - // you know, it seems there should be a better way to do this... - $build = array_reverse($build); - $num = 0; - for ($x=0; $x> $x)) * $mult; - } else { - $value = ((($build[$x] & (ASN_BIT-1)) >> $x) ^ ($build[$x+1] << (7 - $x) & 255)) * $mult; - } - $num += $value; - } - $ret .= ".".$num; - $build = array(); // start over - } else { - $ret .= ".".$v; - $build = array(); - } - } - return $ret; - } - - public static function printASN($x, $indent=''){ - if (is_object($x)) { - echo $indent.$x->typeName."\n"; - if (ASN_NULL == $x->type) return; - if (is_array($x->data)) { - while ($d = $x->value) { - echo self::printASN($d, $indent.'. '); - } - $x->reset(); - } else { - echo self::printASN($x->data, $indent.'. '); - } - } elseif (is_array($x)) { - foreach ($x as $d) { - echo self::printASN($d, $indent); - } - } else { - if (preg_match('/[^[:print:]]/', $x)) // if we have non-printable characters that would - $x = base64_encode($x); // mess up the console, then print the base64 of them... - echo $indent.$x."\n"; - } - } - - -} - diff --git a/src/Module/PublicRSAKey.php b/src/Module/PublicRSAKey.php index 3d0423688f..7c46b63356 100644 --- a/src/Module/PublicRSAKey.php +++ b/src/Module/PublicRSAKey.php @@ -21,11 +21,13 @@ namespace Friendica\Module; -use ASN_BASE; use Friendica\BaseModule; use Friendica\DI; use Friendica\Model\User; use Friendica\Network\HTTPException\BadRequestException; +use Friendica\Util\Crypto; +use Friendica\Util\Strings; +use phpseclib\File\ASN1; /** * prints the public RSA key of a user @@ -49,18 +51,10 @@ class PublicRSAKey extends BaseModule throw new BadRequestException(); } - $lines = explode("\n", $user['spubkey']); - unset($lines[0]); - unset($lines[count($lines)]); - - $asnString = base64_decode(implode('', $lines)); - $asnBase = ASN_BASE::parseASNString($asnString); - - $m = $asnBase[0]->asnData[1]->asnData[0]->asnData[0]->asnData; - $e = $asnBase[0]->asnData[1]->asnData[0]->asnData[1]->asnData; + Crypto::pemToMe($user['spubkey'], $modulus, $exponent); header('Content-type: application/magic-public-key'); - echo 'RSA' . '.' . $m . '.' . $e; + echo 'RSA' . '.' . Strings::base64UrlEncode($modulus, true) . '.' . Strings::base64UrlEncode($exponent, true); exit(); } diff --git a/src/Protocol/Salmon.php b/src/Protocol/Salmon.php index 88c342a87c..169a4d0cbd 100644 --- a/src/Protocol/Salmon.php +++ b/src/Protocol/Salmon.php @@ -229,7 +229,7 @@ class Salmon */ public static function salmonKey($pubkey) { - Crypto::pemToMe($pubkey, $m, $e); - return 'RSA' . '.' . Strings::base64UrlEncode($m, true) . '.' . Strings::base64UrlEncode($e, true); + Crypto::pemToMe($pubkey, $modulus, $exponent); + return 'RSA' . '.' . Strings::base64UrlEncode($modulus, true) . '.' . Strings::base64UrlEncode($exponent, true); } } diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index 8adacf7104..ab669823b5 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -21,12 +21,12 @@ namespace Friendica\Util; -use ASN_BASE; use ASNValue; use Friendica\Core\Hook; use Friendica\Core\Logger; use Friendica\Core\System; use Friendica\DI; +use phpseclib\Crypt\RSA; /** * Crypto class @@ -86,27 +86,6 @@ class Crypto return $result; } - /** - * @param string $Der der formatted string - * @return string - */ - private static function DerToRsa($Der) - { - //Encode: - $Der = base64_encode($Der); - //Split lines: - $lines = str_split($Der, 64); - $body = implode("\n", $lines); - //Get title: - $title = 'RSA PUBLIC KEY'; - //Add wrapping: - $result = "-----BEGIN {$title}-----\n"; - $result .= $body . "\n"; - $result .= "-----END {$title}-----\n"; - - return $result; - } - /** * @param string $Modulus modulo * @param string $PublicExponent exponent @@ -136,26 +115,6 @@ class Crypto return $PublicDER; } - /** - * @param string $Modulus modulo - * @param string $PublicExponent exponent - * @return string - */ - private static function pkcs1Encode($Modulus, $PublicExponent) - { - //Encode key sequence - $modulus = new ASNValue(ASNValue::TAG_INTEGER); - $modulus->SetIntBuffer($Modulus); - $publicExponent = new ASNValue(ASNValue::TAG_INTEGER); - $publicExponent->SetIntBuffer($PublicExponent); - $keySequenceItems = [$modulus, $publicExponent]; - $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE); - $keySequence->SetSequence($keySequenceItems); - //Encode bit string - $bitStringValue = $keySequence->Encode(); - return $bitStringValue; - } - /** * @param string $m modulo * @param string $e exponent @@ -169,79 +128,37 @@ class Crypto } /** - * @param string $key key - * @param string $m modulo reference - * @param object $e exponent reference + * Transform RSA public keys to standard PEM output + * + * @param string $key A RSA public key + * + * @return string The PEM output of this key + */ + public static function rsaToPem(string $key) + { + $publicKey = new RSA(); + $publicKey->setPublicKey($key); + + return $publicKey->getPublicKey(RSA::PUBLIC_FORMAT_PKCS8); + } + + /** + * Extracts the modulo and exponent reference from a public PEM key + * + * @param string $key public PEM key + * @param string $modulus (ref) modulo reference + * @param string $exponent (ref) exponent reference + * * @return void - * @throws \Exception */ - private static function pubRsaToMe($key, &$m, &$e) + public static function pemToMe(string $key, string &$modulus, string &$exponent) { - $lines = explode("\n", $key); - unset($lines[0]); - unset($lines[count($lines)]); - $x = base64_decode(implode('', $lines)); + $publicKey = new RSA(); + $publicKey->loadKey($key); + $publicKey->setPublicKey(); - $r = ASN_BASE::parseASNString($x); - - $m = Strings::base64UrlDecode($r[0]->asnData[0]->asnData); - $e = Strings::base64UrlDecode($r[0]->asnData[1]->asnData); - } - - /** - * @param string $key key - * @return string - * @throws \Exception - */ - public static function rsaToPem($key) - { - self::pubRsaToMe($key, $m, $e); - return self::meToPem($m, $e); - } - - /** - * @param string $key key - * @return string - * @throws \Exception - */ - private static function pemToRsa($key) - { - self::pemToMe($key, $m, $e); - return self::meToRsa($m, $e); - } - - /** - * @param string $key key - * @param string $m modulo reference - * @param string $e exponent reference - * @return void - * @throws \Exception - */ - public static function pemToMe($key, &$m, &$e) - { - $lines = explode("\n", $key); - unset($lines[0]); - unset($lines[count($lines)]); - $x = base64_decode(implode('', $lines)); - - $r = ASN_BASE::parseASNString($x); - - if (isset($r[0])) { - $m = Strings::base64UrlDecode($r[0]->asnData[1]->asnData[0]->asnData[0]->asnData); - $e = Strings::base64UrlDecode($r[0]->asnData[1]->asnData[0]->asnData[1]->asnData); - } - } - - /** - * @param string $m modulo - * @param string $e exponent - * @return string - */ - private static function meToRsa($m, $e) - { - $der = self::pkcs1Encode($m, $e); - $key = self::DerToRsa($der); - return $key; + $modulus = $publicKey->modulus->toBytes(); + $exponent = $publicKey->exponent->toBytes(); } /** @@ -312,42 +229,6 @@ class Crypto return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); } - /** - * Encrypt a string with 'aes-256-ctr' cipher method. - * - * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php - * - * @param string $data - * @param string $key The key used for encryption. - * @param string $iv A non-NULL Initialization Vector. - * - * @return string|boolean Encrypted string or false on failure. - */ - private static function encryptAES256CTR($data, $key, $iv) - { - $key = substr($key, 0, 32); - $iv = substr($iv, 0, 16); - return openssl_encrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); - } - - /** - * Decrypt a string with 'aes-256-ctr' cipher method. - * - * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php - * - * @param string $data - * @param string $key The key used for decryption. - * @param string $iv A non-NULL Initialization Vector. - * - * @return string|boolean Decrypted string or false on failure. - */ - private static function decryptAES256CTR($data, $key, $iv) - { - $key = substr($key, 0, 32); - $iv = substr($iv, 0, 16); - return openssl_decrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0")); - } - /** * * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php diff --git a/tests/datasets/crypto/rsa/diaspora-public-pem b/tests/datasets/crypto/rsa/diaspora-public-pem new file mode 100644 index 0000000000..09dd1640d3 --- /dev/null +++ b/tests/datasets/crypto/rsa/diaspora-public-pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDReSjW7O4u4tK+UGKwogyw4Dok +j1Z4f70INc4CTlHk2sngzTa3uMzk1EU+9nYigqMfI1/DYoSCC0ZqikvZVGkrMJj6 +khM7orTasR4Av9Sn54rOQaM+raUC3JXd9AdkdXx1IBC71cAXVqIg/ERCrrUpxDxc +E6VXs4mFWpDHJ4q01QIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/tests/datasets/crypto/rsa/diaspora-public-rsa-base64 b/tests/datasets/crypto/rsa/diaspora-public-rsa-base64 new file mode 100644 index 0000000000..ba835a4711 --- /dev/null +++ b/tests/datasets/crypto/rsa/diaspora-public-rsa-base64 @@ -0,0 +1 @@ +LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tDQpNSUdKQW9HQkFORjVLTmJzN2k3aTByNVFZckNpRExEZ09pU1BWbmgvdlFnMXpnSk9VZVRheWVETk5yZTR6T1RVDQpSVDcyZGlLQ294OGpYOE5paElJTFJtcUtTOWxVYVNzd21QcVNFenVpdE5xeEhnQy8xS2ZuaXM1Qm96NnRwUUxjDQpsZDMwQjJSMWZIVWdFTHZWd0JkV29pRDhSRUt1dFNuRVBGd1RwVmV6aVlWYWtNY25pclRWQWdNQkFBRT0NCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0 \ No newline at end of file diff --git a/tests/src/Util/CryptoTest.php b/tests/src/Util/CryptoTest.php index 9f232210c9..51305337a9 100644 --- a/tests/src/Util/CryptoTest.php +++ b/tests/src/Util/CryptoTest.php @@ -21,6 +21,8 @@ */ namespace Friendica\Util; +use phpseclib\Crypt\RSA; +use phpseclib\Math\BigInteger; use PHPUnit\Framework\TestCase; class CryptoTest extends TestCase @@ -32,7 +34,7 @@ class CryptoTest extends TestCase private function assertRandomInt($min, $max) { global $phpMock; - $phpMock['random_int'] = function($mMin, $mMax) use ($min, $max) { + $phpMock['random_int'] = function ($mMin, $mMax) use ($min, $max) { $this->assertEquals($min, $mMin); $this->assertEquals($max, $mMax); return 1; @@ -51,6 +53,50 @@ class CryptoTest extends TestCase $this->assertEquals(8, strlen($test)); $this->assertEquals(11111111, $test); } + + public function dataRsa() + { + return [ + 'diaspora' => [ + 'key' => file_get_contents(__DIR__ . '/../../datasets/crypto/rsa/diaspora-public-rsa-base64'), + 'expected' => file_get_contents(__DIR__ . '/../../datasets/crypto/rsa/diaspora-public-pem'), + ], + ]; + } + + /** + * @dataProvider dataRsa + */ + public function testPubRsaToMe(string $key, string $expected) + { + $this->assertEquals($expected, Crypto::rsaToPem(base64_decode($key))); + } + + + public function datePem() + { + return [ + 'diaspora' => [ + 'key' => file_get_contents(__DIR__ . '/../../datasets/crypto/rsa/diaspora-public-pem'), + ], + ]; + } + + /** + * @dataProvider datePem + */ + public function testPemToMe(string $key) + { + Crypto::pemToMe($key, $m, $e); + + $expectedRSA = new RSA(); + $expectedRSA->loadKey([ + 'e' => new BigInteger($e, 256), + 'n' => new BigInteger($m, 256) + ]); + + $this->assertEquals($expectedRSA->getPublicKey(), $key); + } } /**