├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── autoload-shim.php ├── composer.json ├── phpunit.xml.dist ├── psalm.xml ├── src ├── Curve25519 │ ├── EdwardsPublicKey.php │ ├── EdwardsSecretKey.php │ ├── MontgomeryPublicKey.php │ ├── MontgomerySecretKey.php │ └── X25519.php ├── ECDSA │ ├── ConstantTimeMath.php │ ├── HedgedRandomNumberGenerator.php │ ├── PublicKey.php │ ├── PublicKeyDerParser.php │ ├── SecretKey.php │ └── Signature.php ├── EasyECC.php ├── EncryptionInterface.php ├── Exception │ ├── ConfigException.php │ ├── EasyEccException.php │ ├── InvalidPublicKeyException.php │ └── NotImplementedException.php └── Integration │ └── Defuse.php └── tests ├── ECDSA ├── ConstantTimeMathTest.php └── SignatureTest.php ├── EasyECCTest.php ├── Integration └── DefuseTest.php ├── K256Test.php ├── KeyLengthTest.php ├── P256Test.php ├── P384Test.php ├── P521Test.php └── SodiumTest.php /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | moderate: 7 | name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} 8 | runs-on: ${{ matrix.operating-system }} 9 | strategy: 10 | matrix: 11 | operating-system: ['ubuntu-latest'] 12 | php-versions: ['7.1', '7.2', '7.3'] 13 | phpunit-versions: ['latest'] 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup PHP 19 | uses: shivammathur/setup-php@v2 20 | with: 21 | php-version: ${{ matrix.php-versions }} 22 | extensions: gmp, mbstring, intl, sodium 23 | ini-values: post_max_size=256M, max_execution_time=180 24 | 25 | - name: Install dependencies 26 | run: composer install 27 | 28 | - name: Full Test Suite 29 | run: composer test 30 | 31 | modern: 32 | name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} 33 | runs-on: ${{ matrix.operating-system }} 34 | strategy: 35 | matrix: 36 | operating-system: ['ubuntu-latest'] 37 | php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3'] 38 | phpunit-versions: ['latest'] 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@v4 42 | 43 | - name: Setup PHP 44 | uses: shivammathur/setup-php@v2 45 | with: 46 | php-version: ${{ matrix.php-versions }} 47 | extensions: gmp, mbstring, intl, sodium 48 | ini-values: post_max_size=256M, max_execution_time=180 49 | 50 | - name: Install dependencies 51 | run: composer install 52 | 53 | - name: Full Test Suite 54 | run: composer test 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | /composer.lock 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * ISC License 3 | * 4 | * Copyright (c) 2016-2022 5 | * Paragon Initiative Enterprises 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easy-ECC 2 | 3 | [![Build Status](https://github.com/paragonie/easy-ecc/actions/workflows/ci.yml/badge.svg)](https://github.com/paragonie/easy-ecc/actions) 4 | [![Latest Stable Version](https://poser.pugx.org/paragonie/easy-ecc/v/stable)](https://packagist.org/packages/paragonie/easy-ecc) 5 | [![Latest Unstable Version](https://poser.pugx.org/paragonie/easy-ecc/v/unstable)](https://packagist.org/packages/paragonie/easy-ecc) 6 | [![License](https://poser.pugx.org/paragonie/easy-ecc/license)](https://packagist.org/packages/paragonie/easy-ecc) 7 | [![Downloads](https://img.shields.io/packagist/dt/paragonie/easy-ecc.svg)](https://packagist.org/packages/paragonie/easy-ecc) 8 | 9 | A usability wrapper for [PHP ECC](https://github.com/paragonie/phpecc) 10 | that also further hardens against timing attacks. 11 | 12 | ## Installing 13 | 14 | ``` 15 | composer require paragonie/easy-ecc 16 | ``` 17 | 18 | ## Using Easy-ECC 19 | 20 | ```php 21 | generatePrivateKey(); 29 | $alice_pk = $alice_sk->getPublicKey(); 30 | 31 | // Signing a message (with PEM-formatted signatures): 32 | $message = 'This is extremely simple to use correctly.'; 33 | $signature = $ecc->sign($message, $alice_sk); 34 | 35 | if (!$ecc->verify($message, $alice_pk, $signature)) { 36 | throw new Exception('Signature validation failed'); 37 | } 38 | 39 | // Let's do a key exchange: 40 | $bob_sk = $ecc->generatePrivateKey(); 41 | $bob_pk = $alice_sk->getPublicKey(); 42 | 43 | $alice_to_bob = $ecc->keyExchange($alice_sk, $bob_pk, true); 44 | $bob_to_alice = $ecc->keyExchange($bob_sk, $alice_pk, false); 45 | ``` 46 | 47 | ### Other Easy-ECC Modes 48 | 49 | #### secp256k1 + SHA256 50 | 51 | ```php 52 | generatePrivateKey(); 98 | /** @var PublicKey $alice_pk */ 99 | $alice_pk = $alice_sk->getPublicKey(); 100 | 101 | // Serialize as PEM (for OpenSSL compatibility): 102 | $alice_sk_pem = $alice_sk->exportPem(); 103 | $alice_pk_pem = $alice_pk->exportPem(); 104 | 105 | // Serialize public key as compressed point (for brevity): 106 | $alice_pk_cpt = $alice_pk->toString(); 107 | 108 | $message = 'This is extremely simple to use correctly.'; 109 | // Signing a message (with IEEE-P1363-formatted signatures): 110 | $signature = $ecc->sign($message, $alice_sk, true); 111 | if (!$ecc->verify($message, $alice_pk, $signature, true)) { 112 | throw new Exception('Signature validation failed'); 113 | } 114 | 115 | // Let's do a key exchange: 116 | $bob_sk = $ecc->generatePrivateKey(); 117 | $bob_pk = $alice_sk->getPublicKey(); 118 | 119 | $alice_to_bob = $ecc->keyExchange($alice_sk, $bob_pk, true); 120 | $bob_to_alice = $ecc->keyExchange($bob_sk, $alice_pk, false); 121 | ``` 122 | 123 | ### Asymmetric Encryption 124 | 125 | We provide an interface that you can implement for the underlying symmetric 126 | cryptography to suit your needs. This library provides a built-in integration 127 | for [Defuse's PHP encryption library](https://github.com/defuse/php-encryption). 128 | 129 | ```php 130 | seal($superSecret, $publicKey); 150 | $opened = $defuse->unseal($sealed, $secretKey); 151 | 152 | // Or you can encrypt between two keypairs: 153 | $otherSecret = $ecc->generatePrivateKey(); 154 | $otherPublic = $otherSecret->getPublicKey(); 155 | $encrypted = $defuse->asymmetricEncrypt($superSecret, $secretKey, $otherPublic); 156 | $decrypted = $defuse->asymmetricDecrypt($encrypted, $otherSecret, $publicKey); 157 | ``` 158 | 159 | ## Support Contracts 160 | 161 | If your company uses this library in their products or services, you may be 162 | interested in [purchasing a support contract from Paragon Initiative Enterprises](https://paragonie.com/enterprise). 163 | -------------------------------------------------------------------------------- /autoload-shim.php: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | ./tests 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Curve25519/EdwardsPublicKey.php: -------------------------------------------------------------------------------- 1 | publicKey = $keyMaterial; 32 | } else { 33 | throw new \SodiumException('Invalid secret key provided'); 34 | } 35 | } 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function getAsString(): string 41 | { 42 | return $this->publicKey; 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function toString(): string 49 | { 50 | return Hex::encode($this->publicKey); 51 | } 52 | 53 | /** 54 | * @param string $str 55 | * @return self 56 | * @throws \SodiumException 57 | */ 58 | public static function fromString(string $str): self 59 | { 60 | return new EdwardsPublicKey(Hex::decode($str)); 61 | } 62 | 63 | /** 64 | * @return CurveFpInterface 65 | * @throws NotImplementedException 66 | */ 67 | public function getCurve(): CurveFpInterface 68 | { 69 | throw new NotImplementedException('This is not part of the curve25519 interface'); 70 | } 71 | 72 | /** 73 | * @return MontgomeryPublicKey 74 | * @throws \SodiumException 75 | */ 76 | public function getMontgomery(): MontgomeryPublicKey 77 | { 78 | return new MontgomeryPublicKey( 79 | \sodium_crypto_sign_ed25519_pk_to_curve25519($this->publicKey) 80 | ); 81 | } 82 | 83 | /** 84 | * @return PointInterface 85 | * @throws NotImplementedException 86 | */ 87 | public function getPoint(): PointInterface 88 | { 89 | throw new NotImplementedException('This is not part of the curve25519 interface'); 90 | } 91 | 92 | /** 93 | * @return GeneratorPoint 94 | * @throws NotImplementedException 95 | */ 96 | public function getGenerator(): GeneratorPoint 97 | { 98 | throw new NotImplementedException('This is not part of the curve25519 interface'); 99 | } 100 | } -------------------------------------------------------------------------------- /src/Curve25519/EdwardsSecretKey.php: -------------------------------------------------------------------------------- 1 | secretKey = \sodium_crypto_sign_secretkey($keyMaterial); 31 | } else if (Binary::safeStrlen($keyMaterial) === SODIUM_CRYPTO_SIGN_SECRETKEYBYTES) { 32 | $this->secretKey = $keyMaterial; 33 | } else if (Binary::safeStrlen($keyMaterial) === SODIUM_CRYPTO_SIGN_SEEDBYTES) { 34 | $keypair = \sodium_crypto_sign_seed_keypair($keyMaterial); 35 | $this->secretKey = \sodium_crypto_sign_secretkey($keypair); 36 | } else { 37 | throw new \SodiumException('Invalid secret key provided'); 38 | } 39 | } 40 | 41 | /** 42 | * @return string 43 | */ 44 | public function getAsString(): string 45 | { 46 | return $this->secretKey; 47 | } 48 | 49 | /** 50 | * @return string 51 | */ 52 | public function toString(): string 53 | { 54 | return $this->secretKey; 55 | } 56 | 57 | /** 58 | * @return MontgomerySecretKey 59 | * @throws \SodiumException 60 | */ 61 | public function getMontgomery(): MontgomerySecretKey 62 | { 63 | return new MontgomerySecretKey( 64 | \sodium_crypto_sign_ed25519_sk_to_curve25519($this->secretKey) 65 | ); 66 | } 67 | 68 | /** 69 | * @return PublicKeyInterface 70 | * @throws \SodiumException 71 | */ 72 | public function getPublicKey(): PublicKeyInterface 73 | { 74 | return new EdwardsPublicKey( 75 | \sodium_crypto_sign_publickey_from_secretkey($this->secretKey) 76 | ); 77 | } 78 | 79 | /** 80 | * @return GeneratorPoint 81 | * @throws NotImplementedException 82 | */ 83 | public function getPoint(): GeneratorPoint 84 | { 85 | throw new NotImplementedException('This is not part of the curve25519 interface'); 86 | } 87 | 88 | /** 89 | * @return \GMP 90 | * @throws NotImplementedException 91 | */ 92 | public function getSecret(): \GMP 93 | { 94 | throw new NotImplementedException('This is not part of the curve25519 interface'); 95 | } 96 | 97 | /** 98 | * @param PublicKeyInterface $recipient 99 | * @return EcDHInterface 100 | * @throws NotImplementedException 101 | */ 102 | public function createExchange(PublicKeyInterface $recipient): EcDHInterface 103 | { 104 | throw new NotImplementedException('This is not part of the curve25519 interface'); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Curve25519/MontgomeryPublicKey.php: -------------------------------------------------------------------------------- 1 | publicKey = $keyMaterial; 31 | } else { 32 | throw new \SodiumException('Invalid secret key provided'); 33 | } 34 | } 35 | 36 | /** 37 | * @return string 38 | */ 39 | public function getAsString(): string 40 | { 41 | return $this->publicKey; 42 | } 43 | 44 | /** 45 | * @return CurveFpInterface 46 | * @throws NotImplementedException 47 | */ 48 | public function getCurve(): CurveFpInterface 49 | { 50 | throw new NotImplementedException('This is not part of the curve25519 interface'); 51 | } 52 | 53 | /** 54 | * @return PointInterface 55 | * @throws NotImplementedException 56 | */ 57 | public function getPoint(): PointInterface 58 | { 59 | throw new NotImplementedException('This is not part of the curve25519 interface'); 60 | } 61 | 62 | /** 63 | * @return GeneratorPoint 64 | * @throws NotImplementedException 65 | */ 66 | public function getGenerator(): GeneratorPoint 67 | { 68 | throw new NotImplementedException('This is not part of the curve25519 interface'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Curve25519/MontgomerySecretKey.php: -------------------------------------------------------------------------------- 1 | secretKey = $keyMaterial; 31 | } else { 32 | throw new \SodiumException('Invalid secret key provided'); 33 | } 34 | } 35 | 36 | /** 37 | * @return string 38 | */ 39 | public function getAsString(): string 40 | { 41 | return $this->secretKey; 42 | } 43 | 44 | /** 45 | * @return PublicKeyInterface 46 | * @throws \SodiumException 47 | */ 48 | public function getPublicKey(): PublicKeyInterface 49 | { 50 | return new MontgomeryPublicKey( 51 | \sodium_crypto_box_publickey_from_secretkey($this->secretKey) 52 | ); 53 | } 54 | 55 | /** 56 | * @return GeneratorPoint 57 | * @throws NotImplementedException 58 | */ 59 | public function getPoint(): GeneratorPoint 60 | { 61 | throw new NotImplementedException('This is not part of the curve25519 interface'); 62 | } 63 | 64 | /** 65 | * @return \GMP 66 | * @throws NotImplementedException 67 | */ 68 | public function getSecret(): \GMP 69 | { 70 | throw new NotImplementedException('This is not part of the curve25519 interface'); 71 | } 72 | 73 | /** 74 | * @param PublicKeyInterface $recipient 75 | * @return EcDHInterface 76 | * @throws NotImplementedException 77 | */ 78 | public function createExchange(PublicKeyInterface $recipient): EcDHInterface 79 | { 80 | throw new NotImplementedException('This is not part of the curve25519 interface'); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Curve25519/X25519.php: -------------------------------------------------------------------------------- 1 | setSenderKey($sk); 33 | } 34 | if ($pk) { 35 | $this->setRecipientKey($pk); 36 | } 37 | } 38 | 39 | /** 40 | * @return string 41 | * @throws \SodiumException 42 | * @throws \TypeError 43 | */ 44 | public function scalarMult(): string 45 | { 46 | return \sodium_crypto_scalarmult( 47 | $this->sk->getAsString(), 48 | $this->pk->getAsString() 49 | ); 50 | } 51 | 52 | /** 53 | * @param bool $isClient 54 | * @return string 55 | * @throws \SodiumException 56 | * @throws \TypeError 57 | */ 58 | public function keyExchange(bool $isClient): string 59 | { 60 | /** @var MontgomeryPublicKey $s_pk */ 61 | $s_pk = $this->sk->getPublicKey(); 62 | 63 | if ($isClient) { 64 | return \sodium_crypto_kx_client_session_keys( 65 | $this->sk->getAsString() . $s_pk->getAsString(), 66 | $this->pk->getAsString() 67 | )[0]; 68 | } 69 | return \sodium_crypto_kx_server_session_keys( 70 | $this->sk->getAsString() . $s_pk->getAsString(), 71 | $this->pk->getAsString() 72 | )[1]; 73 | } 74 | 75 | /** 76 | * Calculates and returns the shared key for the exchange. 77 | * 78 | * @return \GMP 79 | * @throws NotImplementedException 80 | */ 81 | public function calculateSharedKey(): \GMP 82 | { 83 | throw new NotImplementedException('This is not part of the curve25519 interface'); 84 | } 85 | 86 | /** 87 | * @return PublicKeyInterface 88 | * @throws NotImplementedException 89 | */ 90 | public function createMultiPartyKey(): PublicKeyInterface 91 | { 92 | throw new NotImplementedException('This is not part of the curve25519 interface'); 93 | } 94 | 95 | /** 96 | * Sets the sender's key. 97 | * 98 | * @param PrivateKeyInterface $key 99 | * @return self 100 | * @throws \SodiumException 101 | * @throws \TypeError 102 | */ 103 | public function setSenderKey(PrivateKeyInterface $key) 104 | { 105 | if ($key instanceof MontgomerySecretKey) { 106 | $this->sk = $key; 107 | } elseif ($key instanceof EdwardsSecretKey) { 108 | $this->sk = $key->getMontgomery(); 109 | } else { 110 | throw new \TypeError('Only libsodium keys are allowed'); 111 | } 112 | return $this; 113 | } 114 | 115 | /** 116 | * Sets the recipient key. 117 | * 118 | * @param PublicKeyInterface $key 119 | * @return self 120 | * @throws \SodiumException 121 | * @throws \TypeError 122 | */ 123 | public function setRecipientKey(PublicKeyInterface $key) 124 | { 125 | if ($key instanceof MontgomeryPublicKey) { 126 | $this->pk = $key; 127 | } elseif ($key instanceof EdwardsPublicKey) { 128 | $this->pk = $key->getMontgomery(); 129 | } else { 130 | throw new \TypeError('Only libsodium keys are allowed'); 131 | } 132 | return $this; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/ECDSA/ConstantTimeMath.php: -------------------------------------------------------------------------------- 1 | $other. 26 | * Sets $eq to1 if $first === $other. 27 | * 28 | * See {@link cmp()} for usage. 29 | * 30 | * | first | other | gt | eq | 31 | * |-------|-------|----|----| 32 | * | -1 | -1 | 0 | 1 | 33 | * | -1 | 0 | 0 | 0 | 34 | * | -1 | 1 | 0 | 0 | 35 | * | 0 | -1 | 1 | 0 | 36 | * | 0 | 0 | 0 | 1 | 37 | * | 0 | 1 | 1 | 0 | 38 | * | 1 | -1 | 1 | 0 | 39 | * | 1 | 0 | 1 | 0 | 40 | * | 1 | 1 | 0 | 1 | 41 | * 42 | * @param int $first_sign 43 | * @param int $other_sign 44 | * @return int[] 45 | */ 46 | public function compareSigns(int $first_sign, int $other_sign): array 47 | { 48 | // Coerce to positive (-1, 0, 1) -> (0, 1, 2) 49 | ++$first_sign; 50 | ++$other_sign; 51 | $gt = (($other_sign - $first_sign) >> 2) & 1; 52 | $eq = ((($first_sign ^ $other_sign) - 1) >> 2) & 1; 53 | return [$gt, $eq]; 54 | } 55 | 56 | /** 57 | * Compare two GMP objects, without timing leaks. 58 | * 59 | * @param GMP $first 60 | * @param GMP $other 61 | * @return int -1 if $first < $other 62 | * 0 if $first === $other 63 | * 1 if $first > $other 64 | */ 65 | public function cmp(GMP $first, GMP $other): int 66 | { 67 | /** 68 | * @var string $left 69 | * @var string $right 70 | * @var int $length 71 | */ 72 | list($left, $right, $length) = $this->normalizeLengths($first, $other); 73 | 74 | $first_sign = \gmp_sign($first); 75 | $other_sign = \gmp_sign($other); 76 | list($gt, $eq) = $this->compareSigns($first_sign, $other_sign); 77 | 78 | for ($i = 0; $i < $length; ++$i) { 79 | $gt |= (($this->ord($right[$i]) - $this->ord($left[$i])) >> 8) & $eq; 80 | $eq &= (($this->ord($right[$i]) ^ $this->ord($left[$i])) - 1) >> 8; 81 | } 82 | return ($gt + $gt + $eq) - 1; 83 | } 84 | 85 | /** 86 | * {@inheritDoc} 87 | * @see GmpMathInterface::inverseMod() 88 | * @codeCoverageIgnore 89 | */ 90 | public function inverseMod(GMP $a, GMP $m): GMP 91 | { 92 | list($x, $y) = $this->binaryGcd($a, $m); 93 | if (!$this->equals($y, \gmp_init(1))) { 94 | throw new NumberTheoryException('No inverse exists for these two numbers'); 95 | } 96 | 97 | return $x; 98 | } 99 | 100 | /** 101 | * Stein's Algorithm (Binary GCD) 102 | * 103 | * Based on algorithm 14.61 from the Handbook of Applied Cryptography 104 | * 105 | * @param GMP $X 106 | * @param GMP $Y 107 | * @return GMP[] ($gcd, $inverse) 108 | * @codeCoverageIgnore 109 | */ 110 | public function binaryGcd(GMP $X, GMP $Y): array 111 | { 112 | // Don't mutate the input parameters 113 | $x = clone $X; 114 | $y = clone $Y; 115 | $g = \min($this->trailingZeroes($x), $this->trailingZeroes($y)); 116 | $x = $this->rightShift($x, $g); 117 | $x = $this->rightShift($x, $g); 118 | $u = clone $x; 119 | $v = clone $y; 120 | 121 | $zero = \gmp_init(0, 10); 122 | $a = \gmp_init(1, 10); 123 | $b = \gmp_init(0, 10); 124 | $c = \gmp_init(0, 10); 125 | $d = \gmp_init(1, 10); 126 | 127 | do { 128 | for ($bits = $this->trailingZeroes($u); $bits > 0; --$bits) { 129 | $u = $this->rightShift($u, 1); 130 | $swap = (~$this->lsb($a) & ~$this->lsb($b)) & 1; 131 | 132 | $a = $this->select($swap, $a, $this->add($a, $y)); 133 | $a = $this->rightShift($a, 1); 134 | 135 | $b = $this->select($swap, $b, $this->sub($b, $x)); 136 | $b = $this->rightShift($b, 1); 137 | } 138 | 139 | for ($bits = $this->trailingZeroes($v); $bits > 0; --$bits) { 140 | $v = $this->rightShift($v, 1); 141 | $swap = (~$this->lsb($c) & ~$this->lsb($d)) & 1; 142 | 143 | $c = $this->select($swap, $c, $this->add($c, $y)); 144 | $c = $this->rightShift($c, 1); 145 | 146 | $d = $this->select($swap, $d, $this->sub($d, $x)); 147 | $d = $this->rightShift($d, 1); 148 | } 149 | 150 | $cmp = $this->cmp($u, $v); 151 | /* 152 | | cmp(u, v) | swap | 153 | +---------------+------+ 154 | | -1 | 0 | 155 | | 0 | 1 | 156 | | 1 | 1 | 157 | */ 158 | // if ($u >= $v): 159 | $swap = 1 - (($cmp >> 1) & 1); 160 | 161 | // swap = (1 - (compare_alt(u, v)[0] >>> 31)); 162 | $u = $this->select($swap, $this->sub($u, $v), $u); 163 | $a = $this->select($swap, $this->sub($a, $c), $a); 164 | $b = $this->select($swap, $this->sub($b, $d), $b); 165 | 166 | $swap = 1 - $swap; 167 | // else: 168 | $v = $this->select($swap, $this->sub($v, $u), $v); 169 | $c = $this->select($swap, $this->sub($c, $a), $c); 170 | $d = $this->select($swap, $this->sub($d, $b), $d); 171 | } while (!$this->equals($u, $zero)); 172 | 173 | return [$c, $this->leftShift($v, $g)]; 174 | } 175 | 176 | /** 177 | * Constant-time conditional select. 178 | * 179 | * returns ($bit === 1 ? $a : $b) 180 | * 181 | * @param int $bit 182 | * @param GMP $a 183 | * @param GMP $b 184 | * @return GMP 185 | */ 186 | public function select(int $bit, GMP $a, GMP $b): GMP 187 | { 188 | // Handle the sign bits (for multiplying later) 189 | $a_sign = gmp_sign($a); 190 | $b_sign = gmp_sign($b); 191 | /* if bit: sign = a_sign 192 | * else: sign = b_sign 193 | */ 194 | $sign = $b_sign ^ (($a_sign ^ $b_sign) & -$bit); 195 | 196 | // ($mask = $bit ? 0xff : 0x00) without branches 197 | $mask = -($bit & 1) & 0xff; 198 | 199 | // Work with the positive hex values: 200 | /** 201 | * @var string $left 202 | * @var string $right 203 | * @var int $length 204 | */ 205 | list($left, $right, $length) = $this->normalizeLengths($a, $b); 206 | 207 | $out = []; 208 | for ($i = 0; $i < $length; ++$i) { 209 | $l = $this->ord($left[$i]); 210 | $r = $this->ord($right[$i]); 211 | $out[$i] = $this->chr($r ^ (($l ^ $r) & $mask)); 212 | } 213 | // Re-multiply the sign bit: 214 | return $this->mul( 215 | gmp_init(Hex::encode(implode('', $out)), 16), 216 | gmp_init($sign, 10) 217 | ); 218 | } 219 | 220 | /** 221 | * How many trailing zero bits are in this number? 222 | * 223 | * We can't just use gmp_scan1() for this, because its runtime 224 | * is variable based on the number of trailing 0 bits. 225 | * 226 | * @param GMP $num 227 | * @return int 228 | * 229 | * @psalm-suppress UnusedVariable (False positive; https://github.com/vimeo/psalm/issues/6145) 230 | */ 231 | public function trailingZeroes(GMP $num): int 232 | { 233 | $trailing = 0; 234 | $b = 0; 235 | $found = 0; 236 | $strval = gmp_strval($num, 2); 237 | for ($i = BinaryString::length($strval) - 1; $i >= 0; --$i) { 238 | $bit = $this->ord($strval[$i]) & 1; 239 | $trailing = ((-$bit & $b) & ~$found) ^ ($trailing & $found); 240 | $found |= -$bit; // -1 if found, 0 if not 241 | ++$b; 242 | } 243 | return $trailing; 244 | } 245 | 246 | /** 247 | * Get the least significant bit of $num. 248 | * 249 | * @param GMP $num 250 | * @return int 251 | */ 252 | public function lsb(GMP $num): int 253 | { 254 | return gmp_intval($num) & 1; 255 | } 256 | 257 | /** 258 | * Get an unsigned integer for the character in the provided string at index 0. 259 | * 260 | * @param string $chr 261 | * @return int 262 | */ 263 | public function ord(string $chr): int 264 | { 265 | return (int) unpack('C', $chr)[1]; 266 | } 267 | 268 | /** 269 | * Turn an integer in the range [0, 255] into a string character. 270 | * Unlike PHP's chr(), this doesn't have a cache-timing leak. 271 | * 272 | * @param int $c 273 | * @return string 274 | */ 275 | public function chr(int $c): string 276 | { 277 | return pack('C', $c); 278 | } 279 | 280 | /** 281 | * Normalize the lengths of two input numbers. 282 | * 283 | * @param GMP $a 284 | * @param GMP $b 285 | * @return array 286 | */ 287 | public function normalizeLengths(GMP $a, GMP $b): array 288 | { 289 | $a_hex = gmp_strval(gmp_abs($a), 16); 290 | $b_hex = gmp_strval(gmp_abs($b), 16); 291 | $length = max(BinaryString::length($a_hex), BinaryString::length($b_hex)); 292 | $length += $length & 1; 293 | 294 | $left = Hex::decode(str_pad($a_hex, $length, '0', STR_PAD_LEFT)); 295 | $right = Hex::decode(str_pad($b_hex, $length, '0', STR_PAD_LEFT)); 296 | $length >>= 1; 297 | return [$left, $right, $length]; 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/ECDSA/HedgedRandomNumberGenerator.php: -------------------------------------------------------------------------------- 1 | 41 | */ 42 | private $algSize = array( 43 | 'sha1' => 160, 44 | 'sha224' => 224, 45 | 'sha256' => 256, 46 | 'sha384' => 384, 47 | 'sha512' => 512 48 | ); 49 | 50 | /** 51 | * Hedged constructor. 52 | * 53 | * @param GmpMathInterface $math 54 | * @param PrivateKeyInterface $privateKey 55 | * @param \GMP $messageHash - decimal hash of the message (*may* be truncated) 56 | * @param string $algorithm - hashing algorithm 57 | */ 58 | public function __construct( 59 | GmpMathInterface $math, 60 | PrivateKeyInterface $privateKey, 61 | \GMP $messageHash, 62 | string $algorithm 63 | ) { 64 | if (!isset($this->algSize[$algorithm])) { 65 | throw new \InvalidArgumentException('Unsupported hashing algorithm'); 66 | } 67 | 68 | $this->math = $math; 69 | $this->algorithm = $algorithm; 70 | $this->privateKey = $privateKey; 71 | $this->messageHash = $messageHash; 72 | } 73 | 74 | /** 75 | * @param string $bits - binary string of bits 76 | * @param \GMP $qlen - length of q in bits 77 | * @return \GMP 78 | */ 79 | public function bits2int(string $bits, \GMP $qlen): \GMP 80 | { 81 | $vlen = gmp_init(BinaryString::length($bits) * 8, 10); 82 | $hex = bin2hex($bits); 83 | $v = gmp_init($hex, 16); 84 | 85 | if ($this->math->cmp($vlen, $qlen) > 0) { 86 | $v = $this->math->rightShift($v, (int) $this->math->toString($this->math->sub($vlen, $qlen))); 87 | } 88 | 89 | return $v; 90 | } 91 | 92 | /** 93 | * @param \GMP $int 94 | * @param \GMP $rlen - rounded octet length 95 | * @return string 96 | */ 97 | public function int2octets(\GMP $int, \GMP $rlen): string 98 | { 99 | $out = pack("H*", $this->math->decHex(gmp_strval($int, 10))); 100 | $length = gmp_init(BinaryString::length($out), 10); 101 | if ($this->math->cmp($length, $rlen) < 0) { 102 | return str_pad('', (int) $this->math->toString($this->math->sub($rlen, $length)), "\x00") . $out; 103 | } 104 | 105 | if ($this->math->cmp($length, $rlen) > 0) { 106 | return BinaryString::substring($out, 0, (int) $this->math->toString($rlen)); 107 | } 108 | 109 | return $out; 110 | } 111 | 112 | /** 113 | * @param string $algorithm 114 | * @return int 115 | */ 116 | private function getHashLength(string $algorithm): int 117 | { 118 | return $this->algSize[$algorithm]; 119 | } 120 | 121 | /** 122 | * @param \GMP $max 123 | * @return \GMP 124 | */ 125 | public function generate(\GMP $max): \GMP 126 | { 127 | $qlen = gmp_init(NumberSize::bnNumBits($this->math, $max), 10); 128 | $rlen = $this->math->rightShift($this->math->add($qlen, gmp_init(7, 10)), 3); 129 | $hlen = $this->getHashLength($this->algorithm); 130 | $bx = $this->int2octets($this->privateKey->getSecret(), $rlen) . $this->int2octets($this->messageHash, $rlen); 131 | // This is the hedged part: 132 | $bx .= random_bytes(32); 133 | 134 | $v = str_pad('', $hlen >> 3, "\x01", STR_PAD_LEFT); 135 | $k = str_pad('', $hlen >> 3, "\x00", STR_PAD_LEFT); 136 | 137 | $k = hash_hmac($this->algorithm, $v . "\x00" . $bx, $k, true); 138 | $v = hash_hmac($this->algorithm, $v, $k, true); 139 | 140 | $k = hash_hmac($this->algorithm, $v . "\x01" . $bx, $k, true); 141 | $v = hash_hmac($this->algorithm, $v, $k, true); 142 | 143 | $t = ''; 144 | for ($tries = 0; $tries < 1024; ++$tries) { 145 | $toff = gmp_init(0, 10); 146 | while ($this->math->cmp($toff, $rlen) < 0) { 147 | $v = hash_hmac($this->algorithm, $v, $k, true); 148 | 149 | $cc = min(BinaryString::length($v), (int) gmp_strval(gmp_sub($rlen, $toff), 10)); 150 | $t .= BinaryString::substring($v, 0, $cc); 151 | $toff = gmp_add($toff, $cc); 152 | } 153 | $k = $this->bits2int($t, $qlen); 154 | if ($this->math->cmp($k, gmp_init(0, 10)) > 0 && $this->math->cmp($k, $max) < 0) { 155 | return $k; 156 | } 157 | 158 | $k = Hex::decode(gmp_strval($k, 16)); 159 | $k = hash_hmac($this->algorithm, $v . "\x00", $k, true); 160 | $v = hash_hmac($this->algorithm, $v, $k, true); 161 | } 162 | throw new EasyEccException('Infinite loop breached'); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/ECDSA/PublicKey.php: -------------------------------------------------------------------------------- 1 | serialize($this); 32 | } 33 | 34 | /** 35 | * @param string $encoded 36 | * @return self 37 | * @throws ParserException 38 | */ 39 | public static function importPem(string $encoded): self 40 | { 41 | $adapter = new GmpMath(); 42 | $serializer = new PublicKeyDerParser($adapter); 43 | 44 | $encoded = preg_replace('/-+(BEGIN|END).+?PUBLIC KEY-+/', '', $encoded); 45 | $encoded = preg_replace('/[^A-Za-z0-9+\/]/', '', $encoded); 46 | 47 | $data = Base64::decode($encoded); 48 | return self::promote($serializer->parse($data)); 49 | } 50 | 51 | /** 52 | * Promote an instance of the base public key type to this type. 53 | * 54 | * @param PublicKeyInterface $key 55 | * @return self 56 | */ 57 | public static function promote(PublicKeyInterface $key): self 58 | { 59 | return new self(EccFactory::getAdapter(), $key->getGenerator(), $key->getPoint()); 60 | } 61 | 62 | /** 63 | * @return string 64 | */ 65 | public function toString(): string 66 | { 67 | $serializer = new CompressedPointSerializer($this->getGenerator()->getAdapter()); 68 | return $serializer->serialize($this->getPoint()); 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | public function __toString() 75 | { 76 | return $this->toString(); 77 | } 78 | 79 | /** 80 | * @param string $hexString 81 | * @param string $curve 82 | * @return PublicKey 83 | * 84 | * @throws InvalidPublicKeyException 85 | */ 86 | public static function fromString( 87 | string $hexString, 88 | string $curve = EasyECC::DEFAULT_ECDSA_CURVE 89 | ): self { 90 | $adapter = EccFactory::getAdapter(); 91 | switch ($curve) { 92 | case 'K256': 93 | $generator = EccFactory::getSecgCurves()->generator256k1(null, true); 94 | $namedCurve = $generator->getCurve(); 95 | if (Binary::safeStrlen($hexString) !== 66) { 96 | throw new InvalidPublicKeyException('Public key is the wrong size for ' . $curve); 97 | } 98 | break; 99 | case 'P256': 100 | $generator = EccFactory::getNistCurves()->generator256(null, true); 101 | $namedCurve = $generator->getCurve(); 102 | if (Binary::safeStrlen($hexString) !== 66) { 103 | throw new InvalidPublicKeyException('Public key is the wrong size for ' . $curve); 104 | } 105 | break; 106 | case 'P384': 107 | $generator = EccFactory::getNistCurves()->generator384(null, true); 108 | $namedCurve = $generator->getCurve(); 109 | if (Binary::safeStrlen($hexString) !== 98) { 110 | throw new InvalidPublicKeyException('Public key is the wrong size for ' . $curve); 111 | } 112 | break; 113 | case 'P521': 114 | $generator = EccFactory::getNistCurves()->generator521(null, true); 115 | $namedCurve = $generator->getCurve(); 116 | if (Binary::safeStrlen($hexString) !== 134) { 117 | throw new InvalidPublicKeyException('Public key is the wrong size for ' . $curve); 118 | } 119 | break; 120 | default: 121 | throw new \TypeError('This can only be used with ECDSA keys'); 122 | } 123 | $serializer = new CompressedPointSerializer($adapter); 124 | $point = $serializer->unserialize($namedCurve, $hexString); 125 | return new self($adapter, $generator, $point); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/ECDSA/PublicKeyDerParser.php: -------------------------------------------------------------------------------- 1 | getAdapter(), 27 | $pk->getGenerator(), 28 | $pk->getPoint() 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ECDSA/SecretKey.php: -------------------------------------------------------------------------------- 1 | serialize($this); 28 | } 29 | 30 | /** 31 | * @return PublicKeyInterface 32 | */ 33 | public function getPublicKey(): PublicKeyInterface 34 | { 35 | $adapter = new GmpMath(); 36 | $pk = parent::getPublicKey(); 37 | return new PublicKey($adapter, $pk->getGenerator(), $pk->getPoint()); 38 | } 39 | 40 | /** 41 | * @param string $curve 42 | * @return self 43 | * @throws NotImplementedException 44 | */ 45 | public static function generate(string $curve = EasyECC::DEFAULT_ECDSA_CURVE): self 46 | { 47 | $generator = EasyECC::getGenerator($curve); 48 | $sk = EasyECC::getGenerator($curve, true)->createPrivateKey(); 49 | $adapter = new GmpMath(); 50 | return new self($adapter, $generator, $sk->getSecret()); 51 | } 52 | 53 | /** 54 | * @param string $encoded 55 | * @return self 56 | */ 57 | public static function importPem(string $encoded): self 58 | { 59 | $serializer = new PemPrivateKeySerializer(new DerPrivateKeySerializer()); 60 | $sk = $serializer->parse($encoded); 61 | if (!($sk instanceof PrivateKey)) { 62 | throw new \TypeError('Parsed public key MUST be an instance of the inherited class.'); 63 | } 64 | return self::promote($sk); 65 | } 66 | 67 | /** 68 | * @param PrivateKeyInterface $key 69 | * @return self 70 | */ 71 | public static function promote(PrivateKeyInterface $key): self 72 | { 73 | return new self(EccFactory::getAdapter(), $key->getPoint(), $key->getSecret()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/ECDSA/Signature.php: -------------------------------------------------------------------------------- 1 | getR(), 16); 26 | $s = gmp_strval($this->getS(), 16); 27 | $len = max(Binary::safeStrlen($r), Binary::safeStrlen($s), $length); 28 | return str_pad($r, $len, '0', STR_PAD_LEFT) . 29 | str_pad($s, $len, '0', STR_PAD_LEFT); 30 | } 31 | 32 | /** 33 | * Returns a hexadecimal-encoded signature. 34 | * 35 | * @return string 36 | */ 37 | public function __toString() 38 | { 39 | return $this->toString(); 40 | } 41 | 42 | /** 43 | * Promote an instance of the base signature type to this type. 44 | * 45 | * @param SignatureInterface $sig 46 | * @return self 47 | */ 48 | public static function promote(SignatureInterface $sig): self 49 | { 50 | return new self($sig->getR(), $sig->getS()); 51 | } 52 | 53 | /** 54 | * Serializes a signature from a hexadecimal string. 55 | * 56 | * @param string $hexString 57 | * @return self 58 | * @throws \SodiumException 59 | */ 60 | public static function fromString(string $hexString): self 61 | { 62 | $binary = sodium_hex2bin($hexString); 63 | $total_length = BinaryString::length($binary); 64 | if (($total_length & 1) !== 0) { 65 | throw new SignatureDecodeException('IEEE-P1363 signatures must be an even length'); 66 | } 67 | $piece_len = $total_length >> 1; 68 | $r = bin2hex(BinaryString::substring($binary, 0, $piece_len)); 69 | $s = bin2hex(BinaryString::substring($binary, $piece_len, $piece_len)); 70 | 71 | return new self(gmp_init($r, 16), gmp_init($s, 16)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/EasyECC.php: -------------------------------------------------------------------------------- 1 | 64, 40 | 'P256' => 64, 41 | 'P384' => 96, 42 | 'P521' => 132 43 | ]; 44 | 45 | /** @var string string */ 46 | protected $curve; 47 | 48 | /** @var GmpMathInterface $adapter */ 49 | protected $adapter; 50 | 51 | /** @var GeneratorPoint $generator */ 52 | protected $generator; 53 | 54 | /** @var string $hashAlgo */ 55 | protected $hashAlgo; 56 | 57 | /** @var SignHasher $hasher */ 58 | protected $hasher; 59 | 60 | /** 61 | * EasyECC constructor. 62 | * @param string $curve 63 | * @throws ConfigException 64 | */ 65 | public function __construct(string $curve = self::DEFAULT_CURVE) 66 | { 67 | if (!\in_array($curve, self::CURVES, true)) { 68 | throw new ConfigException('Invalid curve choice'); 69 | } 70 | $this->curve = $curve; 71 | switch ($curve) { 72 | case 'K256': 73 | $this->adapter = EccFactory::getAdapter(); 74 | $this->generator = SecureCurveFactory::getGeneratorByName('secp256k1'); 75 | $this->hashAlgo = 'sha256'; 76 | $this->hasher = new SignHasher($this->hashAlgo, $this->adapter); 77 | break; 78 | case 'P256': 79 | $this->adapter = EccFactory::getAdapter(); 80 | $this->generator = EccFactory::getNistCurves($this->adapter)->generator256( 81 | RandomGeneratorFactory::getRandomGenerator(), 82 | true 83 | ); 84 | $this->hashAlgo = 'sha256'; 85 | $this->hasher = new SignHasher($this->hashAlgo, $this->adapter); 86 | break; 87 | case 'P384': 88 | $this->adapter = EccFactory::getAdapter(); 89 | $this->generator = EccFactory::getNistCurves($this->adapter)->generator384( 90 | RandomGeneratorFactory::getRandomGenerator(), 91 | true 92 | ); 93 | $this->hashAlgo = 'sha384'; 94 | $this->hasher = new SignHasher($this->hashAlgo, $this->adapter); 95 | break; 96 | case 'P521': 97 | $this->adapter = EccFactory::getAdapter(); 98 | $this->generator = EccFactory::getNistCurves($this->adapter)->generator521(); 99 | $this->hashAlgo = 'sha512'; 100 | $this->hasher = new SignHasher($this->hashAlgo, $this->adapter); 101 | break; 102 | case 'sodium': 103 | break; 104 | } 105 | } 106 | 107 | /** 108 | * @return PrivateKeyInterface 109 | * @throws NotImplementedException 110 | * @throws \SodiumException 111 | */ 112 | public function generatePrivateKey(): PrivateKeyInterface 113 | { 114 | if ($this->curve === 'sodium') { 115 | return new EdwardsSecretKey(\sodium_crypto_sign_keypair()); 116 | } 117 | return SecretKey::generate($this->curve); 118 | } 119 | 120 | /** 121 | * @param PrivateKeyInterface $private 122 | * @param PublicKeyInterface $public 123 | * @param bool $isClient 124 | * @param string $hashAlgo 125 | * @return string 126 | * @throws \SodiumException 127 | * @throws \TypeError 128 | */ 129 | public function keyExchange( 130 | PrivateKeyInterface $private, 131 | PublicKeyInterface $public, 132 | bool $isClient, 133 | string $hashAlgo = '' 134 | ): string { 135 | if ($this->curve === 'sodium') { 136 | $ecdh = new X25519(); 137 | $ecdh->setSenderKey($private); 138 | $ecdh->setRecipientKey($public); 139 | return $ecdh->keyExchange($isClient); 140 | } 141 | if (empty($hashAlgo)) { 142 | // Use the default 143 | $hashAlgo = $this->hashAlgo; 144 | } 145 | $ss = $this->scalarMult($private, $public); 146 | $derSer = new DerPublicKeySerializer(); 147 | 148 | $recip_pk = $derSer->serialize($public); 149 | $sender_pk = $derSer->serialize($private->getPublicKey()); 150 | 151 | if ($isClient) { 152 | return hash( 153 | $hashAlgo, 154 | $ss . $sender_pk . $recip_pk, 155 | true 156 | ); 157 | } else { 158 | return hash( 159 | $hashAlgo, 160 | $ss . $recip_pk . $sender_pk, 161 | true 162 | ); 163 | } 164 | } 165 | 166 | /** 167 | * @param PrivateKeyInterface $private 168 | * @param PublicKeyInterface $public 169 | * @return string 170 | * @throws \SodiumException 171 | * @throws \TypeError 172 | */ 173 | public function scalarmult( 174 | PrivateKeyInterface $private, 175 | PublicKeyInterface $public 176 | ): string { 177 | if ($this->curve === 'sodium') { 178 | $ecdh = new X25519(); 179 | $ecdh->setSenderKey($private); 180 | $ecdh->setRecipientKey($public); 181 | return $ecdh->scalarMult(); 182 | } 183 | 184 | $scalarmult = $private 185 | ->createExchange($public) 186 | ->calculateSharedKey(); 187 | return $this->adapter->intToFixedSizeString( 188 | $scalarmult, 189 | NumberSize::bnNumBytes($this->adapter, $this->generator->getOrder()) 190 | ); 191 | } 192 | 193 | /** 194 | * @param string $message 195 | * @param PrivateKeyInterface $privateKey 196 | * @param bool $ieeeFormat Set to TRUE for IEEE-P1363 formatted signatures 197 | * @return string 198 | * 199 | * @throws \SodiumException 200 | * @throws \TypeError 201 | */ 202 | public function sign( 203 | string $message, 204 | PrivateKeyInterface $privateKey, 205 | bool $ieeeFormat = false 206 | ): string { 207 | if ($this->curve === 'sodium') { 208 | if ($privateKey instanceof EdwardsSecretKey) { 209 | return \sodium_crypto_sign_detached( 210 | $message, 211 | $privateKey->getAsString() 212 | ); 213 | } else { 214 | throw new \TypeError('Only Ed25519 secret keys can be used to sign'); 215 | } 216 | } 217 | $hash = $this->hasher->makeHash($message, $this->generator); 218 | 219 | // RFC 6979 with additional randomness 220 | $kGen = new HedgedRandomNumberGenerator( 221 | EccFactory::getAdapter(), 222 | $privateKey, 223 | $hash, 224 | $this->hashAlgo 225 | ); 226 | $k = $kGen->generate($this->generator->getOrder()); 227 | 228 | // We care about leaking the one-time secret: 229 | $signer = new Signer(new ConstantTimeMath(), true); 230 | $signature = $signer->sign($privateKey, $hash, $k); 231 | 232 | if ($ieeeFormat) { 233 | return (Signature::promote($signature)) 234 | ->toString(self::SIGNATURE_SIZES[$this->curve]); 235 | } 236 | $serializer = new DerSignatureSerializer(); 237 | return $serializer->serialize($signature); 238 | } 239 | 240 | /** 241 | * @param string $message 242 | * @param PublicKeyInterface $publicKey 243 | * @param string $signature 244 | * @param bool $ieeeFormat Set to TRUE for IEEE-P1363 formatted signatures 245 | * @return bool 246 | * 247 | * @throws ParserException 248 | * @throws \SodiumException 249 | * @throws \TypeError 250 | */ 251 | public function verify( 252 | string $message, 253 | PublicKeyInterface $publicKey, 254 | string $signature, 255 | bool $ieeeFormat = false 256 | ): bool { 257 | if ($this->curve === 'sodium') { 258 | if ($publicKey instanceof EdwardsPublicKey) { 259 | return \sodium_crypto_sign_verify_detached( 260 | $signature, 261 | $message, 262 | $publicKey->getAsString() 263 | ); 264 | } else { 265 | throw new \TypeError('Only Ed25519 secret keys can be used to sign'); 266 | } 267 | } 268 | 269 | if ($ieeeFormat) { 270 | $sig = Signature::fromString($signature); 271 | } else { 272 | $sigSerializer = new DerSignatureSerializer(); 273 | $sig = $sigSerializer->parse($signature); 274 | } 275 | 276 | $hash = $this->hasher->makeHash($message, $this->generator); 277 | 278 | // This can safely be variable-time: 279 | $signer = new Signer($this->adapter, true); 280 | 281 | return $signer->verify($publicKey, $sig, $hash); 282 | } 283 | 284 | /** 285 | * Which curve was this instantiated with? 286 | * 287 | * @return string 288 | */ 289 | public function getCurveName(): string 290 | { 291 | return $this->curve; 292 | } 293 | 294 | /** 295 | * @return int 296 | * @throws NotImplementedException 297 | */ 298 | public function getPublicKeyLength(): int 299 | { 300 | switch ($this->curve) { 301 | case 'sodium': 302 | return 64; 303 | case 'K256': 304 | case 'P256': 305 | return 66; 306 | case 'P384': 307 | return 98; 308 | case 'P521': 309 | return 134; 310 | default: 311 | throw new NotImplementedException("Unknown curve"); 312 | } 313 | } 314 | 315 | /** 316 | * @param string $curve 317 | * @param bool $constantTime 318 | * @return GeneratorPoint 319 | * @throws NotImplementedException 320 | */ 321 | public static function getGenerator( 322 | string $curve = self::DEFAULT_ECDSA_CURVE, 323 | bool $constantTime = false 324 | ): GeneratorPoint { 325 | switch ($curve) { 326 | case 'K256': 327 | return CurveFactory::getGeneratorByName('secp256k1'); 328 | case 'P256': 329 | if ($constantTime) { 330 | return EccFactory::getNistCurves(new ConstantTimeMath())->generator256( 331 | RandomGeneratorFactory::getRandomGenerator(), 332 | true 333 | ); 334 | } 335 | return EccFactory::getNistCurves()->generator256(); 336 | case 'P384': 337 | if ($constantTime) { 338 | return EccFactory::getNistCurves(new ConstantTimeMath())->generator384( 339 | RandomGeneratorFactory::getRandomGenerator(), 340 | true 341 | ); 342 | } 343 | return EccFactory::getNistCurves()->generator384(); 344 | case 'P521': 345 | if ($constantTime) { 346 | return EccFactory::getNistCurves(new ConstantTimeMath())->generator521(); 347 | } 348 | return EccFactory::getNistCurves()->generator521(); 349 | default: 350 | throw new NotImplementedException('This curve is not supported'); 351 | } 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /src/EncryptionInterface.php: -------------------------------------------------------------------------------- 1 | ecc = $ecc; 39 | } 40 | 41 | /** 42 | * @param PrivateKeyInterface $private 43 | * @param PublicKeyInterface $public 44 | * @param bool $isClient 45 | * @return Key 46 | * 47 | * @throws \Defuse\Crypto\Exception\BadFormatException 48 | * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException 49 | * @throws \SodiumException 50 | * @throws \TypeError 51 | */ 52 | public function keyExchange( 53 | PrivateKeyInterface $private, 54 | PublicKeyInterface $public, 55 | bool $isClient 56 | ): Key { 57 | return Key::loadFromAsciiSafeString( 58 | Encoding::saveBytesToChecksummedAsciiSafeString( 59 | Key::KEY_CURRENT_VERSION, 60 | $this->ecc->keyExchange($private, $public, $isClient, 'sha256') 61 | ) 62 | ); 63 | } 64 | 65 | /** 66 | * @param string $message 67 | * @param PrivateKeyInterface $privateKey 68 | * @param PublicKeyInterface $publicKey 69 | * @return string 70 | * 71 | * @throws EnvironmentIsBrokenException 72 | * @throws BadFormatException 73 | * @throws \SodiumException 74 | * @throws \TypeError 75 | */ 76 | public function asymmetricEncrypt( 77 | string $message, 78 | PrivateKeyInterface $privateKey, 79 | PublicKeyInterface $publicKey 80 | ): string { 81 | return $this->symmetricEncrypt( 82 | $message, 83 | $this->keyExchange($privateKey, $publicKey, true) 84 | ); 85 | } 86 | 87 | /** 88 | * @param string $message 89 | * @param PrivateKeyInterface $privateKey 90 | * @param PublicKeyInterface $publicKey 91 | * @return string 92 | * 93 | * @throws EnvironmentIsBrokenException 94 | * @throws WrongKeyOrModifiedCiphertextException 95 | * @throws BadFormatException 96 | * @throws \SodiumException 97 | * @throws \TypeError 98 | */ 99 | public function asymmetricDecrypt( 100 | string $message, 101 | PrivateKeyInterface $privateKey, 102 | PublicKeyInterface $publicKey 103 | ): string { 104 | return $this->symmetricDecrypt( 105 | $message, 106 | $this->keyExchange($privateKey, $publicKey, false) 107 | ); 108 | } 109 | 110 | /** 111 | * @param string $message 112 | * @param Key $key 113 | * @return string 114 | * 115 | * @throws EnvironmentIsBrokenException 116 | */ 117 | public function symmetricEncrypt(string $message, Key $key): string 118 | { 119 | return Base64UrlSafe::encode( 120 | Crypto::encrypt($message, $key, true) 121 | ); 122 | } 123 | 124 | /** 125 | * @param string $message 126 | * @param Key $key 127 | * @return string 128 | * 129 | * @throws EnvironmentIsBrokenException 130 | * @throws WrongKeyOrModifiedCiphertextException 131 | * @throws \TypeError 132 | */ 133 | public function symmetricDecrypt(string $message, Key $key): string 134 | { 135 | return Crypto::decrypt( 136 | Base64UrlSafe::decode($message), 137 | $key, 138 | true 139 | ); 140 | } 141 | 142 | /** 143 | * @param string $message 144 | * @param PublicKeyInterface $publicKey 145 | * @return string 146 | * 147 | * @throws BadFormatException 148 | * @throws EnvironmentIsBrokenException 149 | * @throws NotImplementedException 150 | * @throws SodiumException 151 | */ 152 | public function seal( 153 | string $message, 154 | PublicKeyInterface $publicKey 155 | ): string { 156 | $ephSK = $this->ecc->generatePrivateKey(); 157 | /** @var PublicKey|EdwardsPublicKey $ephPK */ 158 | $ephPK = $ephSK->getPublicKey(); 159 | $encrypted = $this->asymmetricEncrypt($message, $ephSK, $publicKey); 160 | return $ephPK->toString() . $encrypted; 161 | } 162 | 163 | /** 164 | * @param string $message 165 | * @param PrivateKeyInterface $privateKey 166 | * @return string 167 | * 168 | * @throws BadFormatException 169 | * @throws EnvironmentIsBrokenException 170 | * @throws InvalidPublicKeyException 171 | * @throws NotImplementedException 172 | * @throws SodiumException 173 | * @throws WrongKeyOrModifiedCiphertextException 174 | */ 175 | public function unseal( 176 | string $message, 177 | PrivateKeyInterface $privateKey 178 | ): string { 179 | $pkLen = $this->ecc->getPublicKeyLength(); 180 | $pubKey = Binary::safeSubstr($message, 0, $pkLen); 181 | $ciphertext = Binary::safeSubstr($message, $pkLen); 182 | 183 | /** @var PublicKey|EdwardsPublicKey $ephPK */ 184 | if ($this->ecc->getCurveName() === 'sodium') { 185 | $ephPK = EdwardsPublicKey::fromString($pubKey); 186 | } else { 187 | $ephPK = PublicKey::fromString( 188 | $pubKey, 189 | $this->ecc->getCurveName() 190 | ); 191 | } 192 | return $this->asymmetricDecrypt($ciphertext, $privateKey, $ephPK); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /tests/ECDSA/ConstantTimeMathTest.php: -------------------------------------------------------------------------------- 1 | $row) { 43 | list($first, $other, $gt, $eq) = $row; 44 | list($a, $b) = $math->compareSigns($first, $other); 45 | $this->assertSame($a, $gt, "gt is wrong on row {$i} ({$first}, {$other}, {$gt}, {$eq})"); 46 | $this->assertSame($b, $eq, "eq is wrong on row {$i} ({$first}, {$other}, {$gt}, {$eq})"); 47 | } 48 | } 49 | 50 | public function testCmp() 51 | { 52 | $math = new ConstantTimeMath(); 53 | 54 | $big = '01' . bin2hex(random_bytes(16)) . '01'; 55 | $bigger = '7f' . bin2hex(random_bytes(16)) . '7f'; 56 | $a = gmp_init($big, 16); 57 | $b = gmp_init($bigger, 16); 58 | $c = gmp_init('-' . $bigger, 16); // negative 59 | 60 | $this->assertEquals(-1, $math->cmp($a, $b), "{$a} < {$b}"); 61 | $this->assertEquals(0, $math->cmp($a, $a), "{$a} == {$a}"); 62 | $this->assertEquals(0, $math->cmp($b, $b), "{$b} == {$b}"); 63 | $this->assertEquals(1, $math->cmp($b, $a), "{$b} > {$a}"); 64 | 65 | $this->assertEquals(-1, $math->cmp($c, $b), "{$c} < {$b}"); 66 | $this->assertEquals(0, $math->cmp($b, $b), "{$b} == {$b}"); 67 | $this->assertEquals(0, $math->cmp($c, $c), "{$c} == {$c}"); 68 | $this->assertEquals(1, $math->cmp($b, $c), "{$c} > {$b}"); 69 | 70 | $d = gmp_init(0, 10); 71 | $e = gmp_init(1, 10); 72 | $this->assertEquals(-1, $math->cmp($d, $e), "{$d} < {$e}"); 73 | $this->assertEquals(0, $math->cmp($d, $d), "{$d} == {$d}"); 74 | $this->assertEquals(0, $math->cmp($e, $e), "{$e} == {$e}"); 75 | $this->assertEquals(1, $math->cmp($e, $d), "{$e} > {$d}"); 76 | 77 | $f = gmp_init('1e0ea4fd44a90d57c67fda8e7b9fb98b5dca575e777d911e6de72dfc8cd02b55', 16); 78 | $g = gmp_init('ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551', 16); 79 | $this->assertEquals(-1, $math->cmp($f, $g), "{$f} < {$g}"); 80 | } 81 | 82 | public function testOrdChr() 83 | { 84 | $math = new ConstantTimeMath(); 85 | $byte = random_bytes(1); 86 | $ord = $math->ord($byte); 87 | $this->assertGreaterThan(-1, $ord); 88 | $this->assertLessThan(256, $ord); 89 | $chr = $math->chr($ord); 90 | $this->assertSame(bin2hex($chr), bin2hex($byte)); 91 | } 92 | 93 | public function testTrailingZeroes() 94 | { 95 | $math = new ConstantTimeMath(); 96 | $vectors = [ 97 | [gmp_init('0000', 16), 0], // gmp_scan1($x, 0) says -1, we say 0 98 | [gmp_init('ffff', 16), 0], 99 | [gmp_init('fffe', 16), 1], 100 | [gmp_init('fffc', 16), 2], 101 | [gmp_init('fff8', 16), 3], 102 | [gmp_init('fff0', 16), 4], 103 | [gmp_init('ff00', 16), 8], 104 | [gmp_init('f000', 16), 12], 105 | [gmp_init('e000', 16), 13], 106 | [gmp_init('c000', 16), 14], 107 | [gmp_init('8000', 16), 15], 108 | ]; 109 | foreach ($vectors as $vector) { 110 | list($in, $expect) = $vector; 111 | $this->assertEquals( 112 | $expect, 113 | $math->trailingZeroes($in) 114 | ); 115 | } 116 | } 117 | 118 | public function testLsb() 119 | { 120 | $math = new ConstantTimeMath(); 121 | $odd = gmp_init('1234567', 10); 122 | $even = gmp_init('2345678', 10); 123 | $this->assertEquals(1, $math->lsb($odd)); 124 | $this->assertEquals(0, $math->lsb($even)); 125 | } 126 | 127 | public function testSelect() 128 | { 129 | $math = new ConstantTimeMath(); 130 | $left = gmp_init('1234567', 10); 131 | $right = gmp_init('7654321', 10); 132 | $this->assertEquals( 133 | $left, 134 | $math->select(1, $left, $right) 135 | ); 136 | $this->assertEquals( 137 | $right, 138 | $math->select(0, $left, $right) 139 | ); 140 | 141 | $left = gmp_init('-1234567', 10); 142 | $right = gmp_init('7654321', 10); 143 | $this->assertEquals( 144 | $left, 145 | $math->select(1, $left, $right) 146 | ); 147 | $this->assertEquals( 148 | $right, 149 | $math->select(0, $left, $right) 150 | ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tests/ECDSA/SignatureTest.php: -------------------------------------------------------------------------------- 1 | 33, 26 | 'P256' => 33, 27 | 'P384' => 49, 28 | 'P521' => 67 29 | ]; 30 | 31 | /** 32 | * @throws NotImplementedException 33 | * @throws ParserException 34 | * @throws ConfigException 35 | * @throws \SodiumException 36 | */ 37 | public function testSign() 38 | { 39 | $msg = 'this is a test message'; 40 | foreach (EasyECC::CURVES as $curve) { 41 | if (!array_key_exists($curve, EasyECC::SIGNATURE_SIZES)) { 42 | // Not an ECDSA curve 43 | continue; 44 | } 45 | $sk = SecretKey::generate($curve); 46 | /** @var PublicKey $pk */ 47 | $pk = $sk->getPublicKey(); 48 | $this->assertInstanceOf(PublicKey::class, $pk); 49 | $this->assertSame( 50 | self::PUBKEY_SIZES[$curve], 51 | BinaryString::length($pk->toString()) >> 1, 52 | 'Compressed public keys must be the correct length' 53 | ); 54 | 55 | $ecc = new EasyECC($curve); 56 | $signature = $ecc->sign($msg, $sk, true); 57 | $this->assertSame( 58 | EasyECC::SIGNATURE_SIZES[$curve], 59 | BinaryString::length($signature) >> 1, 60 | 'IEEE-P1363 formatted signatures must be the correct length' 61 | ); 62 | 63 | $this->assertTrue( 64 | $ecc->verify($msg, $pk, $signature, true), 65 | 'ECDSA signature must validate' 66 | ); 67 | 68 | $this->assertFalse( 69 | $ecc->verify($msg . ' foo', $pk, $signature, true), 70 | 'Invalid ECDSA signature must not validate' 71 | ); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/EasyECCTest.php: -------------------------------------------------------------------------------- 1 | assertSame('sodium', $ecc->getCurveName()); 19 | } 20 | 21 | public function easyEccCurves() 22 | { 23 | return [ 24 | [new EasyECC()], 25 | [new EasyECC('K256')], 26 | [new EasyECC('P256')], 27 | [new EasyECC('P384')], 28 | [new EasyECC('P521')], 29 | ]; 30 | } 31 | 32 | /** 33 | * @dataProvider easyEccCurves 34 | * @param EasyECC $ecc 35 | * 36 | * @throws NotImplementedException 37 | * @throws SodiumException 38 | */ 39 | public function testCongruentOps(EasyECC $ecc): void 40 | { 41 | $aliceSK = $ecc->generatePrivateKey(); 42 | /** @var PublicKey $alicePK */ 43 | $alicePK = $aliceSK->getPublicKey(); 44 | 45 | $goodMessage = 'This is a test message'; 46 | $badMessage = 'This is a test message!'; 47 | $sign = $ecc->sign($goodMessage, $aliceSK); 48 | $this->assertTrue($ecc->verify($goodMessage, $alicePK, $sign)); 49 | $this->assertFalse($ecc->verify($badMessage, $alicePK, $sign)); 50 | 51 | $bobSK = $ecc->generatePrivateKey(); 52 | /** @var PublicKey $bobPK */ 53 | $bobPK = $bobSK->getPublicKey(); 54 | 55 | $this->assertSame($ecc->getPublicKeyLength(), Binary::safeStrlen($bobPK->toString())); 56 | 57 | $this->assertNotSame( 58 | $alicePK->toString(), 59 | $bobPK->toString(), 60 | 'Same key generated?' 61 | ); 62 | 63 | // This should be equal (ECDH): 64 | $send = $ecc->keyExchange($aliceSK, $bobPK, true); 65 | $recv = $ecc->keyExchange($bobSK, $alicePK, false); 66 | $this->assertSame(Hex::encode($send), Hex::encode($recv), 'Key exchange'); 67 | 68 | // This should also be equal: 69 | $send2 = $ecc->keyExchange($aliceSK, $bobPK, false); 70 | $recv2 = $ecc->keyExchange($bobSK, $alicePK, true); 71 | $this->assertSame(Hex::encode($send2), Hex::encode($recv2), 'Key exchange'); 72 | 73 | // These MUST differ, since we're mixing the data in different orders: 74 | $this->assertNotSame(Hex::encode($send), Hex::encode($recv2), 'Key exchange'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Integration/DefuseTest.php: -------------------------------------------------------------------------------- 1 | k256 = new EasyECC('K256'); 40 | $this->p256 = new EasyECC('P256'); 41 | $this->p384 = new EasyECC('P384'); 42 | $this->p521 = new EasyECC('P521'); 43 | $this->sodium = new EasyECC(); 44 | } 45 | 46 | /** 47 | * @throws BadFormatException 48 | * @throws EnvironmentIsBrokenException 49 | * @throws WrongKeyOrModifiedCiphertextException 50 | * @throws \SodiumException 51 | * @throws \TypeError 52 | */ 53 | public function testAsymmetricEncryptK256() 54 | { 55 | $alice_sk = $this->k256->generatePrivateKey(); 56 | $alice_pk = $alice_sk->getPublicKey(); 57 | $bob_sk = $this->k256->generatePrivateKey(); 58 | $bob_pk = $bob_sk->getPublicKey(); 59 | 60 | $defuse = new Defuse($this->k256); 61 | 62 | $message = 'This is a test message.'; 63 | $ciphertext = $defuse->asymmetricEncrypt($message, $alice_sk, $bob_pk); 64 | 65 | $this->assertSame( 66 | $message, 67 | $defuse->asymmetricDecrypt($ciphertext, $bob_sk, $alice_pk) 68 | ); 69 | } 70 | 71 | /** 72 | * @throws BadFormatException 73 | * @throws EnvironmentIsBrokenException 74 | * @throws WrongKeyOrModifiedCiphertextException 75 | * @throws \SodiumException 76 | * @throws \TypeError 77 | */ 78 | public function testAsymmetricEncryptP256() 79 | { 80 | $alice_sk = $this->p256->generatePrivateKey(); 81 | $alice_pk = $alice_sk->getPublicKey(); 82 | $bob_sk = $this->p256->generatePrivateKey(); 83 | $bob_pk = $bob_sk->getPublicKey(); 84 | 85 | $defuse = new Defuse($this->p256); 86 | 87 | $message = 'This is a test message.'; 88 | $ciphertext = $defuse->asymmetricEncrypt($message, $alice_sk, $bob_pk); 89 | 90 | $this->assertSame( 91 | $message, 92 | $defuse->asymmetricDecrypt($ciphertext, $bob_sk, $alice_pk) 93 | ); 94 | } 95 | 96 | /** 97 | * @throws BadFormatException 98 | * @throws EnvironmentIsBrokenException 99 | * @throws WrongKeyOrModifiedCiphertextException 100 | * @throws \SodiumException 101 | * @throws \TypeError 102 | */ 103 | public function testAsymmetricEncryptP384() 104 | { 105 | $alice_sk = $this->p384->generatePrivateKey(); 106 | $alice_pk = $alice_sk->getPublicKey(); 107 | $bob_sk = $this->p384->generatePrivateKey(); 108 | $bob_pk = $bob_sk->getPublicKey(); 109 | 110 | $defuse = new Defuse($this->p384); 111 | 112 | $message = 'This is a test message.'; 113 | $ciphertext = $defuse->asymmetricEncrypt($message, $alice_sk, $bob_pk); 114 | 115 | $this->assertSame( 116 | $message, 117 | $defuse->asymmetricDecrypt($ciphertext, $bob_sk, $alice_pk) 118 | ); 119 | } 120 | 121 | /** 122 | * @throws BadFormatException 123 | * @throws EnvironmentIsBrokenException 124 | * @throws WrongKeyOrModifiedCiphertextException 125 | * @throws \SodiumException 126 | * @throws \TypeError 127 | */ 128 | public function testAsymmetricEncryptP521() 129 | { 130 | $alice_sk = $this->p521->generatePrivateKey(); 131 | $alice_pk = $alice_sk->getPublicKey(); 132 | $bob_sk = $this->p521->generatePrivateKey(); 133 | $bob_pk = $bob_sk->getPublicKey(); 134 | 135 | $defuse = new Defuse($this->p521); 136 | 137 | $message = 'This is a test message.'; 138 | $ciphertext = $defuse->asymmetricEncrypt($message, $alice_sk, $bob_pk); 139 | 140 | $this->assertSame( 141 | $message, 142 | $defuse->asymmetricDecrypt($ciphertext, $bob_sk, $alice_pk) 143 | ); 144 | } 145 | 146 | /** 147 | * @throws BadFormatException 148 | * @throws EnvironmentIsBrokenException 149 | * @throws WrongKeyOrModifiedCiphertextException 150 | * @throws \SodiumException 151 | * @throws \TypeError 152 | */ 153 | public function testAsymmetricEncryptSodium() 154 | { 155 | $alice_sk = $this->sodium->generatePrivateKey(); 156 | $alice_pk = $alice_sk->getPublicKey(); 157 | $bob_sk = $this->sodium->generatePrivateKey(); 158 | $bob_pk = $bob_sk->getPublicKey(); 159 | 160 | $defuse = new Defuse($this->sodium); 161 | 162 | $message = 'This is a test message.'; 163 | $ciphertext = $defuse->asymmetricEncrypt($message, $alice_sk, $bob_pk); 164 | 165 | $this->assertSame( 166 | $message, 167 | $defuse->asymmetricDecrypt($ciphertext, $bob_sk, $alice_pk) 168 | ); 169 | } 170 | 171 | public function easyEccCurves(): array 172 | { 173 | return [ 174 | [new EasyECC()], 175 | [new EasyECC('K256')], 176 | [new EasyECC('P256')], 177 | [new EasyECC('P384')], 178 | [new EasyECC('P521')], 179 | ]; 180 | } 181 | 182 | /** 183 | * @dataProvider easyEccCurves 184 | */ 185 | public function testSeal(EasyECC $ecc): void 186 | { 187 | $defuse = new Defuse($ecc); 188 | $alice_sk = $ecc->generatePrivateKey(); 189 | $alice_pk = $alice_sk->getPublicKey(); 190 | 191 | $message = 'This is a test message.'; 192 | $sealed1 = $defuse->seal($message, $alice_pk); 193 | $sealed2 = $defuse->seal($message, $alice_pk); 194 | $this->assertNotSame($sealed1, $sealed2, 'Ciphertexts must not be the same'); 195 | 196 | $unsealed1 = $defuse->unseal($sealed1, $alice_sk); 197 | $unsealed2 = $defuse->unseal($sealed2, $alice_sk); 198 | $this->assertSame($unsealed1, $unsealed2, 'Plaintexts should be the same'); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /tests/K256Test.php: -------------------------------------------------------------------------------- 1 | ecc = new EasyECC('K256'); 23 | } 24 | 25 | /** 26 | * @throws \SodiumException 27 | * @throws \TypeError 28 | */ 29 | public function testKeyExchange() 30 | { 31 | $alice_sk = $this->ecc->generatePrivateKey(); 32 | $alice_pk = $alice_sk->getPublicKey(); 33 | $bob_sk = $this->ecc->generatePrivateKey(); 34 | $bob_pk = $bob_sk->getPublicKey(); 35 | $alice_to_bob = $this->ecc->keyExchange($alice_sk, $bob_pk, false); 36 | $bob_to_alice = $this->ecc->keyExchange($bob_sk, $alice_pk, true); 37 | $alice_to_bob2 = $this->ecc->keyExchange($alice_sk, $bob_pk, true); 38 | $bob_to_alice2 = $this->ecc->keyExchange($bob_sk, $alice_pk, false); 39 | $this->assertSame($alice_to_bob, $bob_to_alice); 40 | $this->assertSame($alice_to_bob2, $bob_to_alice2); 41 | $this->assertNotSame($alice_to_bob, $bob_to_alice2); 42 | $this->assertNotSame($alice_to_bob2, $bob_to_alice); 43 | } 44 | 45 | /** 46 | * @throws \SodiumException 47 | * @throws \TypeError 48 | */ 49 | public function testScalarMult() 50 | { 51 | $alice_sk = $this->ecc->generatePrivateKey(); 52 | $alice_pk = $alice_sk->getPublicKey(); 53 | $bob_sk = $this->ecc->generatePrivateKey(); 54 | $bob_pk = $bob_sk->getPublicKey(); 55 | $alice_to_bob = $this->ecc->scalarmult($alice_sk, $bob_pk); 56 | $bob_to_alice = $this->ecc->scalarmult($bob_sk, $alice_pk); 57 | $this->assertSame($alice_to_bob, $bob_to_alice); 58 | } 59 | 60 | /** 61 | * @throws ParserException 62 | * @throws \SodiumException 63 | * @throws \TypeError 64 | */ 65 | public function testSign() 66 | { 67 | $sk = $this->ecc->generatePrivateKey(); 68 | $pk = $sk->getPublicKey(); 69 | 70 | $message = 'sample'; 71 | $sig = $this->ecc->sign($message, $sk); 72 | $this->assertTrue($this->ecc->verify($message, $pk, $sig)); 73 | $this->assertFalse($this->ecc->verify('samplf', $pk, $sig)); 74 | 75 | $sig = $this->ecc->sign($message, $sk, true); 76 | $this->assertTrue($this->ecc->verify($message, $pk, $sig, true)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/KeyLengthTest.php: -------------------------------------------------------------------------------- 1 | generatePrivateKey(); 33 | $pk = $sk->getPublicKey(); 34 | $encoder = new DerPublicKeySerializer(); 35 | $der = $encoder->serialize($pk); 36 | $this->assertSame($expected, Binary::safeStrlen($der), 'length mismatch'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/P256Test.php: -------------------------------------------------------------------------------- 1 | ecc = new EasyECC('P256'); 23 | } 24 | 25 | /** 26 | * @throws \SodiumException 27 | * @throws \TypeError 28 | */ 29 | public function testKeyExchange() 30 | { 31 | $alice_sk = $this->ecc->generatePrivateKey(); 32 | $alice_pk = $alice_sk->getPublicKey(); 33 | $bob_sk = $this->ecc->generatePrivateKey(); 34 | $bob_pk = $bob_sk->getPublicKey(); 35 | $alice_to_bob = $this->ecc->keyExchange($alice_sk, $bob_pk, false); 36 | $bob_to_alice = $this->ecc->keyExchange($bob_sk, $alice_pk, true); 37 | $alice_to_bob2 = $this->ecc->keyExchange($alice_sk, $bob_pk, true); 38 | $bob_to_alice2 = $this->ecc->keyExchange($bob_sk, $alice_pk, false); 39 | $this->assertSame($alice_to_bob, $bob_to_alice); 40 | $this->assertSame($alice_to_bob2, $bob_to_alice2); 41 | $this->assertNotSame($alice_to_bob, $bob_to_alice2); 42 | $this->assertNotSame($alice_to_bob2, $bob_to_alice); 43 | } 44 | 45 | /** 46 | * @throws \SodiumException 47 | * @throws \TypeError 48 | */ 49 | public function testScalarMult() 50 | { 51 | $alice_sk = $this->ecc->generatePrivateKey(); 52 | $alice_pk = $alice_sk->getPublicKey(); 53 | $bob_sk = $this->ecc->generatePrivateKey(); 54 | $bob_pk = $bob_sk->getPublicKey(); 55 | $alice_to_bob = $this->ecc->scalarmult($alice_sk, $bob_pk); 56 | $bob_to_alice = $this->ecc->scalarmult($bob_sk, $alice_pk); 57 | $this->assertSame($alice_to_bob, $bob_to_alice); 58 | } 59 | 60 | /** 61 | * @throws ParserException 62 | * @throws \SodiumException 63 | * @throws \TypeError 64 | */ 65 | public function testSign() 66 | { 67 | $sk = $this->ecc->generatePrivateKey(); 68 | $pk = $sk->getPublicKey(); 69 | 70 | $message = 'sample'; 71 | $sig = $this->ecc->sign($message, $sk); 72 | $this->assertTrue($this->ecc->verify($message, $pk, $sig)); 73 | $this->assertFalse($this->ecc->verify('samplf', $pk, $sig)); 74 | 75 | $sig = $this->ecc->sign($message, $sk, true); 76 | $this->assertTrue($this->ecc->verify($message, $pk, $sig, true)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/P384Test.php: -------------------------------------------------------------------------------- 1 | ecc = new EasyECC('P384'); 23 | } 24 | 25 | /** 26 | * @throws \SodiumException 27 | * @throws \TypeError 28 | */ 29 | public function testKeyExchange() 30 | { 31 | $alice_sk = $this->ecc->generatePrivateKey(); 32 | $alice_pk = $alice_sk->getPublicKey(); 33 | $bob_sk = $this->ecc->generatePrivateKey(); 34 | $bob_pk = $bob_sk->getPublicKey(); 35 | $alice_to_bob = $this->ecc->keyExchange($alice_sk, $bob_pk, false); 36 | $bob_to_alice = $this->ecc->keyExchange($bob_sk, $alice_pk, true); 37 | $alice_to_bob2 = $this->ecc->keyExchange($alice_sk, $bob_pk, true); 38 | $bob_to_alice2 = $this->ecc->keyExchange($bob_sk, $alice_pk, false); 39 | $this->assertSame($alice_to_bob, $bob_to_alice); 40 | $this->assertSame($alice_to_bob2, $bob_to_alice2); 41 | $this->assertNotSame($alice_to_bob, $bob_to_alice2); 42 | $this->assertNotSame($alice_to_bob2, $bob_to_alice); 43 | } 44 | 45 | /** 46 | * @throws \SodiumException 47 | * @throws \TypeError 48 | */ 49 | public function testScalarMult() 50 | { 51 | $alice_sk = $this->ecc->generatePrivateKey(); 52 | $alice_pk = $alice_sk->getPublicKey(); 53 | $bob_sk = $this->ecc->generatePrivateKey(); 54 | $bob_pk = $bob_sk->getPublicKey(); 55 | $alice_to_bob = $this->ecc->scalarmult($alice_sk, $bob_pk); 56 | $bob_to_alice = $this->ecc->scalarmult($bob_sk, $alice_pk); 57 | $this->assertSame($alice_to_bob, $bob_to_alice); 58 | } 59 | 60 | /** 61 | * @throws ParserException 62 | * @throws \SodiumException 63 | * @throws \TypeError 64 | */ 65 | public function testSign() 66 | { 67 | $sk = $this->ecc->generatePrivateKey(); 68 | $pk = $sk->getPublicKey(); 69 | 70 | $message = 'sample'; 71 | $sig = $this->ecc->sign($message, $sk); 72 | $this->assertTrue($this->ecc->verify($message, $pk, $sig)); 73 | $this->assertFalse($this->ecc->verify('samplf', $pk, $sig)); 74 | 75 | $sig = $this->ecc->sign($message, $sk, true); 76 | $this->assertTrue($this->ecc->verify($message, $pk, $sig, true)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/P521Test.php: -------------------------------------------------------------------------------- 1 | ecc = new EasyECC('P521'); 23 | } 24 | 25 | /** 26 | * @throws \SodiumException 27 | * @throws \TypeError 28 | */ 29 | public function testKeyExchange() 30 | { 31 | $alice_sk = $this->ecc->generatePrivateKey(); 32 | $alice_pk = $alice_sk->getPublicKey(); 33 | $bob_sk = $this->ecc->generatePrivateKey(); 34 | $bob_pk = $bob_sk->getPublicKey(); 35 | $alice_to_bob = $this->ecc->keyExchange($alice_sk, $bob_pk, false); 36 | $bob_to_alice = $this->ecc->keyExchange($bob_sk, $alice_pk, true); 37 | $alice_to_bob2 = $this->ecc->keyExchange($alice_sk, $bob_pk, true); 38 | $bob_to_alice2 = $this->ecc->keyExchange($bob_sk, $alice_pk, false); 39 | $this->assertSame($alice_to_bob, $bob_to_alice); 40 | $this->assertSame($alice_to_bob2, $bob_to_alice2); 41 | $this->assertNotSame($alice_to_bob, $bob_to_alice2); 42 | $this->assertNotSame($alice_to_bob2, $bob_to_alice); 43 | } 44 | 45 | /** 46 | * @throws \SodiumException 47 | * @throws \TypeError 48 | */ 49 | public function testScalarMult() 50 | { 51 | $alice_sk = $this->ecc->generatePrivateKey(); 52 | $alice_pk = $alice_sk->getPublicKey(); 53 | $bob_sk = $this->ecc->generatePrivateKey(); 54 | $bob_pk = $bob_sk->getPublicKey(); 55 | $alice_to_bob = $this->ecc->scalarmult($alice_sk, $bob_pk); 56 | $bob_to_alice = $this->ecc->scalarmult($bob_sk, $alice_pk); 57 | $this->assertSame($alice_to_bob, $bob_to_alice); 58 | } 59 | 60 | /** 61 | * @throws ParserException 62 | * @throws \SodiumException 63 | * @throws \TypeError 64 | */ 65 | public function testSign() 66 | { 67 | $sk = $this->ecc->generatePrivateKey(); 68 | $pk = $sk->getPublicKey(); 69 | 70 | $message = 'sample'; 71 | $sig = $this->ecc->sign($message, $sk); 72 | $this->assertTrue($this->ecc->verify($message, $pk, $sig)); 73 | $this->assertFalse($this->ecc->verify('samplf', $pk, $sig)); 74 | 75 | $sig = $this->ecc->sign($message, $sk, true); 76 | $this->assertTrue($this->ecc->verify($message, $pk, $sig, true)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/SodiumTest.php: -------------------------------------------------------------------------------- 1 | ecc = new EasyECC('sodium'); 23 | } 24 | 25 | /** 26 | * @throws \SodiumException 27 | * @throws \TypeError 28 | */ 29 | public function testKeyExchange() 30 | { 31 | $alice_sk = $this->ecc->generatePrivateKey(); 32 | $alice_pk = $alice_sk->getPublicKey(); 33 | $bob_sk = $this->ecc->generatePrivateKey(); 34 | $bob_pk = $bob_sk->getPublicKey(); 35 | $alice_to_bob = $this->ecc->keyExchange($alice_sk, $bob_pk, false); 36 | $bob_to_alice = $this->ecc->keyExchange($bob_sk, $alice_pk, true); 37 | $alice_to_bob2 = $this->ecc->keyExchange($alice_sk, $bob_pk, true); 38 | $bob_to_alice2 = $this->ecc->keyExchange($bob_sk, $alice_pk, false); 39 | $this->assertSame($alice_to_bob, $bob_to_alice); 40 | $this->assertSame($alice_to_bob2, $bob_to_alice2); 41 | $this->assertNotSame($alice_to_bob, $bob_to_alice2); 42 | $this->assertNotSame($alice_to_bob2, $bob_to_alice); 43 | } 44 | 45 | /** 46 | * @throws \SodiumException 47 | * @throws \TypeError 48 | */ 49 | public function testScalarMult() 50 | { 51 | $alice_sk = $this->ecc->generatePrivateKey(); 52 | $alice_pk = $alice_sk->getPublicKey(); 53 | $bob_sk = $this->ecc->generatePrivateKey(); 54 | $bob_pk = $bob_sk->getPublicKey(); 55 | $alice_to_bob = $this->ecc->scalarmult($alice_sk, $bob_pk); 56 | $bob_to_alice = $this->ecc->scalarmult($bob_sk, $alice_pk); 57 | $this->assertSame($alice_to_bob, $bob_to_alice); 58 | } 59 | 60 | /** 61 | * @throws ParserException 62 | * @throws \SodiumException 63 | * @throws \TypeError 64 | */ 65 | public function testSign() 66 | { 67 | $sk = $this->ecc->generatePrivateKey(); 68 | $pk = $sk->getPublicKey(); 69 | 70 | $message = 'sample'; 71 | $sig = $this->ecc->sign($message, $sk); 72 | $this->assertTrue($this->ecc->verify($message, $pk, $sig)); 73 | $this->assertFalse($this->ecc->verify('samplf', $pk, $sig)); 74 | } 75 | } 76 | --------------------------------------------------------------------------------