.gitattributes000064400000000146150736751310007450 0ustar00/doc export-ignore /examples export-ignore /tests export-ignore .github/workflows/test.yml000064400000004224150736751310011655 0ustar00name: Tests on: push: pull_request: jobs: test: runs-on: ${{ matrix.operating-system }} strategy: matrix: operating-system: ['ubuntu-20.04', 'ubuntu-18.04'] php-versions: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] phpunit-versions: ['latest'] steps: - name: Checkout uses: actions/checkout@v2 - name: Checkout submodules uses: textbook/git-checkout-submodule-action@master - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} extensions: gmp coverage: none - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache dependencies uses: actions/cache@v2 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --prefer-dist - name: Run tests run: make phpunit-full-ci coverage: runs-on: ${{ matrix.operating-system }} strategy: matrix: operating-system: ['ubuntu-20.04'] php-versions: ['7.4'] steps: - name: Checkout uses: actions/checkout@v2 - name: Checkout submodules uses: textbook/git-checkout-submodule-action@master - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} extensions: gmp, xdebug - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache dependencies uses: actions/cache@v2 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --prefer-dist - name: Run tests run: make phpunit-ci - name: Upload coverage to Scrutinizer run: make scrutinizer .gitignore000075500000000044150736751310006545 0ustar00tests/output vendor/ composer.lock .gitmodules000064400000000162150736751310006730 0ustar00[submodule "tests/import/wycheproof"] path = tests/import/wycheproof url = https://github.com/google/wycheproof .scrutinizer.yml000064400000004771150736751310007747 0ustar00build: nodes: php-scrutinizer-run: project_setup: override: true tests: override: [php-scrutinizer-run] environment: mysql: false redis: false postgresql: false php: version: 7.2 ini: 'zend.assertions': '1' 'assert.exception': '1' dependencies: override: - command: 'composer install' tests: override: true project_setup: override: true filter: paths: - src/* excluded_paths: - examples/* - tests/* - vendor/* tools: php_cpd: true php_pdepend: true php_analyzer: true php_sim: true php_changetracking: true php_mess_detector: true php_code_sniffer: true sensiolabs_security_checker: true php_code_coverage: true php_pdepend: excluded_dirs: [vendor, doc, examples, build, tests] external_code_coverage: timeout: 1300 runs: 1 changetracking: bug_patterns: ["\bfix(?:es|ed)?\b"] feature_patterns: ["\badd(?:s|ed)?\b", "\bimplement(?:s|ed)?\b"] build_failure_conditions: # No new critical issue is introduced (existing ones are tolerated) - 'issues.severity(>= CRITICAL).new.exists' # Code Coverage drops below 60% - 'project.metric("scrutinizer.test_coverage", < 0.80)' # Code Coverage decreased from previous inspection by more than 1% - 'project.metric_change("scrutinizer.test_coverage", < -0.01)' checks: php: simplify_boolean_return: true return_doc_comments: true return_doc_comment_if_not_inferrable: true remove_extra_empty_lines: true properties_in_camelcaps: true phpunit_assertions: true parameters_in_camelcaps: true parameter_doc_comments: true param_doc_comment_if_not_inferrable: true overriding_parameter: true no_short_variable_names: minimum: '3' no_short_method_names: minimum: '3' no_long_variable_names: maximum: '20' no_goto: true fix_use_statements: remove_unused: true preserve_multiple: false preserve_blanklines: false order_alphabetically: false fix_line_ending: true check_method_contracts: verify_interface_like_constraints: true verify_documented_constraints: true verify_parent_constraints: true Makefile000064400000002516150736751310006220 0ustar00test: phpunit phpcs .PHONY: test phpunit phpcs pretest: if [ ! -d vendor ] || [ ! -f composer.lock ]; then composer install; else echo "Already have dependencies"; fi phpunit: pretest mkdir -p tests/output vendor/bin/phpunit --coverage-text --coverage-clover=tests/output/coverage.clover --coverage-html=tests/output/Results test-examples: ./validate_examples.sh phpunit-ci: pretest vendor/bin/phpunit --coverage-text --coverage-clover=tests/output/coverage.clover phpunit-full-ci: pretest vendor/bin/phpunit -c phpunit.full.xml --coverage-text --coverage-clover=tests/output/coverage.clover ifndef STRICT STRICT = 0 endif ifeq "$(STRICT)" "1" phpcs: pretest vendor/bin/phpcs --standard=PSR2 src tests/unit/ else phpcs: pretest vendor/bin/phpcs --standard=PSR2 -n src tests/unit/ endif phpcbf: pretest vendor/bin/phpcbf --standard=PSR2 -n src tests/unit/ ocular: wget https://scrutinizer-ci.com/ocular.phar ifdef OCULAR_TOKEN scrutinizer: ocular @php ocular.phar code-coverage:upload --format=php-clover tests/output/coverage.clover --access-token=$(OCULAR_TOKEN); else scrutinizer: ocular php ocular.phar code-coverage:upload --format=php-clover tests/output/coverage.clover; endif clean: clean-env clean-deps clean-env: rm -rf coverage.clover rm -rf ocular.phar rm -rf tests/output/ clean-deps: rm -rf vendor/ README.md000064400000005323150736751310006036 0ustar00## Pure PHP Elliptic Curve DSA and DH [![Build Status](https://travis-ci.org/phpecc/phpecc.svg?branch=master)](https://travis-ci.org/phpecc/phpecc) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/phpecc/phpecc/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/phpecc/phpecc?branch=master) [![Code Coverage](https://scrutinizer-ci.com/g/phpecc/phpecc/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/phpecc/phpecc/?branch=master) [![Latest Stable Version](https://poser.pugx.org/mdanter/ecc/v/stable.png)](https://packagist.org/packages/mdanter/ecc) [![Total Downloads](https://poser.pugx.org/mdanter/ecc/downloads.png)](https://packagist.org/packages/mdanter/ecc) [![Latest Unstable Version](https://poser.pugx.org/mdanter/ecc/v/unstable.png)](https://packagist.org/packages/mdanter/ecc) [![License](https://poser.pugx.org/mdanter/ecc/license.png)](https://packagist.org/packages/mdanter/ecc) ### Information This library is a rewrite/update of Matyas Danter's ECC library. All credit goes to him. For more information on Elliptic Curve Cryptography please read [this fine article](http://www.matyasdanter.com/2010/12/elliptic-curve-php-oop-dsa-and-diffie-hellman/). The library supports the following curves: - secp112r1 - secp256k1 - nistp192 - nistp224 - nistp256 / secp256r1 - nistp384 / secp384r1 - nistp521 During ECDSA, a random value `k` is required. It is acceptable to use a true RNG to generate this value, but should the same `k` value ever be repeatedly used for a key, an attacker can recover that signing key. The HMAC random generator can derive a deterministic k value from the message hash and private key, voiding this concern. The library uses a non-branching Montgomery ladder for scalar multiplication, as it's constant time and avoids secret dependant branches. ### License This package is released under the MIT license. ### Requirements * PHP 7.0+ or PHP 8.0+ * composer * ext-gmp Support for older PHP versions: * v0.4.x: php ^5.6|<7.2 * v0.5.x: php ^7.0 * v1.0.x: php ^7.0|^8.0 ### Installation You can install this library via Composer : `composer require mdanter/ecc:^1.0` ### Contribute When sending in pull requests, please make sure to run the `make` command. The default target runs all PHPUnit and PHPCS tests. All tests must validate for your contribution to be accepted. It's also always a good idea to check the results of the [Scrutinizer analysis](https://scrutinizer-ci.com/g/phpecc/phpecc/) for your pull requests. ### Usage Examples: * [Key generation](./examples/key_generation.php) * [ECDH exchange](./examples/ecdh_exchange.php) * [Signature creation](./examples/creating_signature.php) * [Signature verification](./examples/verify_signature.php) composer.json000064400000002535150736751310007303 0ustar00{ "name": "mdanter/ecc", "description": "PHP Elliptic Curve Cryptography library", "type": "library", "homepage": "https://github.com/phpecc/phpecc", "keywords": ["secp256k1", "secp256r1", "nistp192", "nistp224", "nistp256", "nistp384", "nistp521", "ECDSA", "diffie", "hellman", "ECDH", "elliptic", "curve", "phpecc"], "license": "MIT", "authors": [ { "name": "Matyas Danter", "homepage": "http://matejdanter.com/", "role": "Author" }, { "name": "Thibaud Fabre", "email": "thibaud@aztech.io", "homepage": "http://aztech.io", "role": "Maintainer" }, { "name": "Thomas Kerin", "email": "afk11@users.noreply.github.com", "role": "Maintainer" } ], "require": { "php": "^7.0||^8.0", "ext-gmp": "*", "fgrosse/phpasn1": "^2.0" }, "require-dev": { "phpunit/phpunit": "^6.0||^8.0||^9.0", "squizlabs/php_codesniffer": "^2.0", "symfony/yaml": "^2.6|^3.0" }, "autoload": { "psr-4": { "Mdanter\\Ecc\\": "src/" } }, "autoload-dev": { "psr-4": { "Mdanter\\Ecc\\Tests\\": "tests/unit", "Mdanter\\Ecc\\WycheProof\\": "tests/wycheproof" } } } phpunit.full.xml000064400000002242150736751310007726 0ustar00 ./tests/wycheproof ./tests/unit src/ phpunit.xml000075500000001657150736751310007001 0ustar00 ./tests/unit src/ src/Crypto/EcDH/EcDH.php000064400000011775150736751310010655 0ustar00adapter = $adapter; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\EcDH\EcDHInterface::calculateSharedKey() */ public function calculateSharedKey(): \GMP { $this->calculateKey(); return $this->secretKey->getPoint()->getX(); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\EcDH\EcDHInterface::createMultiPartyKey() */ public function createMultiPartyKey(): PublicKeyInterface { $this->calculateKey(); return $this->secretKey; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\EcDH\EcDHInterface::setRecipientKey() */ public function setRecipientKey(PublicKeyInterface $key = null) { $this->recipientKey = $key; return $this; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\EcDH\EcDHInterface::setSenderKey() */ public function setSenderKey(PrivateKeyInterface $key) { $this->senderKey = $key; return $this; } /** * */ private function calculateKey() { $this->checkExchangeState(); if ($this->secretKey === null) { try { // Multiply our secret with recipients public key $point = $this->recipientKey->getPoint()->mul($this->senderKey->getSecret()); // Ensure we completed a valid exchange, ensure we can create a // public key instance for the shared secret using our generator. $this->secretKey = $this->senderKey->getPoint()->getPublicKeyFrom($point->getX(), $point->getY()); } catch (\Exception $e) { throw new ExchangeException("Invalid ECDH exchange", 0, $e); } } } /** * Verifies that the shared secret is known, or that the required keys are available * to calculate the shared secret. * @throws \RuntimeException when the exchange has not been made. */ private function checkExchangeState() { if ($this->secretKey !== null) { return; } if ($this->senderKey === null) { throw new ExchangeException('Sender key not set.'); } if ($this->recipientKey === null) { throw new ExchangeException('Recipient key not set.'); } // Check the point exists on our curve. $point = $this->recipientKey->getPoint(); if (!$this->senderKey->getPoint()->getCurve()->contains($point->getX(), $point->getY())) { throw new ExchangeException("Invalid ECDH exchange - Point does not exist on our curve"); } } } src/Crypto/EcDH/EcDHInterface.php000064400000004124150736751310012464 0ustar00adapter = $adapter; $this->generator = $generator; $this->secretMultiplier = $secretMultiplier; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\Key\PrivateKeyInterface::getPublicKey() */ public function getPublicKey(): PublicKeyInterface { return new PublicKey($this->adapter, $this->generator, $this->generator->mul($this->secretMultiplier)); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\Key\PrivateKeyInterface::getPoint() */ public function getPoint(): GeneratorPoint { return $this->generator; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\Key\PrivateKeyInterface::getCurve() */ public function getCurve(): CurveFpInterface { return $this->generator->getCurve(); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\Key\PrivateKeyInterface::getSecret() */ public function getSecret(): \GMP { return $this->secretMultiplier; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\Key\PrivateKeyInterface::createExchange() */ public function createExchange(PublicKeyInterface $recipient = null): EcDHInterface { $ecdh = new EcDH($this->adapter); $ecdh ->setSenderKey($this) ->setRecipientKey($recipient); return $ecdh; } } src/Crypto/Key/PrivateKeyInterface.php000064400000003666150736751310014003 0ustar00curve = $generator->getCurve(); $this->generator = $generator; $this->point = $point; $this->adapter = $adapter; // step 1: not point at infinity. if ($point->isInfinity()) { throw new PublicKeyException($generator, $point, "Cannot use point at infinity for public key"); } // step 2 full & partial public key validation routine if ($adapter->cmp($point->getX(), gmp_init(0, 10)) < 0 || $adapter->cmp($this->curve->getPrime(), $point->getX()) < 0 || $adapter->cmp($point->getY(), gmp_init(0, 10)) < 0 || $adapter->cmp($this->curve->getPrime(), $point->getY()) < 0 ) { throw new PublicKeyException($generator, $point, "Point has x and y out of range."); } // Sanity check. Point (x,y) values are qualified against it's // generator and curve. Here we ensure the Point and Generator // are the same. if (!$generator->getCurve()->equals($point->getCurve())) { throw new PublicKeyException($generator, $point, "Curve for given point not in common with GeneratorPoint"); } } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\Key\PublicKeyInterface::getCurve() */ public function getCurve(): CurveFpInterface { return $this->curve; } /** * {$inheritDoc} * @see \Mdanter\Ecc\Crypto\Key\PublicKeyInterface::getGenerator() */ public function getGenerator(): GeneratorPoint { return $this->generator; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\Key\PublicKeyInterface::getPoint() */ public function getPoint(): PointInterface { return $this->point; } } src/Crypto/Key/PublicKeyInterface.php000064400000003504150736751310013576 0ustar00 20, 'sha224' => 28, 'sha256' => 32, 'sha384' => 48, 'sha512' => 64, ]; /** * @var GmpMathInterface */ private $adapter; /** * @var string */ private $algorithm; /** * SignHasher constructor. * @param string $algorithm * @param GmpMathInterface|null $math */ public function __construct(string $algorithm, GmpMathInterface $math = null) { if (!array_key_exists($algorithm, self::$sizeMap)) { throw new \InvalidArgumentException("Unsupported hashing algorithm"); } $this->algorithm = $algorithm; $this->adapter = $math ?: EccFactory::getAdapter(); } /** * @return string */ public function getAlgorithm(): string { return $this->algorithm; } /** * @return int */ public function getLengthInBytes(): int { return self::$sizeMap[$this->algorithm]; } /** * @param string $data * @return string */ public function makeRawHash(string $data): string { return hash($this->algorithm, $data, false); } /** * @param \GMP $hash * @param GeneratorPoint $G * @return \GMP */ public function truncateForECDSA(\GMP $hash, GeneratorPoint $G) { $hashBits = gmp_strval($hash, 2); if (BinaryString::length($hashBits) < self::$sizeMap[$this->algorithm] * 8) { $hashBits = str_pad($hashBits, self::$sizeMap[$this->algorithm] * 8, '0', STR_PAD_LEFT); } return gmp_init(BinaryString::substring($hashBits, 0, NumberSize::bnNumBits($this->adapter, $G->getOrder())), 2); } /** * @param string $data * @param GeneratorPoint $G * @return \GMP */ public function makeHash(string $data, GeneratorPoint $G): \GMP { $hash = gmp_init($this->makeRawHash($data), 16); return $this->truncateForECDSA($hash, $G); } } src/Crypto/Signature/Signature.php000064400000004140150736751310013235 0ustar00r = $r; $this->s = $s; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\Signature\SignatureInterface::getR() */ public function getR(): \GMP { return $this->r; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Crypto\Signature\SignatureInterface::getS() */ public function getS(): \GMP { return $this->s; } } src/Crypto/Signature/SignatureInterface.php000064400000003245150736751310015063 0ustar00adapter = $adapter; } /** * @param PrivateKeyInterface $key * @param \GMP $truncatedHash - hash truncated for use in ECDSA * @param \GMP $randomK * @return SignatureInterface */ public function sign(PrivateKeyInterface $key, \GMP $truncatedHash, \GMP $randomK): SignatureInterface { $math = $this->adapter; $generator = $key->getPoint(); $modMath = $math->getModularArithmetic($generator->getOrder()); $k = $math->mod($randomK, $generator->getOrder()); $p1 = $generator->mul($k); $r = $p1->getX(); $zero = gmp_init(0, 10); if ($math->equals($r, $zero)) { throw new \RuntimeException("Error: random number R = 0"); } $s = $modMath->div($modMath->add($truncatedHash, $math->mul($key->getSecret(), $r)), $k); if ($math->equals($s, $zero)) { throw new \RuntimeException("Error: random number S = 0"); } return new Signature($r, $s); } /** * @param PublicKeyInterface $key * @param SignatureInterface $signature * @param \GMP $hash * @return bool */ public function verify(PublicKeyInterface $key, SignatureInterface $signature, \GMP $hash): bool { $generator = $key->getGenerator(); $n = $generator->getOrder(); $r = $signature->getR(); $s = $signature->getS(); $math = $this->adapter; $one = gmp_init(1, 10); if ($math->cmp($r, $one) < 0 || $math->cmp($r, $math->sub($n, $one)) > 0) { return false; } if ($math->cmp($s, $one) < 0 || $math->cmp($s, $math->sub($n, $one)) > 0) { return false; } $modMath = $math->getModularArithmetic($n); $c = $math->inverseMod($s, $n); $u1 = $modMath->mul($hash, $c); $u2 = $modMath->mul($r, $c); $xy = $generator->mul($u1)->add($key->getPoint()->mul($u2)); $v = $math->mod($xy->getX(), $n); return BinaryString::constantTimeCompare($math->toString($v), $math->toString($r)); } } src/Curves/CurveFactory.php000064400000007115150736751310011743 0ustar00curve192(); case NistCurve::NAME_P224: return $nistFactory->curve224(); case NistCurve::NAME_P256: return $nistFactory->curve256(); case NistCurve::NAME_P384: return $nistFactory->curve384(); case NistCurve::NAME_P521: return $nistFactory->curve521(); case SecgCurve::NAME_SECP_112R1: return $secpFactory->curve112r1(); case SecgCurve::NAME_SECP_192K1: return $secpFactory->curve192k1(); case SecgCurve::NAME_SECP_256K1: return $secpFactory->curve256k1(); case SecgCurve::NAME_SECP_256R1: return $secpFactory->curve256r1(); case SecgCurve::NAME_SECP_384R1: return $secpFactory->curve384r1(); default: $error = new UnsupportedCurveException('Unknown curve.'); $error->setCurveName($name); throw $error; } } /** * @param string $name * @return GeneratorPoint */ public static function getGeneratorByName(string $name): GeneratorPoint { $adapter = MathAdapterFactory::getAdapter(); $nistFactory = self::getNistFactory($adapter); $secpFactory = self::getSecpFactory($adapter); switch ($name) { case NistCurve::NAME_P192: return $nistFactory->generator192(); case NistCurve::NAME_P224: return $nistFactory->generator224(); case NistCurve::NAME_P256: return $nistFactory->generator256(); case NistCurve::NAME_P384: return $nistFactory->generator384(); case NistCurve::NAME_P521: return $nistFactory->generator521(); case SecgCurve::NAME_SECP_112R1: return $secpFactory->generator112r1(); case SecgCurve::NAME_SECP_192K1: return $secpFactory->generator192k1(); case SecgCurve::NAME_SECP_256K1: return $secpFactory->generator256k1(); case SecgCurve::NAME_SECP_256R1: return $secpFactory->generator256r1(); case SecgCurve::NAME_SECP_384R1: return $secpFactory->generator384r1(); default: $error = new UnsupportedCurveException('Unknown generator.'); $error->setCurveName($name); throw $error; } } /** * @param GmpMathInterface $math * @return NistCurve */ private static function getNistFactory(GmpMathInterface $math): NistCurve { return new NistCurve($math); } /** * @param GmpMathInterface $math * @return SecgCurve */ private static function getSecpFactory(GmpMathInterface $math): SecgCurve { return new SecgCurve($math); } } src/Curves/NamedCurveFp.php000064400000001326150736751310011644 0ustar00name = $name; parent::__construct($parameters, $adapter); } /** * @return string */ public function getName(): string { return $this->name; } } src/Curves/NistCurve.php000064400000020373150736751310011252 0ustar00adapter = $adapter; } /** * Returns an NIST P-192 curve. * * @return NamedCurveFp */ public function curve192(): NamedCurveFp { $p = gmp_init('6277101735386680763835789423207666416083908700390324961279', 10); $b = gmp_init('64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1', 16); $parameters = new CurveParameters(192, $p, gmp_init('-3', 10), $b); return new NamedCurveFp(self::NAME_P192, $parameters, $this->adapter); } /** * Returns an NIST P-192 generator. * * @param RandomNumberGeneratorInterface $randomGenerator * @return GeneratorPoint */ public function generator192(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint { $curve = $this->curve192(); $order = gmp_init('6277101735386680763835789423176059013767194773182842284081', 10); $x = gmp_init('188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012', 16); $y = gmp_init('07192b95ffc8da78631011ed6b24cdd573f977a11e794811', 16); return $curve->getGenerator($x, $y, $order, $randomGenerator); } /** * Returns an NIST P-224 curve * * @return NamedCurveFp */ public function curve224(): NamedCurveFp { $p = gmp_init('26959946667150639794667015087019630673557916260026308143510066298881', 10); $b = gmp_init('b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4', 16); $parameters = new CurveParameters(224, $p, gmp_init(-3, 10), $b); return new NamedCurveFp(self::NAME_P224, $parameters, $this->adapter); } /** * Returns an NIST P-224 generator. * * @param RandomNumberGeneratorInterface $randomGenerator * @return GeneratorPoint */ public function generator224(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint { $curve = $this->curve224(); $order = gmp_init('26959946667150639794667015087019625940457807714424391721682722368061', 10); $x = gmp_init('b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21', 16); $y = gmp_init('bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34', 16); return $curve->getGenerator($x, $y, $order, $randomGenerator); } /** * Returns an NIST P-256 curve. * * @return NamedCurveFp */ public function curve256(): NamedCurveFp { $p = gmp_init('115792089210356248762697446949407573530086143415290314195533631308867097853951', 10); $b = gmp_init('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b', 16); $parameters = new CurveParameters(256, $p, gmp_init(-3, 10), $b); return new NamedCurveFp(self::NAME_P256, $parameters, $this->adapter); } /** * Returns an NIST P-256 generator. * * @param RandomNumberGeneratorInterface $randomGenerator * @return GeneratorPoint */ public function generator256(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint { $curve = $this->curve256(); $order = gmp_init('115792089210356248762697446949407573529996955224135760342422259061068512044369', 10); $x = gmp_init('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296', 16); $y = gmp_init('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5', 16); return $curve->getGenerator($x, $y, $order, $randomGenerator); } /** * Returns an NIST P-384 curve. * * @return NamedCurveFp */ public function curve384(): NamedCurveFp { $p = gmp_init('39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319', 10); $b = gmp_init('0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef', 16); $parameters = new CurveParameters(384, $p, gmp_init(-3, 10), $b); return new NamedCurveFp(self::NAME_P384, $parameters, $this->adapter); } /** * Returns an NIST P-384 generator. * * @param RandomNumberGeneratorInterface $randomGenerator * @return GeneratorPoint */ public function generator384(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint { $curve = $this->curve384(); $order = gmp_init('39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643', 10); $x = gmp_init('0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7', 16); $y = gmp_init('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f', 16); return $curve->getGenerator($x, $y, $order, $randomGenerator); } /** * Returns an NIST P-521 curve. * * @return NamedCurveFp */ public function curve521(): NamedCurveFp { $p = gmp_init('6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151', 10); $b = gmp_init('0x051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00', 16); $parameters = new CurveParameters(521, $p, gmp_init(-3, 10), $b); return new NamedCurveFp(self::NAME_P521, $parameters, $this->adapter); } /** * Returns an NIST P-521 generator. * * @param RandomNumberGeneratorInterface $randomGenerator * @return GeneratorPoint */ public function generator521(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint { $curve = $this->curve521(); $order = gmp_init('6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449', 10); $x = gmp_init('0xc6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66', 16); $y = gmp_init('0x11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650', 16); return $curve->getGenerator($x, $y, $order, $randomGenerator); } } src/Curves/SecgCurve.php000075500000016550150736751310011223 0ustar00adapter = $adapter; } /** * @return NamedCurveFp */ public function curve112r1(): NamedCurveFp { $p = gmp_init('0xDB7C2ABF62E35E668076BEAD208B', 16); $a = gmp_init('0xDB7C2ABF62E35E668076BEAD2088', 16); $b = gmp_init('0x659EF8BA043916EEDE8911702B22', 16); $parameters = new CurveParameters(112, $p, $a, $b); return new NamedCurveFp(self::NAME_SECP_112R1, $parameters, $this->adapter); } /** * @param RandomNumberGeneratorInterface $randomGenerator * @return GeneratorPoint */ public function generator112r1(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint { $curve = $this->curve112r1(); $order = gmp_init('0xDB7C2ABF62E35E7628DFAC6561C5', 16); $x = gmp_init('0x09487239995A5EE76B55F9C2F098', 16); $y = gmp_init('0xA89CE5AF8724C0A23E0E0FF77500', 16); return $curve->getGenerator($x, $y, $order, $randomGenerator); } /** * @return NamedCurveFp */ public function curve192k1(): NamedCurveFp { $p = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37', 16); $a = gmp_init(0, 10); $b = gmp_init(3, 10); $parameters = new CurveParameters(192, $p, $a, $b); return new NamedCurveFp(self::NAME_SECP_192K1, $parameters, $this->adapter); } /** * @param RandomNumberGeneratorInterface $randomGenerator * @return \Mdanter\Ecc\Primitives\GeneratorPoint */ public function generator192k1(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint { $curve = $this->curve192k1(); $order = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D', 16); $x = gmp_init('0xDB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D', 16); $y = gmp_init('0x9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D', 16); return $curve->getGenerator($x, $y, $order, $randomGenerator); } /** * @return NamedCurveFp */ public function curve256k1(): NamedCurveFp { $p = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', 16); $a = gmp_init(0, 10); $b = gmp_init(7, 10); $parameters = new CurveParameters(256, $p, $a, $b); return new NamedCurveFp(self::NAME_SECP_256K1, $parameters, $this->adapter); } /** * @param RandomNumberGeneratorInterface $randomGenerator * @return GeneratorPoint */ public function generator256k1(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint { $curve = $this->curve256k1(); $order = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16); $x = gmp_init('0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', 16); $y = gmp_init('0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8', 16); return $curve->getGenerator($x, $y, $order, $randomGenerator); } /** * @return NamedCurveFp */ public function curve256r1(): NamedCurveFp { $p = gmp_init('0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF', 16); $a = gmp_init('0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC', 16); $b = gmp_init('0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B', 16); $parameters = new CurveParameters(256, $p, $a, $b); return new NamedCurveFp(self::NAME_SECP_256R1, $parameters, $this->adapter); } /** * @param RandomNumberGeneratorInterface $randomGenerator * @return GeneratorPoint */ public function generator256r1(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint { $curve = $this->curve256r1(); $order = gmp_init('0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551', 16); $x = gmp_init('0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296', 16); $y = gmp_init('0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5', 16); return $curve->getGenerator($x, $y, $order, $randomGenerator); } /** * @return NamedCurveFp */ public function curve384r1(): NamedCurveFp { $p = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF', 16); $a = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC', 16); $b = gmp_init('0xB3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF', 16); $parameters = new CurveParameters(384, $p, $a, $b); return new NamedCurveFp(self::NAME_SECP_384R1, $parameters, $this->adapter); } /** * @param RandomNumberGeneratorInterface $randomGenerator * @return GeneratorPoint */ public function generator384r1(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint { $curve = $this->curve384r1(); $order = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973', 16); $x = gmp_init('0xAA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7', 16); $y = gmp_init('0x3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F', 16); return $curve->getGenerator($x, $y, $order, $randomGenerator); } } src/EccFactory.php000064400000005103150736751310010075 0ustar00G = $G; $this->point = $point; parent::__construct($message, $code, $previous); } public function getGenerator(): GeneratorPoint { return $this->G; } public function getPoint(): PointInterface { return $this->point; } } src/Exception/SignatureDecodeException.php000064400000000201150736751310014727 0ustar00curveName = $curveName; return $this; } public function setOid(string $oid) { $this->oid = $oid; return $this; } public function hasCurveName(): bool { return is_string($this->curveName); } public function hasOid(): bool { return is_string($this->oid); } public function getCurveName(): string { return $this->curveName; } public function getOid(): string { return $this->oid; } } src/Math/DebugDecorator.php000064400000030705150736751310011643 0ustar00adapter = $adapter; $this->writer = $callback ?: function ($message) { echo $message; }; } /** * * @param string $message */ private function write($message) { call_user_func($this->writer, $message); } /** * * @param string $func * @param array $args * @return mixed */ private function call($func, $args) { $strArgs = array_map( function ($arg) { if ($arg instanceof \GMP) { return var_export($this->adapter->toString($arg), true); } else { return var_export($arg, true); } }, $args ); if (strpos($func, '::')) { list(, $func) = explode('::', $func); } $this->write($func.'('.implode(', ', $strArgs).')'); $res = call_user_func_array([ $this->adapter, $func ], $args); if ($res instanceof \GMP) { $this->write(' => ' . var_export($this->adapter->toString($res), true) . PHP_EOL); } else { $this->write(' => ' . var_export($res, true) . PHP_EOL); } return $res; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::cmp() */ public function cmp(\GMP $first, \GMP $other): int { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::cmp() */ public function equals(\GMP $first, \GMP $other): bool { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::mod() */ public function mod(\GMP $number, \GMP $modulus): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::add() */ public function add(\GMP $augend, \GMP $addend): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::sub() */ public function sub(\GMP $minuend, \GMP $subtrahend): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::mul() */ public function mul(\GMP $multiplier, \GMP $multiplicand): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::div() */ public function div(\GMP $dividend, \GMP $divisor): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::pow() */ public function pow(\GMP $base, int $exponent): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::bitwiseAnd() */ public function bitwiseAnd(\GMP $first, \GMP $other): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\MathAdapter::toString() */ public function toString(\GMP $value): string { return $this->adapter->toString($value); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::hexDec() */ public function hexDec(string $hexString): string { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::decHex() */ public function decHex(string $decString): string { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::powmod() */ public function powmod(\GMP $base, \GMP $exponent, \GMP $modulus): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::isPrime() */ public function isPrime(\GMP $n): bool { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::nextPrime() */ public function nextPrime(\GMP $currentPrime): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::inverseMod() */ public function inverseMod(\GMP $a, \GMP $m): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::jacobi() */ public function jacobi(\GMP $a, \GMP $p): int { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::intToString() */ public function intToFixedSizeString(\GMP $x, int $byteSize): string { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::intToString() */ public function intToString(\GMP $x): string { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::stringToInt() */ public function stringToInt(string $s): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::digestInteger() */ public function digestInteger(\GMP $m): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::gcd2() */ public function gcd2(\GMP $a, \GMP $m): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::rightShift() */ public function rightShift(\GMP $number, int $positions): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::leftShift() */ public function leftShift(\GMP $number, int $positions): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call', ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::bitwiseXor() */ public function bitwiseXor(\GMP $first, \GMP $other): \GMP { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call' ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::baseConvert() */ public function baseConvert(string $value, int $fromBase, int $toBase): string { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call' ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::getEcMath() */ public function getEcMath(GeneratorPoint $generatorPoint, $input) { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call' ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::getModularArithmetic() */ public function getModularArithmetic(\GMP $modulus): ModularArithmetic { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call' ), $func, $args ); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Math\GmpMathInterface::getNumberTheory() */ public function getNumberTheory(): NumberTheory { $func = __METHOD__; $args = func_get_args(); return call_user_func( array( $this, 'call' ), $func, $args ); } } src/Math/GmpMath.php000064400000016724150736751310010314 0ustar00cmp($exponent, gmp_init(0, 10)) < 0) { throw new \InvalidArgumentException("Negative exponents (" . $this->toString($exponent) . ") not allowed."); } return gmp_powm($base, $exponent, $modulus); } /** * {@inheritDoc} * @see GmpMathInterface::isPrime() */ public function isPrime(\GMP $n): bool { $prob = gmp_prob_prime($n); if ($prob > 0) { return true; } return false; } /** * {@inheritDoc} * @see GmpMathInterface::nextPrime() */ public function nextPrime(\GMP $starting_value): \GMP { return gmp_nextprime($starting_value); } /** * {@inheritDoc} * @see GmpMathInterface::inverseMod() */ public function inverseMod(\GMP $a, \GMP $m): \GMP { return gmp_invert($a, $m); } /** * {@inheritDoc} * @see GmpMathInterface::jacobi() */ public function jacobi(\GMP $a, \GMP $n): int { return gmp_jacobi($a, $n); } /** * @param \GMP $x * @param int $byteSize * @return string */ public function intToFixedSizeString(\GMP $x, int $byteSize): string { if ($byteSize < 0) { throw new \RuntimeException("Byte size cannot be negative"); } if (gmp_cmp($x, 0) < 0) { throw new \RuntimeException("x was negative - not yet supported"); } $two = gmp_init(2); $range = gmp_pow($two, $byteSize * 8); if (NumberSize::bnNumBits($this, $x) >= NumberSize::bnNumBits($this, $range)) { throw new \RuntimeException("Number overflows byte size"); } $maskShift = gmp_pow($two, 8); $mask = gmp_mul(gmp_init(255), $range); $binary = ''; for ($i = $byteSize - 1; $i >= 0; $i--) { $mask = gmp_div($mask, $maskShift); $binary .= pack('C', gmp_strval(gmp_div(gmp_and($x, $mask), gmp_pow($two, $i * 8)), 10)); } return $binary; } /** * {@inheritDoc} * @see GmpMathInterface::intToString() */ public function intToString(\GMP $x): string { if (gmp_cmp($x, 0) < 0) { throw new \InvalidArgumentException('Unable to convert negative integer to string'); } $hex = gmp_strval($x, 16); if (BinaryString::length($hex) % 2 != 0) { $hex = '0'.$hex; } return pack('H*', $hex); } /** * {@inheritDoc} * @see GmpMathInterface::stringToInt() */ public function stringToInt(string $s): \GMP { $result = gmp_init(0, 10); $sLen = BinaryString::length($s); for ($c = 0; $c < $sLen; $c ++) { $result = gmp_add(gmp_mul(256, $result), gmp_init(ord($s[$c]), 10)); } return $result; } /** * {@inheritDoc} * @see GmpMathInterface::digestInteger() */ public function digestInteger(\GMP $m): \GMP { return $this->stringToInt(hash('sha1', $this->intToString($m), true)); } /** * {@inheritDoc} * @see GmpMathInterface::gcd2() */ public function gcd2(\GMP $a, \GMP $b): \GMP { while ($this->cmp($a, gmp_init(0)) > 0) { $temp = $a; $a = $this->mod($b, $a); $b = $temp; } return $b; } /** * {@inheritDoc} * @see GmpMathInterface::baseConvert() */ public function baseConvert(string $number, int $from, int $to): string { return gmp_strval(gmp_init($number, $from), $to); } /** * {@inheritDoc} * @see GmpMathInterface::getNumberTheory() */ public function getNumberTheory(): NumberTheory { return new NumberTheory($this); } /** * @param \GMP $modulus * @return ModularArithmetic */ public function getModularArithmetic(\GMP $modulus): ModularArithmetic { return new ModularArithmetic($this, $modulus); } } src/Math/GmpMathInterface.php000064400000012153150736751310012125 0ustar00adapter = $adapter; $this->modulus = $modulus; } /** * @param \GMP $augend * @param \GMP $addend * @return \GMP */ public function add(\GMP $augend, \GMP $addend): \GMP { return $this->adapter->mod($this->adapter->add($augend, $addend), $this->modulus); } /** * @param \GMP $minuend * @param \GMP $subtrahend * @return \GMP */ public function sub(\GMP $minuend, \GMP $subtrahend): \GMP { return $this->adapter->mod($this->adapter->sub($minuend, $subtrahend), $this->modulus); } /** * @param \GMP $multiplier * @param \GMP $muliplicand * @return \GMP */ public function mul(\GMP $multiplier, \GMP $muliplicand): \GMP { return $this->adapter->mod($this->adapter->mul($multiplier, $muliplicand), $this->modulus); } /** * @param \GMP $dividend * @param \GMP $divisor * @return \GMP */ public function div(\GMP $dividend, \GMP $divisor): \GMP { return $this->mul($dividend, $this->adapter->inverseMod($divisor, $this->modulus)); } /** * @param \GMP $base * @param \GMP $exponent * @return \GMP */ public function pow(\GMP $base, \GMP $exponent): \GMP { return $this->adapter->powmod($base, $exponent, $this->modulus); } } src/Math/NumberTheory.php000064400000021351150736751310011372 0ustar00adapter = $adapter; $this->zero = gmp_init(0, 10); $this->one = gmp_init(1, 10); $this->two = gmp_init(2, 10); } /** * @param \GMP[] $poly * @param \GMP[] $polymod * @param \GMP $p * @return \GMP[] */ public function polynomialReduceMod(array $poly, array $polymod, \GMP $p): array { $adapter = $this->adapter; // Only enter if last value is set, implying count > 0 if ((($last = end($polymod)) instanceof \GMP) && $adapter->equals($last, $this->one)) { $count_polymod = count($polymod); while (count($poly) >= $count_polymod) { if (!$adapter->equals(end($poly), $this->zero)) { for ($i = 2; $i < $count_polymod + 1; $i++) { $poly[count($poly) - $i] = $adapter->mod( $adapter->sub( $poly[count($poly) - $i], $adapter->mul( end($poly), $polymod[$count_polymod - $i] ) ), $p ); } } $poly = array_slice($poly, 0, count($poly) - 1); } return $poly; } throw new NumberTheoryException('Unable to calculate polynomialReduceMod'); } /** * @param \GMP[] $m1 * @param \GMP[] $m2 * @param \GMP[] $polymod * @param \GMP $p * @return \GMP[] */ public function polynomialMultiplyMod(array $m1, array $m2, array $polymod, \GMP $p): array { $prod = array(); $cm1 = count($m1); $cm2 = count($m2); for ($i = 0; $i < $cm1; $i++) { for ($j = 0; $j < $cm2; $j++) { $index = $i + $j; if (!isset($prod[$index])) { $prod[$index] = $this->zero; } $prod[$index] = $this->adapter->mod( $this->adapter->add( $prod[$index], $this->adapter->mul( $m1[$i], $m2[$j] ) ), $p ); } } return $this->polynomialReduceMod($prod, $polymod, $p); } /** * @param \GMP[] $base * @param \GMP $exponent * @param \GMP[] $polymod * @param \GMP $p * @return \GMP[] */ public function polynomialPowMod(array $base, \GMP $exponent, array $polymod, \GMP $p): array { $adapter = $this->adapter; if ($adapter->cmp($exponent, $p) < 0) { if ($adapter->equals($exponent, $this->zero)) { return $this->one; } $G = $base; $k = $exponent; if ($adapter->equals($adapter->mod($k, $this->two), $this->one)) { $s = $G; } else { $s = array($this->one); } while ($adapter->cmp($k, $this->one) > 0) { $k = $adapter->div($k, $this->two); $G = $this->polynomialMultiplyMod($G, $G, $polymod, $p); if ($adapter->equals($adapter->mod($k, $this->two), $this->one)) { $s = $this->polynomialMultiplyMod($G, $s, $polymod, $p); } } return $s; } throw new NumberTheoryException('Unable to calculate polynomialPowMod'); } /** * @param \GMP $a * @param \GMP $p * @return \GMP */ public function squareRootModP(\GMP $a, \GMP $p): \GMP { $math = $this->adapter; $four = gmp_init(4, 10); $eight = gmp_init(8, 10); $modMath = $math->getModularArithmetic($p); if ($math->cmp($this->one, $p) < 0) { if ($math->equals($a, $this->zero)) { return $this->zero; } if ($math->equals($p, $this->two)) { return $a; } $jac = $math->jacobi($a, $p); if ($jac === -1) { throw new SquareRootException("{$math->toString($a)} has no square root modulo {$math->toString($p)}"); } if ($math->equals($math->mod($p, $four), gmp_init(3, 10))) { return $modMath->pow($a, $math->div($math->add($p, $this->one), $four)); } if ($math->equals($math->mod($p, $eight), gmp_init(5, 10))) { $d = $modMath->pow($a, $math->div($math->sub($p, $this->one), $four)); if ($math->equals($d, $this->one)) { return $modMath->pow($a, $math->div($math->add($p, gmp_init(3, 10)), $eight)); } if ($math->equals($d, $math->sub($p, $this->one))) { return $modMath->mul( $math->mul( $this->two, $a ), $modMath->pow( $math->mul( $four, $a ), $math->div( $math->sub( $p, gmp_init(5, 10) ), $eight ) ) ); } //shouldn't get here } for ($b = $this->two; $math->cmp($b, $p) < 0; $b = gmp_add($b, $this->one)) { if ($math->jacobi( $math->sub( $math->mul($b, $b), $math->mul($four, $a) ), $p ) == -1 ) { $f = array($a, $math->sub($this->zero, $b), $this->one); $ff = $this->polynomialPowMod( array($this->zero, $this->one), $math->div( $math->add( $p, $this->one ), $this->two ), $f, $p ); if ($math->equals($ff[1], $this->zero)) { return $ff[0]; } // if we got here no b was found } } } throw new SquareRootException('Unable to calculate square root mod p!'); } } src/Primitives/CurveFp.php000064400000015647150736751310011576 0ustar00parameters = $parameters; $this->adapter = $adapter; $this->modAdapter = new ModularArithmetic($this->adapter, $this->parameters->getPrime()); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\CurveFpInterface::getModAdapter() */ public function getModAdapter(): ModularArithmetic { return $this->modAdapter; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\CurveFpInterface::getPoint() */ public function getPoint(\GMP $x, \GMP $y, \GMP $order = null): PointInterface { return new Point($this->adapter, $this, $x, $y, $order); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\CurveFpInterface::getInfinity() */ public function getInfinity(): PointInterface { return new Point($this->adapter, $this, gmp_init(0, 10), gmp_init(0, 10), null, true); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\CurveFpInterface::getGenerator() */ public function getGenerator(\GMP $x, \GMP $y, \GMP $order, RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint { return new GeneratorPoint($this->adapter, $this, $x, $y, $order, $randomGenerator); } /** * @param bool $wasOdd * @param \GMP $xCoord * @return \GMP */ public function recoverYfromX(bool $wasOdd, \GMP $xCoord): \GMP { $math = $this->adapter; $prime = $this->getPrime(); try { $root = $this->adapter->getNumberTheory()->squareRootModP( $math->add( $math->add( $this->modAdapter->pow($xCoord, gmp_init(3, 10)), $math->mul($this->getA(), $xCoord) ), $this->getB() ), $prime ); } catch (SquareRootException $e) { throw new PointRecoveryException("Failed to recover y coordinate for point", 0, $e); } if ($math->equals($math->mod($root, gmp_init(2, 10)), gmp_init(1)) === $wasOdd) { return $root; } else { return $math->sub($prime, $root); } } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\CurveFpInterface::contains() */ public function contains(\GMP $x, \GMP $y): bool { $math = $this->adapter; $eq_zero = $math->equals( $this->modAdapter->sub( $math->pow($y, 2), $math->add( $math->add( $math->pow($x, 3), $math->mul($this->getA(), $x) ), $this->getB() ) ), gmp_init(0, 10) ); return $eq_zero; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\CurveFpInterface::getA() */ public function getA(): \GMP { return $this->parameters->getA(); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\CurveFpInterface::getB() */ public function getB(): \GMP { return $this->parameters->getB(); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\CurveFpInterface::getPrime() */ public function getPrime(): \GMP { return $this->parameters->getPrime(); } /** * @return int */ public function getSize(): int { return $this->parameters->getSize(); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\CurveFpInterface::cmp() */ public function cmp(CurveFpInterface $other): int { $math = $this->adapter; $equal = $math->equals($this->getA(), $other->getA()); $equal &= $math->equals($this->getB(), $other->getB()); $equal &= $math->equals($this->getPrime(), $other->getPrime()); return ($equal) ? 0 : 1; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\CurveFpInterface::equals() */ public function equals(CurveFpInterface $other): bool { return $this->cmp($other) == 0; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\CurveFpInterface::__toString() */ public function __toString(): string { return 'curve(' . $this->adapter->toString($this->getA()) . ', ' . $this->adapter->toString($this->getB()) . ', ' . $this->adapter->toString($this->getPrime()) . ')'; } /** * @return array */ public function __debugInfo() { return [ 'a' => $this->adapter->toString($this->getA()), 'b' => $this->adapter->toString($this->getB()), 'prime' => $this->adapter->toString($this->getPrime()) ]; } } src/Primitives/CurveFpInterface.php000064400000007435150736751310013413 0ustar00 0 if $this > $other */ public function cmp(CurveFpInterface $other): int; /** * Checks whether the curve is equal to another. * * @param CurveFpInterface $other * @return bool */ public function equals(CurveFpInterface $other): bool; /** * Return string representation of curve for debugging * * @return string */ public function __toString(): string; } src/Primitives/CurveParameters.php000064400000002272150736751310013322 0ustar00size = $size; $this->prime = $prime; $this->a = $a; $this->b = $b; } /** * @return \GMP */ public function getA(): \GMP { return $this->a; } /** * @return \GMP */ public function getB(): \GMP { return $this->b; } /** * @return \GMP */ public function getPrime(): \GMP { return $this->prime; } /** * @return int */ public function getSize(): int { return $this->size; } } src/Primitives/GeneratorPoint.php000064400000005647150736751310013163 0ustar00generator = $generator ?: RandomGeneratorFactory::getRandomGenerator(); parent::__construct($adapter, $curve, $x, $y, $order); } /** * Verifies validity of given coordinates against the current point and its point. * * @todo Check if really necessary here (only used for testing in lib) * @param \GMP $x * @param \GMP $y * @return bool */ public function isValid(\GMP $x, \GMP $y): bool { $math = $this->getAdapter(); $n = $this->getOrder(); $zero = gmp_init(0, 10); $curve = $this->getCurve(); if ($math->cmp($x, $zero) < 0 || $math->cmp($n, $x) <= 0 || $math->cmp($y, $zero) < 0 || $math->cmp($n, $y) <= 0) { return false; } if (! $curve->contains($x, $y)) { return false; } $point = $curve->getPoint($x, $y)->mul($n); if (! $point->isInfinity()) { return false; } return true; } /** * @return PrivateKeyInterface */ public function createPrivateKey(): PrivateKeyInterface { $secret = $this->generator->generate($this->getOrder()); return new PrivateKey($this->getAdapter(), $this, $secret); } /** * @param \GMP $x * @param \GMP $y * @return PublicKeyInterface */ public function getPublicKeyFrom(\GMP $x, \GMP $y): PublicKeyInterface { $pubPoint = $this->getCurve()->getPoint($x, $y, $this->getOrder()); return new PublicKey($this->getAdapter(), $this, $pubPoint); } /** * @param \GMP $secretMultiplier * @return PrivateKeyInterface */ public function getPrivateKeyFrom(\GMP $secretMultiplier): PrivateKeyInterface { return new PrivateKey($this->getAdapter(), $this, $secretMultiplier); } } src/Primitives/Point.php000064400000026377150736751310011317 0ustar00adapter = $adapter; $this->modAdapter = $curve->getModAdapter(); $this->curve = $curve; $this->x = $x; $this->y = $y; $this->order = $order !== null ? $order : gmp_init(0, 10); $this->infinity = (bool) $infinity; if (! $infinity && ! $curve->contains($x, $y)) { throw new PointNotOnCurveException($x, $y, $curve); } if (!is_null($order)) { $mul = $this->mul($order); if (!$mul->isInfinity()) { throw new PointException("SELF * ORDER MUST EQUAL INFINITY. (" . (string)$mul . " found instead)"); } } } /** * @return GmpMathInterface */ public function getAdapter(): GmpMathInterface { return $this->adapter; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\PointInterface::isInfinity() */ public function isInfinity(): bool { return (bool) $this->infinity; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\PointInterface::getCurve() */ public function getCurve(): CurveFpInterface { return $this->curve; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\PointInterface::getOrder() */ public function getOrder(): \GMP { return $this->order; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\PointInterface::getX() */ public function getX(): \GMP { return $this->x; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\PointInterface::getY() */ public function getY(): \GMP { return $this->y; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\PointInterface::add() * @return self */ public function add(PointInterface $addend): PointInterface { if (! $this->curve->equals($addend->getCurve())) { throw new \RuntimeException("The Elliptic Curves do not match."); } if ($addend->isInfinity()) { return clone $this; } if ($this->isInfinity()) { return clone $addend; } $math = $this->adapter; $modMath = $this->modAdapter; if ($math->equals($addend->getX(), $this->x)) { if ($math->equals($addend->getY(), $this->y)) { return $this->getDouble(); } else { return $this->curve->getInfinity(); } } $slope = $modMath->div( $math->sub($addend->getY(), $this->y), $math->sub($addend->getX(), $this->x) ); $xR = $modMath->sub( $math->sub($math->pow($slope, 2), $this->x), $addend->getX() ); $yR = $modMath->sub( $math->mul($slope, $math->sub($this->x, $xR)), $this->y ); return $this->curve->getPoint($xR, $yR, $this->order); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\PointInterface::cmp() */ public function cmp(PointInterface $other): int { if ($other->isInfinity() && $this->isInfinity()) { return 0; } if ($other->isInfinity() || $this->isInfinity()) { return 1; } $math = $this->adapter; $equal = ($math->equals($this->x, $other->getX())); $equal &= ($math->equals($this->y, $other->getY())); $equal &= $this->isInfinity() == $other->isInfinity(); $equal &= $this->curve->equals($other->getCurve()); if ($equal) { return 0; } return 1; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\PointInterface::equals() */ public function equals(PointInterface $other): bool { return $this->cmp($other) == 0; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\PointInterface::mul() */ public function mul(\GMP $n): PointInterface { if ($this->isInfinity()) { return $this->curve->getInfinity(); } $zero = gmp_init(0, 10); if ($this->adapter->cmp($this->order, $zero) > 0) { $n = $this->adapter->mod($n, $this->order); } if ($this->adapter->equals($n, $zero)) { return $this->curve->getInfinity(); } /** @var Point[] $r */ $r = [ $this->curve->getInfinity(), clone $this ]; $k = $this->curve->getSize(); $n = str_pad($this->adapter->baseConvert($this->adapter->toString($n), 10, 2), $k, '0', STR_PAD_LEFT); for ($i = 0; $i < $k; $i++) { $j = $n[$i]; $this->cswap($r[0], $r[1], $j ^ 1, $k); $r[0] = $r[0]->add($r[1]); $r[1] = $r[1]->getDouble(); $this->cswap($r[0], $r[1], $j ^ 1, $k); } $r[0]->validate(); return $r[0]; } /** * @param Point $a * @param Point $b * @param int $cond * @param int $curveSize */ private function cswap(self $a, self $b, int $cond, int $curveSize) { $this->cswapValue($a->x, $b->x, $cond, $curveSize); $this->cswapValue($a->y, $b->y, $cond, $curveSize); $this->cswapValue($a->order, $b->order, $cond, $curveSize); $this->cswapValue($a->infinity, $b->infinity, $cond, 8); } /** * @param bool|\GMP $a * @param bool|\GMP $b * @param int $cond * @param int $maskBitSize */ public function cswapValue(& $a, & $b, int $cond, int $maskBitSize) { $isGMP = is_object($a) && $a instanceof \GMP; $sa = $isGMP ? $a : gmp_init(intval($a), 10); $sb = $isGMP ? $b : gmp_init(intval($b), 10); $mask = str_pad('', $maskBitSize, (string) (1 - intval($cond)), STR_PAD_LEFT); $mask = gmp_init($mask, 2); $taA = $this->adapter->bitwiseAnd($sa, $mask); $taB = $this->adapter->bitwiseAnd($sb, $mask); $sa = $this->adapter->bitwiseXor($this->adapter->bitwiseXor($sa, $sb), $taB); $sb = $this->adapter->bitwiseXor($this->adapter->bitwiseXor($sa, $sb), $taA); $sa = $this->adapter->bitwiseXor($this->adapter->bitwiseXor($sa, $sb), $taB); $a = $isGMP ? $sa : (bool) gmp_strval($sa, 10); $b = $isGMP ? $sb : (bool) gmp_strval($sb, 10); } /** * */ private function validate() { if (! $this->infinity && ! $this->curve->contains($this->x, $this->y)) { throw new \RuntimeException('Invalid point'); } } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\PointInterface::getDouble() * @return self */ public function getDouble(): PointInterface { if ($this->isInfinity()) { return $this->curve->getInfinity(); } $math = $this->adapter; $modMath = $this->modAdapter; $a = $this->curve->getA(); $threeX2 = $math->mul(gmp_init(3, 10), $math->pow($this->x, 2)); $tangent = $modMath->div( $math->add($threeX2, $a), $math->mul(gmp_init(2, 10), $this->y) ); $x3 = $modMath->sub( $math->pow($tangent, 2), $math->mul(gmp_init(2, 10), $this->x) ); $y3 = $modMath->sub( $math->mul($tangent, $math->sub($this->x, $x3)), $this->y ); return $this->curve->getPoint($x3, $y3, $this->order); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Primitives\PointInterface::__toString() */ public function __toString(): string { if ($this->infinity) { return '[ (infinity) on ' . (string) $this->curve . ' ]'; } return "[ (" . $this->adapter->toString($this->x) . "," . $this->adapter->toString($this->y) . ') on ' . (string) $this->curve . ' ]'; } /** * @return array */ public function __debugInfo(): array { $info = [ 'x' => $this->adapter->toString($this->x), 'y' => $this->adapter->toString($this->y), 'z' => $this->adapter->toString($this->order), 'curve' => $this->curve ]; if ($this->infinity) { $info['x'] = 'inf (' . $info['x'] . ')'; $info['y'] = 'inf (' . $info['y'] . ')'; $info['z'] = 'inf (' . $info['z'] . ')'; } return $info; } } src/Primitives/PointInterface.php000064400000007542150736751310013131 0ustar00generator = $generator; $this->generatorName = $name; } /** * @param \GMP $max * @return \GMP */ public function generate(\GMP $max): \GMP { echo $this->generatorName.'::rand() = '; $result = $this->generator->generate($max); echo gmp_strval($result, 10).PHP_EOL; return $result; } } src/Random/HmacRandomNumberGenerator.php000064400000010414150736751310014325 0ustar00 160, 'sha224' => 224, 'sha256' => 256, 'sha384' => 384, 'sha512' => 512 ); /** * Hmac constructor. * @param GmpMathInterface $math * @param PrivateKeyInterface $privateKey * @param \GMP $messageHash - decimal hash of the message (*may* be truncated) * @param string $algorithm - hashing algorithm */ public function __construct(GmpMathInterface $math, PrivateKeyInterface $privateKey, \GMP $messageHash, string $algorithm) { if (!isset($this->algSize[$algorithm])) { throw new \InvalidArgumentException('Unsupported hashing algorithm'); } $this->math = $math; $this->algorithm = $algorithm; $this->privateKey = $privateKey; $this->messageHash = $messageHash; } /** * @param string $bits - binary string of bits * @param \GMP $qlen - length of q in bits * @return \GMP */ public function bits2int(string $bits, \GMP $qlen): \GMP { $vlen = gmp_init(BinaryString::length($bits) * 8, 10); $hex = bin2hex($bits); $v = gmp_init($hex, 16); if ($this->math->cmp($vlen, $qlen) > 0) { $v = $this->math->rightShift($v, (int) $this->math->toString($this->math->sub($vlen, $qlen))); } return $v; } /** * @param \GMP $int * @param \GMP $rlen - rounded octet length * @return string */ public function int2octets(\GMP $int, \GMP $rlen): string { $out = pack("H*", $this->math->decHex(gmp_strval($int, 10))); $length = gmp_init(BinaryString::length($out), 10); if ($this->math->cmp($length, $rlen) < 0) { return str_pad('', (int) $this->math->toString($this->math->sub($rlen, $length)), "\x00") . $out; } if ($this->math->cmp($length, $rlen) > 0) { return BinaryString::substring($out, 0, (int) $this->math->toString($rlen)); } return $out; } /** * @param string $algorithm * @return int */ private function getHashLength(string $algorithm): int { return $this->algSize[$algorithm]; } /** * @param \GMP $q * @return \GMP */ public function generate(\GMP $q): \GMP { $qlen = gmp_init(NumberSize::bnNumBits($this->math, $q), 10); $rlen = $this->math->rightShift($this->math->add($qlen, gmp_init(7, 10)), 3); $hlen = $this->getHashLength($this->algorithm); $bx = $this->int2octets($this->privateKey->getSecret(), $rlen) . $this->int2octets($this->messageHash, $rlen); $v = str_pad('', $hlen >> 3, "\x01", STR_PAD_LEFT); $k = str_pad('', $hlen >> 3, "\x00", STR_PAD_LEFT); $k = hash_hmac($this->algorithm, $v . "\x00" . $bx, $k, true); $v = hash_hmac($this->algorithm, $v, $k, true); $k = hash_hmac($this->algorithm, $v . "\x01" . $bx, $k, true); $v = hash_hmac($this->algorithm, $v, $k, true); $t = ''; for (;;) { $toff = gmp_init(0, 10); while ($this->math->cmp($toff, $rlen) < 0) { $v = hash_hmac($this->algorithm, $v, $k, true); $cc = min(BinaryString::length($v), (int) gmp_strval(gmp_sub($rlen, $toff), 10)); $t .= BinaryString::substring($v, 0, $cc); $toff = gmp_add($toff, $cc); } $k = $this->bits2int($t, $qlen); if ($this->math->cmp($k, gmp_init(0, 10)) > 0 && $this->math->cmp($k, $q) < 0) { return $k; } $k = hash_hmac($this->algorithm, $v . "\x00", $k, true); $v = hash_hmac($this->algorithm, $v, $k, true); } } } src/Random/RandomGeneratorFactory.php000064400000003501150736751310013712 0ustar00adapter = $adapter; } /** * @param \GMP $max * @return \GMP */ public function generate(\GMP $max): \GMP { $numBits = NumberSize::bnNumBits($this->adapter, $max); $numBytes = (int) ceil($numBits / 8); // Generate an integer of size >= $numBits $bytes = random_bytes($numBytes); $value = $this->adapter->stringToInt($bytes); $mask = gmp_sub(gmp_pow(2, $numBits), 1); $integer = gmp_and($value, $mask); return $integer; } } src/Random/RandomNumberGeneratorInterface.php000064400000000477150736751310015365 0ustar00adapter = $adapter; $this->theory = $adapter->getNumberTheory(); } /** * @param PointInterface $point * @return string */ public function getPrefix(PointInterface $point): string { if ($this->adapter->equals($this->adapter->mod($point->getY(), gmp_init(2, 10)), gmp_init(0))) { return '02'; } else { return '03'; } } /** * @param PointInterface $point * @return string */ public function serialize(PointInterface $point): string { $length = CurveOidMapper::getByteSize($point->getCurve()) * 2; $hexString = $this->getPrefix($point); $hexString .= str_pad(gmp_strval($point->getX(), 16), $length, '0', STR_PAD_LEFT); return $hexString; } /** * @param CurveFpInterface $curve * @param string $data - hex serialized compressed point * @return PointInterface */ public function unserialize(CurveFpInterface $curve, string $data): PointInterface { $prefix = substr($data, 0, 2); if ($prefix !== '03' && $prefix !== '02') { throw new \InvalidArgumentException('Invalid data: only compressed keys are supported.'); } $x = gmp_init(substr($data, 2), 16); $y = $curve->recoverYfromX($prefix === '03', $x); return $curve->getPoint($x, $y); } } src/Serializer/Point/PointSerializerInterface.php000064400000001133150736751310016220 0ustar00getCurve()) * 2; $hexString = '04'; $hexString .= str_pad(gmp_strval($point->getX(), 16), $length, '0', STR_PAD_LEFT); $hexString .= str_pad(gmp_strval($point->getY(), 16), $length, '0', STR_PAD_LEFT); return $hexString; } /** * @param CurveFpInterface $curve * @param string $data * @return PointInterface */ public function unserialize(CurveFpInterface $curve, string $data): PointInterface { if (BinaryString::substring($data, 0, 2) != '04') { throw new \InvalidArgumentException('Invalid data: only uncompressed keys are supported.'); } $data = BinaryString::substring($data, 2); $dataLength = BinaryString::length($data); $x = gmp_init(BinaryString::substring($data, 0, $dataLength / 2), 16); $y = gmp_init(BinaryString::substring($data, $dataLength / 2), 16); return $curve->getPoint($x, $y); } } src/Serializer/PrivateKey/DerPrivateKeySerializer.php000064400000006242150736751310017024 0ustar00adapter = $adapter ?: MathAdapterFactory::getAdapter(); $this->pubKeySerializer = $pubKeySerializer ?: new DerPublicKeySerializer($this->adapter); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Serializer\PrivateKey\PrivateKeySerializerInterface::serialize() */ public function serialize(PrivateKeyInterface $key): string { $privateKeyInfo = new Sequence( new Integer(self::VERSION), new OctetString($this->formatKey($key)), new ExplicitlyTaggedObject(0, CurveOidMapper::getCurveOid($key->getPoint()->getCurve())), new ExplicitlyTaggedObject(1, $this->encodePubKey($key)) ); return $privateKeyInfo->getBinary(); } /** * @param PrivateKeyInterface $key * @return BitString */ private function encodePubKey(PrivateKeyInterface $key): BitString { return new BitString( $this->pubKeySerializer->getUncompressedKey($key->getPublicKey()) ); } /** * @param PrivateKeyInterface $key * @return string */ private function formatKey(PrivateKeyInterface $key): string { return gmp_strval($key->getSecret(), 16); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Serializer\PrivateKey\PrivateKeySerializerInterface::parse() * @throws \FG\ASN1\Exception\ParserException */ public function parse(string $data): PrivateKeyInterface { $asnObject = ASNObject::fromBinary($data); if (! ($asnObject instanceof Sequence) || $asnObject->getNumberofChildren() !== 4) { throw new \RuntimeException('Invalid data.'); } $children = $asnObject->getChildren(); $version = $children[0]; if ($version->getContent() != 1) { throw new \RuntimeException('Invalid data: only version 1 (RFC5915) keys are supported.'); } $key = gmp_init($children[1]->getContent(), 16); $oid = $children[2]->getContent()[0]; $generator = CurveOidMapper::getGeneratorFromOid($oid); return $generator->getPrivateKeyFrom($key); } } src/Serializer/PrivateKey/PemPrivateKeySerializer.php000064400000003020150736751310017022 0ustar00derSerializer = $derSerializer; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Serializer\PrivateKey\PrivateKeySerializerInterface::serialize() */ public function serialize(PrivateKeyInterface $key): string { $privateKeyInfo = $this->derSerializer->serialize($key); $content = '-----BEGIN EC PRIVATE KEY-----'.PHP_EOL; $content .= trim(chunk_split(base64_encode($privateKeyInfo), 64, PHP_EOL)).PHP_EOL; $content .= '-----END EC PRIVATE KEY-----'; return $content; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Serializer\PrivateKey\PrivateKeySerializerInterface::parse() */ public function parse(string $formattedKey): PrivateKeyInterface { $formattedKey = str_replace('-----BEGIN EC PRIVATE KEY-----', '', $formattedKey); $formattedKey = str_replace('-----END EC PRIVATE KEY-----', '', $formattedKey); $data = base64_decode($formattedKey); return $this->derSerializer->parse($data); } } src/Serializer/PrivateKey/PrivateKeySerializerInterface.php000064400000000746150736751310020215 0ustar00pointSerializer = $pointSerializer ?: new UncompressedPointSerializer(); } /** * @param PublicKeyInterface $key * @return string */ public function format(PublicKeyInterface $key): string { if (! ($key->getCurve() instanceof NamedCurveFp)) { throw new \RuntimeException('Not implemented for unnamed curves'); } $sequence = new Sequence( new Sequence( new ObjectIdentifier(DerPublicKeySerializer::X509_ECDSA_OID), CurveOidMapper::getCurveOid($key->getCurve()) ), new BitString($this->encodePoint($key->getPoint())) ); return $sequence->getBinary(); } /** * @param PointInterface $point * @return string */ public function encodePoint(PointInterface $point): string { return $this->pointSerializer->serialize($point); } } src/Serializer/PublicKey/Der/Parser.php000064400000006605150736751310014031 0ustar00adapter = $adapter; $this->pointSerializer = $pointSerializer ?: new UncompressedPointSerializer(); } /** * @param string $binaryData * @return PublicKeyInterface * @throws \FG\ASN1\Exception\ParserException */ public function parse(string $binaryData): PublicKeyInterface { $asnObject = ASNObject::fromBinary($binaryData); if ($asnObject->getType() !== Identifier::SEQUENCE) { throw new \RuntimeException('Invalid data.'); } /** @var Sequence $asnObject */ if ($asnObject->getNumberofChildren() != 2) { throw new \RuntimeException('Invalid data.'); } $children = $asnObject->getChildren(); if (count($children) != 2) { throw new \RuntimeException('Invalid data.'); } if (count($children) != 2) { throw new \RuntimeException('Invalid data.'); } if ($children[0]->getType() !== Identifier::SEQUENCE) { throw new \RuntimeException('Invalid data.'); } if (count($children[0]->getChildren()) != 2) { throw new \RuntimeException('Invalid data.'); } if ($children[0]->getChildren()[0]->getType() !== Identifier::OBJECT_IDENTIFIER) { throw new \RuntimeException('Invalid data.'); } if ($children[0]->getChildren()[1]->getType() !== Identifier::OBJECT_IDENTIFIER) { throw new \RuntimeException('Invalid data.'); } if ($children[1]->getType() !== Identifier::BITSTRING) { throw new \RuntimeException('Invalid data.'); } $oid = $children[0]->getChildren()[0]; $curveOid = $children[0]->getChildren()[1]; $encodedKey = $children[1]; if ($oid->getContent() !== DerPublicKeySerializer::X509_ECDSA_OID) { throw new \RuntimeException('Invalid data: non X509 data.'); } $generator = CurveOidMapper::getGeneratorFromOid($curveOid); return $this->parseKey($generator, $encodedKey->getContent()); } /** * @param GeneratorPoint $generator * @param string $data * @return PublicKeyInterface */ public function parseKey(GeneratorPoint $generator, string $data): PublicKeyInterface { $point = $this->pointSerializer->unserialize($generator->getCurve(), $data); return new PublicKey($this->adapter, $generator, $point); } } src/Serializer/PublicKey/DerPublicKeySerializer.php000064400000003766150736751310016444 0ustar00adapter = $adapter ?: MathAdapterFactory::getAdapter(); $this->formatter = new Formatter(); $this->parser = new Parser($this->adapter, $pointSerializer ?: new UncompressedPointSerializer()); } /** * * @param PublicKeyInterface $key * @return string */ public function serialize(PublicKeyInterface $key): string { return $this->formatter->format($key); } /** * @param PublicKeyInterface $key * @return string */ public function getUncompressedKey(PublicKeyInterface $key): string { return $this->formatter->encodePoint($key->getPoint()); } /** * {@inheritDoc} * @see \Mdanter\Ecc\Serializer\PublicKey\PublicKeySerializerInterface::parse() */ public function parse(string $string): PublicKeyInterface { return $this->parser->parse($string); } } src/Serializer/PublicKey/PemPublicKeySerializer.php000064400000002733150736751310016444 0ustar00derSerializer = $serializer; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Serializer\PublicKey\PublicKeySerializerInterface::serialize() */ public function serialize(PublicKeyInterface $key): string { $publicKeyInfo = $this->derSerializer->serialize($key); $content = '-----BEGIN PUBLIC KEY-----'.PHP_EOL; $content .= trim(chunk_split(base64_encode($publicKeyInfo), 64, PHP_EOL)).PHP_EOL; $content .= '-----END PUBLIC KEY-----'; return $content; } /** * {@inheritDoc} * @see \Mdanter\Ecc\Serializer\PublicKey\PublicKeySerializerInterface::parse() */ public function parse(string $formattedKey): PublicKeyInterface { $formattedKey = str_replace('-----BEGIN PUBLIC KEY-----', '', $formattedKey); $formattedKey = str_replace('-----END PUBLIC KEY-----', '', $formattedKey); $data = base64_decode($formattedKey); return $this->derSerializer->parse($data); } } src/Serializer/PublicKey/PublicKeySerializerInterface.php000064400000000721150736751310017616 0ustar00getR(), 10)), new Integer(gmp_strval($signature->getS(), 10)) ); } /** * @param SignatureInterface $signature * @return string */ public function serialize(SignatureInterface $signature): string { return $this->toAsn($signature)->getBinary(); } } src/Serializer/Signature/Der/Parser.php000064400000002563150736751310014102 0ustar00getType() !== Identifier::SEQUENCE) { throw new SignatureDecodeException('Invalid tag for sequence.'); } if ($asnObject->getNumberofChildren() !== 2) { throw new SignatureDecodeException('Invalid data.'); } if (!($asnObject[0] instanceof Integer && $asnObject[1] instanceof Integer)) { throw new SignatureDecodeException('Invalid data.'); } return new Signature( gmp_init($asnObject[0]->getContent(), 10), gmp_init($asnObject[1]->getContent(), 10) ); } } src/Serializer/Signature/DerSignatureSerializer.php000064400000001655150736751310016563 0ustar00parser = new Der\Parser(); $this->formatter = new Der\Formatter(); } /** * @param SignatureInterface $signature * @return string */ public function serialize(SignatureInterface $signature): string { return $this->formatter->serialize($signature); } /** * @param string $binary * @return SignatureInterface * @throws \FG\ASN1\Exception\ParserException */ public function parse(string $binary): SignatureInterface { return $this->parser->parse($binary); } } src/Serializer/Signature/DerSignatureSerializerInterface.php000064400000001013150736751310020370 0ustar00 self::NIST_P192_OID, NistCurve::NAME_P224 => self::NIST_P224_OID, NistCurve::NAME_P256 => self::NIST_P256_OID, NistCurve::NAME_P384 => self::NIST_P384_OID, NistCurve::NAME_P521 => self::NIST_P521_OID, SecgCurve::NAME_SECP_112R1 => self::SECP_112R1_OID, SecgCurve::NAME_SECP_192K1 => self::SECP_192K1_OID, SecgCurve::NAME_SECP_256K1 => self::SECP_256K1_OID, SecgCurve::NAME_SECP_256R1 => self::SECP_256R1_OID, SecgCurve::NAME_SECP_384R1 => self::SECP_384R1_OID, ); /** * @var array */ private static $sizeMap = array( NistCurve::NAME_P192 => 24, NistCurve::NAME_P224 => 28, NistCurve::NAME_P256 => 32, NistCurve::NAME_P384 => 48, NistCurve::NAME_P521 => 66, SecgCurve::NAME_SECP_112R1 => 14, SecgCurve::NAME_SECP_192K1 => 24, SecgCurve::NAME_SECP_256K1 => 32, SecgCurve::NAME_SECP_256R1 => 32, SecgCurve::NAME_SECP_384R1 => 48, ); /** * @return array */ public static function getNames(): array { return array_keys(self::$oidMap); } /** * @param CurveFpInterface $curve * @return int */ public static function getByteSize(CurveFpInterface $curve): int { if ($curve instanceof NamedCurveFp && array_key_exists($curve->getName(), self::$sizeMap)) { return self::$sizeMap[$curve->getName()]; } throw new UnsupportedCurveException('Unsupported curve type'); } /** * @param NamedCurveFp $curve * @return ObjectIdentifier */ public static function getCurveOid(NamedCurveFp $curve): ObjectIdentifier { if (array_key_exists($curve->getName(), self::$oidMap)) { $oidString = self::$oidMap[$curve->getName()]; return new ObjectIdentifier($oidString); } throw new UnsupportedCurveException('Unsupported curve type'); } /** * @param ObjectIdentifier $oid * @return NamedCurveFp */ public static function getCurveFromOid(ObjectIdentifier $oid): NamedCurveFp { $oidString = $oid->getContent(); $invertedMap = array_flip(self::$oidMap); if (array_key_exists($oidString, $invertedMap)) { return CurveFactory::getCurveByName($invertedMap[$oidString]); } $error = new UnsupportedCurveException('Invalid data: unsupported curve.'); $error->setOid($oidString); throw $error; } /** * @param ObjectIdentifier $oid * @return GeneratorPoint */ public static function getGeneratorFromOid(ObjectIdentifier $oid): GeneratorPoint { $oidString = $oid->getContent(); $invertedMap = array_flip(self::$oidMap); if (array_key_exists($oidString, $invertedMap)) { return CurveFactory::getGeneratorByName($invertedMap[$oidString]); } $error = new UnsupportedCurveException('Invalid data: unsupported generator.'); $error->setOid($oidString); throw $error; } } src/Util/BinaryString.php000064400000003304150736751310011404 0ustar00equals($x, $zero)) { return 0; } $log2 = 0; while (false === $adapter->equals($x, $zero)) { $x = $adapter->rightShift($x, 1); $log2++; } return $log2 ; } } validate_examples.sh000075500000000224150736751310010600 0ustar00#!/bin/bash for i in examples/*.php; do php $i if [ $? != 0 ]; then echo "Error running example code"; exit -1 fi; done