xref: /dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH.php (revision a896ec97b4d9a77a7ab6956f96aaa0e7987f57d1)
1<?php
2
3/**
4 * Pure-PHP (EC)DH implementation
5 *
6 * PHP version 5
7 *
8 * Here's an example of how to compute a shared secret with this library:
9 * <code>
10 * <?php
11 * include 'vendor/autoload.php';
12 *
13 * $ourPrivate = \phpseclib3\Crypt\DH::createKey();
14 * $secret = DH::computeSecret($ourPrivate, $theirPublic);
15 *
16 * ?>
17 * </code>
18 *
19 * @author    Jim Wigginton <terrafrost@php.net>
20 * @copyright 2016 Jim Wigginton
21 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
22 * @link      http://phpseclib.sourceforge.net
23 */
24
25namespace phpseclib3\Crypt;
26
27use phpseclib3\Crypt\Common\AsymmetricKey;
28use phpseclib3\Crypt\DH\Parameters;
29use phpseclib3\Crypt\DH\PrivateKey;
30use phpseclib3\Crypt\DH\PublicKey;
31use phpseclib3\Crypt\EC\Curves\Curve25519;
32use phpseclib3\Crypt\EC\Curves\Curve448;
33use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
34use phpseclib3\Exception\BadConfigurationException;
35use phpseclib3\Exception\NoKeyLoadedException;
36use phpseclib3\Exception\UnsupportedOperationException;
37use phpseclib3\File\ASN1;
38use phpseclib3\File\ASN1\Maps;
39use phpseclib3\Math\BigInteger;
40
41/**
42 * Pure-PHP (EC)DH implementation
43 *
44 * @author  Jim Wigginton <terrafrost@php.net>
45 */
46abstract class DH extends AsymmetricKey
47{
48    /**
49     * Algorithm Name
50     *
51     * @var string
52     */
53    const ALGORITHM = 'DH';
54
55    /**
56     * DH prime
57     *
58     * @var BigInteger
59     */
60    protected $prime;
61
62    /**
63     * DH Base
64     *
65     * Prime divisor of p-1
66     *
67     * @var BigInteger
68     */
69    protected $base;
70
71    /**
72     * Public Key
73     *
74     * @var BigInteger
75     */
76    protected $publicKey;
77
78    /**
79     * Create DH parameters
80     *
81     * This method is a bit polymorphic. It can take any of the following:
82     *  - two BigInteger's (prime and base)
83     *  - an integer representing the size of the prime in bits (the base is assumed to be 2)
84     *  - a string (eg. diffie-hellman-group14-sha1)
85     *
86     * @return Parameters
87     */
88    public static function createParameters(...$args)
89    {
90        $class = new \ReflectionClass(static::class);
91        if ($class->isFinal()) {
92            throw new \RuntimeException('createParameters() should not be called from final classes (' . static::class . ')');
93        }
94
95        $params = new Parameters();
96        if (count($args) == 2 && $args[0] instanceof BigInteger && $args[1] instanceof BigInteger) {
97            //if (!$args[0]->isPrime()) {
98            //    throw new \InvalidArgumentException('The first parameter should be a prime number');
99            //}
100            $params->prime = $args[0];
101            $params->base = $args[1];
102            return $params;
103        } elseif (count($args) == 1 && is_numeric($args[0])) {
104            $params->prime = BigInteger::randomPrime($args[0]);
105            $params->base = new BigInteger(2);
106            return $params;
107        } elseif (count($args) != 1 || !is_string($args[0])) {
108            throw new \InvalidArgumentException('Valid parameters are either: two BigInteger\'s (prime and base), a single integer (the length of the prime; base is assumed to be 2) or a string');
109        }
110        switch ($args[0]) {
111            // see http://tools.ietf.org/html/rfc2409#section-6.2 and
112            // http://tools.ietf.org/html/rfc2412, appendex E
113            case 'diffie-hellman-group1-sha1':
114                $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
115                         '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
116                         '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
117                         'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
118                break;
119            // see http://tools.ietf.org/html/rfc3526#section-3
120            case 'diffie-hellman-group14-sha1': // 2048-bit MODP Group
121            case 'diffie-hellman-group14-sha256':
122                $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
123                         '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
124                         '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
125                         'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
126                         '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
127                         '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
128                         'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
129                         '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
130                break;
131            // see https://tools.ietf.org/html/rfc3526#section-4
132            case 'diffie-hellman-group15-sha512': // 3072-bit MODP Group
133                $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
134                         '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
135                         '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
136                         'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
137                         '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
138                         '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
139                         'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
140                         '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
141                         'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
142                         'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
143                         'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
144                         '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF';
145                break;
146            // see https://tools.ietf.org/html/rfc3526#section-5
147            case 'diffie-hellman-group16-sha512': // 4096-bit MODP Group
148                $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
149                         '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
150                         '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
151                         'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
152                         '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
153                         '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
154                         'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
155                         '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
156                         'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
157                         'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
158                         'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
159                         '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
160                         '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
161                         'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
162                         '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
163                         '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF';
164                break;
165            // see https://tools.ietf.org/html/rfc3526#section-6
166            case 'diffie-hellman-group17-sha512': // 6144-bit MODP Group
167                $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
168                         '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
169                         '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
170                         'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
171                         '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
172                         '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
173                         'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
174                         '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
175                         'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
176                         'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
177                         'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
178                         '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
179                         '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
180                         'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
181                         '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
182                         '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' .
183                         'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' .
184                         'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' .
185                         'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' .
186                         'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' .
187                         '59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' .
188                         'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' .
189                         'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' .
190                         '043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF';
191                break;
192            // see https://tools.ietf.org/html/rfc3526#section-7
193            case 'diffie-hellman-group18-sha512': // 8192-bit MODP Group
194                $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
195                         '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
196                         '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
197                         'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
198                         '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
199                         '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
200                         'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
201                         '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
202                         'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
203                         'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
204                         'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
205                         '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
206                         '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
207                         'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
208                         '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
209                         '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' .
210                         'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' .
211                         'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' .
212                         'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' .
213                         'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' .
214                         '59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' .
215                         'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' .
216                         'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' .
217                         '043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4' .
218                         '38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED' .
219                         '2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652D' .
220                         'E3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B' .
221                         '4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6' .
222                         '6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851D' .
223                         'F9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92' .
224                         '4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA' .
225                         '9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF';
226                break;
227            default:
228                throw new \InvalidArgumentException('Invalid named prime provided');
229        }
230
231        $params->prime = new BigInteger($prime, 16);
232        $params->base = new BigInteger(2);
233
234        return $params;
235    }
236
237    /**
238     * Create public / private key pair.
239     *
240     * The rationale for the second parameter is described in http://tools.ietf.org/html/rfc4419#section-6.2 :
241     *
242     * "To increase the speed of the key exchange, both client and server may
243     *  reduce the size of their private exponents.  It should be at least
244     *  twice as long as the key material that is generated from the shared
245     *  secret.  For more details, see the paper by van Oorschot and Wiener
246     *  [VAN-OORSCHOT]."
247     *
248     * $length is in bits
249     *
250     * @param Parameters $params
251     * @param int $length optional
252     * @return PrivateKey
253     */
254    public static function createKey(Parameters $params, $length = 0)
255    {
256        $class = new \ReflectionClass(static::class);
257        if ($class->isFinal()) {
258            throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
259        }
260
261        $one = new BigInteger(1);
262        if ($length) {
263            $max = $one->bitwise_leftShift($length);
264            $max = $max->subtract($one);
265        } else {
266            $max = $params->prime->subtract($one);
267        }
268
269        $key = new PrivateKey();
270        $key->prime = $params->prime;
271        $key->base = $params->base;
272        $key->privateKey = BigInteger::randomRange($one, $max);
273        $key->publicKey = $key->base->powMod($key->privateKey, $key->prime);
274        return $key;
275    }
276
277    /**
278     * Compute Shared Secret
279     *
280     * @param PrivateKey|EC $private
281     * @param PublicKey|BigInteger|string $public
282     * @return mixed
283     */
284    public static function computeSecret($private, $public)
285    {
286        if ($private instanceof PrivateKey) { // DH\PrivateKey
287            switch (true) {
288                case $public instanceof PublicKey:
289                    if (!$private->prime->equals($public->prime) || !$private->base->equals($public->base)) {
290                        throw new \InvalidArgumentException('The public and private key do not share the same prime and / or base numbers');
291                    }
292                    return $public->publicKey->powMod($private->privateKey, $private->prime)->toBytes(true);
293                case is_string($public):
294                    $public = new BigInteger($public, -256);
295                    // fall-through
296                case $public instanceof BigInteger:
297                    return $public->powMod($private->privateKey, $private->prime)->toBytes(true);
298                default:
299                    throw new \InvalidArgumentException('$public needs to be an instance of DH\PublicKey, a BigInteger or a string');
300            }
301        }
302
303        if ($private instanceof EC\PrivateKey) {
304            $privateCurve = $private->getCurve();
305            switch (true) {
306                case $public instanceof EC\PublicKey:
307                    if ($privateCurve !== $public->getCurve()) {
308                        throw new \InvalidArgumentException("The public key curve (" . $public->getCurve() . ") and private key curve ($privateCurve) need to match");
309                    }
310                    $orig = $public;
311                    $public = $public->getEncodedCoordinates();
312                    // fall-through
313                case is_string($public):
314                    $forcedEngine = EC::getForcedEngine();
315                    if ($forcedEngine === 'libsodium' && $privateCurve !== 'Curve25519') {
316                        throw new BadConfigurationException('Engine libsodium is forced but can only used with Curve25519 for ECDH');
317                    }
318                    if (!isset($forcedEngine) || $forcedEngine === 'OpenSSL') {
319                        // PHP 7.3.0 introduced the openssl_pkey_derive() function
320                        // openssl_dh_computee_key() has been around since PHP 5.3.0+ BUT it did not support ECDH
321                        // until PHP 8.1.0 / OpenSSL 3.0.0
322                        if ($forcedEngine === 'OpenSSL' && !function_exists('openssl_pkey_derive')) {
323                            throw new BadConfigurationException('Engine OpenSSL is forced but unsupported for ECDH');
324                        }
325                        if (function_exists('openssl_pkey_derive')) {
326                            $privateStr = (string) $private->withPassword();
327                            $publicStr = (string) (isset($orig) ? $orig : EC::convertPointToPublicKey($private->getCurve(), $public));
328                            $result = openssl_pkey_derive($publicStr, $privateStr);
329                            if ($result) {
330                                return $result;
331                            }
332                            if ($forcedEngine === 'OpenSSL') {
333                                // i suppose we _could_ try openssl_dh_compute_key() at this point
334                                // quoting https://www.php.net/openssl-dh-compute-key "ECDH is only supported as of PHP 8.1.0 and OpenSSL 3.0.0". ie.
335                                // PHP_VERSION_ID >= 80100 && OPENSSL_VERSION_NUMBER >= 0x3000000f
336                                // but i think that's overkill. if openssl_pkey_derive() doesn't work it seems doubtful to me that openssl_dh_compute_key() would
337                                throw new BadConfigurationException('Engine OpenSSL is forced but was unable to perform ECDH because of ' . openssl_error_string());
338                            }
339                        }
340                    }
341                    $curveName = $private->getCurve();
342                    $isMontgomeryCurve = $curveName == 'Curve25519' || $curveName == 'Curve448';
343                    if (!$isMontgomeryCurve) {
344                        $public = EC::convertPointToPublicKey($curveName, $public, false);
345                    }
346                    $point = $private->multiply($public);
347                    // according to https://www.secg.org/sec1-v2.pdf#page=33 only X is returned
348                    $secret = $isMontgomeryCurve ? $point : substr($point, 1, (strlen($point) - 1) >> 1);
349                    /*
350                    if (($secret[0] & "\x80") === "\x80") {
351                        $secret = "\0$secret";
352                    }
353                    */
354                    return $secret;
355                default:
356                    throw new \InvalidArgumentException('$public needs to be an instance of EC\PublicKey or a string (an encoded coordinate)');
357            }
358        }
359    }
360
361    /**
362     * Load the key
363     *
364     * @param string $key
365     * @param string $password optional
366     * @return AsymmetricKey
367     */
368    public static function load($key, $password = false)
369    {
370        try {
371            return EC::load($key, $password);
372        } catch (NoKeyLoadedException $e) {
373        }
374
375        return parent::load($key, $password);
376    }
377
378    /**
379     * OnLoad Handler
380     *
381     * @return bool
382     */
383    protected static function onLoad(array $components)
384    {
385        if (!isset($components['privateKey']) && !isset($components['publicKey'])) {
386            $new = new Parameters();
387        } else {
388            $new = isset($components['privateKey']) ?
389                new PrivateKey() :
390                new PublicKey();
391        }
392
393        $new->prime = $components['prime'];
394        $new->base = $components['base'];
395
396        if (isset($components['privateKey'])) {
397            $new->privateKey = $components['privateKey'];
398        }
399        if (isset($components['publicKey'])) {
400            $new->publicKey = $components['publicKey'];
401        }
402
403        return $new;
404    }
405
406    /**
407     * Determines which hashing function should be used
408     *
409     * @param string $hash
410     */
411    public function withHash($hash)
412    {
413        throw new UnsupportedOperationException('DH does not use a hash algorithm');
414    }
415
416    /**
417     * Returns the hash algorithm currently being used
418     *
419     */
420    public function getHash()
421    {
422        throw new UnsupportedOperationException('DH does not use a hash algorithm');
423    }
424
425    /**
426     * Returns the parameters
427     *
428     * A public / private key is only returned if the currently loaded "key" contains an x or y
429     * value.
430     *
431     * @see self::getPublicKey()
432     * @return mixed
433     */
434    public function getParameters()
435    {
436        $type = DH::validatePlugin('Keys', 'PKCS1', 'saveParameters');
437
438        $key = $type::saveParameters($this->prime, $this->base);
439        return DH::load($key, 'PKCS1');
440    }
441}
442