diff options
-rw-r--r-- | CHANGELOG.rst | 2 | ||||
-rw-r--r-- | docs/development/test-vectors.rst | 8 | ||||
-rw-r--r-- | docs/hazmat/backends/interfaces.rst | 8 | ||||
-rw-r--r-- | docs/hazmat/primitives/asymmetric/dh.rst | 163 | ||||
-rw-r--r-- | docs/spelling_wordlist.txt | 1 | ||||
-rw-r--r-- | src/cryptography/exceptions.py | 1 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/interfaces.py | 18 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/multibackend.py | 72 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 118 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/dh.py | 182 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/asymmetric/dh.py | 23 | ||||
-rw-r--r-- | tests/hazmat/backends/test_multibackend.py | 58 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_dh.py | 207 | ||||
-rw-r--r-- | vectors/cryptography_vectors/asymmetric/DH/bad_exchange.txt | 22 | ||||
-rw-r--r-- | vectors/cryptography_vectors/asymmetric/DH/vec.txt | 37 |
15 files changed, 845 insertions, 75 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1a51c42e..0d2801ba 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ Changelog * Support for OpenSSL 1.0.0 has been removed. Users on older version of OpenSSL will need to upgrade. +* Added support for Diffie-Hellman key exchange using + :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization.exchange` 1.6 - 2016-11-22 ~~~~~~~~~~~~~~~~ diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 49c5ac23..fb72240d 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -91,6 +91,13 @@ Key exchange * ``vectors/cryptography_vectors/asymmetric/DH/RFC5114.txt`` contains Diffie-Hellman examples from appendix A.1, A.2 and A.3 of :rfc:`5114`. +* ``vectors/cryptography_vectors/asymmetric/DH/vec.txt`` contains + Diffie-Hellman examples from `botan`_. + +* ``vectors/cryptography_vectors/asymmetric/DH/bad_exchange.txt`` contains + Diffie-Hellman vector pairs that were generated using OpenSSL + DH_generate_parameters_ex and DH_generate_key. + X.509 ~~~~~ @@ -463,3 +470,4 @@ header format (substituting the correct information): .. _`Russian CA`: https://e-trust.gosuslugi.ru/MainCA .. _`test/evptests.txt`: https://github.com/openssl/openssl/blob/2d0b44126763f989a4cbffbffe9d0c7518158bb7/test/evptests.txt .. _`unknown signature OID`: https://bugzilla.mozilla.org/show_bug.cgi?id=405966 +.. _`botan`: https://github.com/randombit/botan/blob/57789bdfc55061002b2727d0b32587612829a37c/src/tests/data/pubkey/dh.vec diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 42e07d39..87fc6ab7 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -600,7 +600,9 @@ A specific ``backend`` may provide one or more of these interfaces. A backend with methods for doing Diffie-Hellman key exchange. - .. method:: generate_dh_parameters(key_size) + .. method:: generate_dh_parameters(generator, key_size) + + :param int generator: The generator to use. Often 2 or 5. :param int key_size: The bit length of the prime modulus to generate. @@ -617,7 +619,9 @@ A specific ``backend`` may provide one or more of these interfaces. :return: A new instance of :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`. - .. method:: generate_dh_private_key_and_parameters(self, key_size) + .. method:: generate_dh_private_key_and_parameters(generator, key_size) + + :param int generator: The generator to use. Often 2 or 5. :param int key_size: The bit length of the prime modulus to generate. diff --git a/docs/hazmat/primitives/asymmetric/dh.rst b/docs/hazmat/primitives/asymmetric/dh.rst index 8cb68280..463df90a 100644 --- a/docs/hazmat/primitives/asymmetric/dh.rst +++ b/docs/hazmat/primitives/asymmetric/dh.rst @@ -6,69 +6,70 @@ Diffie-Hellman key exchange .. currentmodule:: cryptography.hazmat.primitives.asymmetric.dh -Numbers -~~~~~~~ - -.. class:: DHPrivateNumbers(x, public_numbers) - - .. versionadded:: 0.8 - - The collection of integers that make up a Diffie-Hellman private key. - - .. attribute:: public_numbers - - :type: :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicNumbers` - - The :class:`DHPublicNumbers` which makes up the DH public - key associated with this DH private key. - - .. attribute:: x - - :type: int - - The private value. +`Diffie-Hellman key exchange`_ (D–H) is a method that allows two parties +to jointly agree on a shared secret using an insecure channel. -.. class:: DHPublicNumbers(y, parameter_numbers) - - .. versionadded:: 0.8 +Exchange Algorithm +~~~~~~~~~~~~~~~~~~ - The collection of integers that make up a Diffie-Hellman public key. +For most applications the ``shared_key`` should be passed to a key +derivation function. - .. attribute:: parameter_numbers - - :type: :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameterNumbers` +.. code-block:: pycon - The parameters for this DH group. + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives.asymmetric import dh + >>> parameters = dh.generate_parameters(generator=2, key_size=2048, + ... backend=default_backend()) + >>> private_key = parameters.generate_private_key() + >>> peer_public_key = parameters.generate_private_key().public_key() + >>> shared_key = private_key.exchange(peer_public_key) - .. attribute:: y +DHE (or EDH), the ephemeral form of this exchange, is **strongly +preferred** over simple DH and provides `forward secrecy`_ when used. +You must generate a new private key using :func:`~DHParameters.generate_private_key` for +each :meth:`~DHPrivateKeyWithSerialization.exchange` when performing an DHE key +exchange. - :type: int +To assemble a :class:`~DHParameters` and a :class:`~DHPublicKey` from +primitive integers, you must first create the +:class:`~DHParameterNumbers` and :class:`~DHPublicNumbers` objects. For +example if **p**, **g**, and **y** are :class:`int` objects received from a +peer:: - The public value. + pn = dh.DHParameterNumbers(p, g) + parameters = pn.parameters(default_backend()) + peer_public_numbers = dh.DHPublicNumbers(y, pn) + peer_public_key = peer_public_numbers.public_key(default_backend()) -.. class:: DHParameterNumbers(p, g) +See also the :class:`~cryptography.hazmat.backends.interfaces.DHBackend` +API for additional functionality. - .. versionadded:: 0.8 +Group parameters +~~~~~~~~~~~~~~~~ - The collection of integers that define a Diffie-Hellman group. +.. function:: generate_parameters(generator, key_size, backend) - .. attribute:: p + .. versionadded:: 0.9 - :type: int + Generate a new DH parameter group for use with ``backend``. - The prime modulus value. + :param generator: The :class:`int` to use as a generator. Must be + 2 or 5. - .. attribute:: g + :param key_size: The bit length of the prime modulus to generate. - :type: int + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.DHBackend` + instance. - The generator value. + :returns: DH parameters as a new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. + :raises ValueError: If ``key_size`` is not at least 512. -Key interfaces -~~~~~~~~~~~~~~ .. class:: DHParameters @@ -99,6 +100,9 @@ Key interfaces :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameterNumbers`. +Key interfaces +~~~~~~~~~~~~~~ + .. class:: DHPrivateKey .. versionadded:: 0.9 @@ -132,6 +136,15 @@ Key interfaces :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateNumbers`. + .. method:: exchange(peer_public_key) + + .. versionadded:: 1.7 + + :param DHPublicKeyWithSerialization peer_public_key: The public key for the + peer. + + :return bytes: The agreed key. The bytes are ordered in 'big' endian. + .. class:: DHPublicKey @@ -159,3 +172,67 @@ Key interfaces Return the numbers that make up this public key. :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicNumbers`. + + +Numbers +~~~~~~~ + +.. class:: DHParameterNumbers(p, g) + + .. versionadded:: 0.8 + + The collection of integers that define a Diffie-Hellman group. + + .. attribute:: p + + :type: int + + The prime modulus value. + + .. attribute:: g + + :type: int + + The generator value. Must be 2 or 5. + +.. class:: DHPrivateNumbers(x, public_numbers) + + .. versionadded:: 0.8 + + The collection of integers that make up a Diffie-Hellman private key. + + .. attribute:: public_numbers + + :type: :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicNumbers` + + The :class:`DHPublicNumbers` which makes up the DH public + key associated with this DH private key. + + .. attribute:: x + + :type: int + + The private value. + + +.. class:: DHPublicNumbers(y, parameter_numbers) + + .. versionadded:: 0.8 + + The collection of integers that make up a Diffie-Hellman public key. + + .. attribute:: parameter_numbers + + :type: :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameterNumbers` + + The parameters for this DH group. + + .. attribute:: y + + :type: int + + The public value. + + +.. _`Diffie-Hellman key exchange`: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange +.. _`forward secrecy`: https://en.wikipedia.org/wiki/Forward_secrecy diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 5efbbdcd..186b7eeb 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -32,6 +32,7 @@ Django Docstrings El Encodings +endian Fernet fernet FIPS diff --git a/src/cryptography/exceptions.py b/src/cryptography/exceptions.py index ee43fed7..648cf9df 100644 --- a/src/cryptography/exceptions.py +++ b/src/cryptography/exceptions.py @@ -18,6 +18,7 @@ class _Reasons(Enum): UNSUPPORTED_SERIALIZATION = 7 UNSUPPORTED_X509 = 8 UNSUPPORTED_EXCHANGE_ALGORITHM = 9 + UNSUPPORTED_DIFFIE_HELLMAN = 10 class UnsupportedAlgorithm(Exception): diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index e15a7ca4..c5f2951c 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -322,9 +322,10 @@ class X509Backend(object): @six.add_metaclass(abc.ABCMeta) class DHBackend(object): @abc.abstractmethod - def generate_dh_parameters(self, key_size): + def generate_dh_parameters(self, generator, key_size): """ Generate a DHParameters instance with a modulus of key_size bits. + Using the given generator. Often 2 or 5. """ @abc.abstractmethod @@ -335,33 +336,28 @@ class DHBackend(object): """ @abc.abstractmethod - def generate_dh_private_key_and_parameters(self, key_size): + def generate_dh_private_key_and_parameters(self, generator, key_size): """ Generate a DHPrivateKey instance using key size only. + Using the given generator. Often 2 or 5. """ @abc.abstractmethod def load_dh_private_numbers(self, numbers): """ - Returns a DHPrivateKey provider. + Load a DHPrivateKey from DHPrivateNumbers """ @abc.abstractmethod def load_dh_public_numbers(self, numbers): """ - Returns a DHPublicKey provider. + Load a DHPublicKey from DHPublicNumbers. """ @abc.abstractmethod def load_dh_parameter_numbers(self, numbers): """ - Returns a DHParameters provider. - """ - - @abc.abstractmethod - def dh_exchange_algorithm_supported(self, exchange_algorithm): - """ - Returns whether the exchange algorithm is supported by this backend. + Load DHParameters from DHParameterNumbers. """ @abc.abstractmethod diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index bcd9c520..097b4908 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -7,9 +7,10 @@ from __future__ import absolute_import, division, print_function from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.backends.interfaces import ( - CMACBackend, CipherBackend, DERSerializationBackend, DSABackend, - EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, - PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend + CMACBackend, CipherBackend, DERSerializationBackend, DHBackend, + DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, + PBKDF2HMACBackend, PEMSerializationBackend, RSABackend, ScryptBackend, + X509Backend ) @@ -24,6 +25,7 @@ from cryptography.hazmat.backends.interfaces import ( @utils.register_interface(EllipticCurveBackend) @utils.register_interface(PEMSerializationBackend) @utils.register_interface(X509Backend) +@utils.register_interface(DHBackend) @utils.register_interface(ScryptBackend) class MultiBackend(object): name = "multibackend" @@ -424,6 +426,70 @@ class MultiBackend(object): _Reasons.UNSUPPORTED_X509 ) + def generate_dh_parameters(self, generator, key_size): + for b in self._filtered_backends(DHBackend): + return b.generate_dh_parameters(generator, key_size) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def load_dh_parameter_numbers(self, numbers): + for b in self._filtered_backends(DHBackend): + return b.load_dh_parameter_numbers(numbers) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def generate_dh_private_key(self, parameters): + for b in self._filtered_backends(DHBackend): + return b.generate_dh_private_key(parameters) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def load_dh_private_numbers(self, numbers): + for b in self._filtered_backends(DHBackend): + return b.load_dh_private_numbers(numbers) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def load_dh_public_numbers(self, numbers): + for b in self._filtered_backends(DHBackend): + return b.load_dh_public_numbers(numbers) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def generate_dh_private_key_and_parameters(self, generator, key_size): + for b in self._filtered_backends(DHBackend): + return b.generate_dh_private_key_and_parameters(generator, + key_size) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def dh_parameters_supported(self, p, g): + for b in self._filtered_backends(DHBackend): + return b.dh_parameters_supported(p, g) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + def x509_name_bytes(self, name): for b in self._filtered_backends(X509Backend): return b.x509_name_bytes(name) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 4a341fc2..9df113b6 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -16,7 +16,7 @@ import six from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.backends.interfaces import ( - CMACBackend, CipherBackend, DERSerializationBackend, DSABackend, + CMACBackend, CipherBackend, DERSerializationBackend, DHBackend, DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend ) @@ -24,6 +24,9 @@ from cryptography.hazmat.backends.openssl.ciphers import ( _AESCTRCipherContext, _CipherContext ) from cryptography.hazmat.backends.openssl.cmac import _CMACContext +from cryptography.hazmat.backends.openssl.dh import ( + _DHParameters, _DHPrivateKey, _DHPublicKey +) from cryptography.hazmat.backends.openssl.dsa import ( _DSAParameters, _DSAPrivateKey, _DSAPublicKey ) @@ -107,6 +110,7 @@ def _pem_password_cb(buf, size, writing, userdata_handle): @utils.register_interface(CipherBackend) @utils.register_interface(CMACBackend) @utils.register_interface(DERSerializationBackend) +@utils.register_interface(DHBackend) @utils.register_interface(DSABackend) @utils.register_interface(EllipticCurveBackend) @utils.register_interface(HashBackend) @@ -329,6 +333,7 @@ class Backend(object): def _bn_to_int(self, bn): assert bn != self._ffi.NULL + if six.PY3: # Python 3 has constant time from_bytes, so use that. bn_num_bytes = self._lib.BN_num_bytes(bn) @@ -1734,6 +1739,117 @@ class Backend(object): serialization._ssh_write_string(public_numbers.encode_point()) ) + def generate_dh_parameters(self, generator, key_size): + if key_size < 512: + raise ValueError("DH key_size must be at least 512 bits") + + if generator not in (2, 5): + raise ValueError("DH generator must be 2 or 5") + + dh_param_cdata = self._lib.DH_new() + self.openssl_assert(dh_param_cdata != self._ffi.NULL) + dh_param_cdata = self._ffi.gc(dh_param_cdata, self._lib.DH_free) + + res = self._lib.DH_generate_parameters_ex( + dh_param_cdata, + key_size, + generator, + self._ffi.NULL + ) + self.openssl_assert(res == 1) + + return _DHParameters(self, dh_param_cdata) + + def generate_dh_private_key(self, parameters): + dh_key_cdata = self._lib.DHparams_dup(parameters._dh_cdata) + self.openssl_assert(dh_key_cdata != self._ffi.NULL) + dh_key_cdata = self._ffi.gc(dh_key_cdata, self._lib.DH_free) + + res = self._lib.DH_generate_key(dh_key_cdata) + self.openssl_assert(res == 1) + + return _DHPrivateKey(self, dh_key_cdata) + + def generate_dh_private_key_and_parameters(self, generator, key_size): + return self.generate_dh_private_key( + self.generate_dh_parameters(generator, key_size)) + + def load_dh_private_numbers(self, numbers): + parameter_numbers = numbers.public_numbers.parameter_numbers + + dh_cdata = self._lib.DH_new() + self.openssl_assert(dh_cdata != self._ffi.NULL) + dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + + p = self._int_to_bn(parameter_numbers.p) + g = self._int_to_bn(parameter_numbers.g) + pub_key = self._int_to_bn(numbers.public_numbers.y) + priv_key = self._int_to_bn(numbers.x) + + res = self._lib.DH_set0_pqg(dh_cdata, p, self._ffi.NULL, g) + self.openssl_assert(res == 1) + + res = self._lib.DH_set0_key(dh_cdata, pub_key, priv_key) + self.openssl_assert(res == 1) + + codes = self._ffi.new("int[]", 1) + res = self._lib.DH_check(dh_cdata, codes) + self.openssl_assert(res == 1) + + if codes[0] != 0: + raise ValueError("DH private numbers did not pass safety checks.") + + return _DHPrivateKey(self, dh_cdata) + + def load_dh_public_numbers(self, numbers): + dh_cdata = self._lib.DH_new() + self.openssl_assert(dh_cdata != self._ffi.NULL) + dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + + parameter_numbers = numbers.parameter_numbers + + p = self._int_to_bn(parameter_numbers.p) + g = self._int_to_bn(parameter_numbers.g) + pub_key = self._int_to_bn(numbers.y) + + res = self._lib.DH_set0_pqg(dh_cdata, p, self._ffi.NULL, g) + self.openssl_assert(res == 1) + + res = self._lib.DH_set0_key(dh_cdata, pub_key, self._ffi.NULL) + self.openssl_assert(res == 1) + + return _DHPublicKey(self, dh_cdata) + + def load_dh_parameter_numbers(self, numbers): + dh_cdata = self._lib.DH_new() + self.openssl_assert(dh_cdata != self._ffi.NULL) + dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + + p = self._int_to_bn(numbers.p) + g = self._int_to_bn(numbers.g) + + res = self._lib.DH_set0_pqg(dh_cdata, p, self._ffi.NULL, g) + self.openssl_assert(res == 1) + + return _DHParameters(self, dh_cdata) + + def dh_parameters_supported(self, p, g): + dh_cdata = self._lib.DH_new() + self.openssl_assert(dh_cdata != self._ffi.NULL) + dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + + p = self._int_to_bn(p) + g = self._int_to_bn(g) + + res = self._lib.DH_set0_pqg(dh_cdata, p, self._ffi.NULL, g) + self.openssl_assert(res == 1) + + codes = self._ffi.new("int[]", 1) + res = self._lib.DH_check(dh_cdata, codes) + self.openssl_assert(res == 1) + + return codes[0] == 0 + def x509_name_bytes(self, name): x509_name = _encode_name_gc(self, name) pp = self._ffi.new("unsigned char **") diff --git a/src/cryptography/hazmat/backends/openssl/dh.py b/src/cryptography/hazmat/backends/openssl/dh.py new file mode 100644 index 00000000..666429f2 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/dh.py @@ -0,0 +1,182 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.hazmat.primitives.asymmetric import dh + + +def _dh_cdata_to_parameters(dh_cdata, backend): + lib = backend._lib + ffi = backend._ffi + + param_cdata = lib.DHparams_dup(dh_cdata) + backend.openssl_assert(param_cdata != ffi.NULL) + param_cdata = ffi.gc(param_cdata, lib.DH_free) + + return _DHParameters(backend, param_cdata) + + +@utils.register_interface(dh.DHParametersWithSerialization) +class _DHParameters(object): + def __init__(self, backend, dh_cdata): + self._backend = backend + self._dh_cdata = dh_cdata + + def parameter_numbers(self): + p = self._backend._ffi.new("BIGNUM **") + g = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_pqg(self._dh_cdata, + p, self._backend._ffi.NULL, g) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) + return dh.DHParameterNumbers( + p=self._backend._bn_to_int(p[0]), + g=self._backend._bn_to_int(g[0]) + ) + + def generate_private_key(self): + return self._backend.generate_dh_private_key(self) + + +def _handle_dh_compute_key_error(errors, backend): + lib = backend._lib + + backend.openssl_assert(errors[0][1:] == ( + lib.ERR_LIB_DH, + lib.DH_F_COMPUTE_KEY, + lib.DH_R_INVALID_PUBKEY + )) + + raise ValueError("Public key value is invalid for this exchange.") + + +def _get_dh_num_bits(backend, dh_cdata): + p = backend._ffi.new("BIGNUM **") + backend._lib.DH_get0_pqg(dh_cdata, p, + backend._ffi.NULL, + backend._ffi.NULL) + backend.openssl_assert(p[0] != backend._ffi.NULL) + return backend._lib.BN_num_bits(p[0]) + + +@utils.register_interface(dh.DHPrivateKeyWithSerialization) +class _DHPrivateKey(object): + def __init__(self, backend, dh_cdata): + self._backend = backend + self._dh_cdata = dh_cdata + self._key_size_bytes = self._backend._lib.DH_size(dh_cdata) + + @property + def key_size(self): + return _get_dh_num_bits(self._backend, self._dh_cdata) + + def private_numbers(self): + p = self._backend._ffi.new("BIGNUM **") + g = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_pqg(self._dh_cdata, + p, self._backend._ffi.NULL, g) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) + pub_key = self._backend._ffi.new("BIGNUM **") + priv_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_key(self._dh_cdata, pub_key, priv_key) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(priv_key[0] != self._backend._ffi.NULL) + return dh.DHPrivateNumbers( + public_numbers=dh.DHPublicNumbers( + parameter_numbers=dh.DHParameterNumbers( + p=self._backend._bn_to_int(p[0]), + g=self._backend._bn_to_int(g[0]) + ), + y=self._backend._bn_to_int(pub_key[0]) + ), + x=self._backend._bn_to_int(priv_key[0]) + ) + + def exchange(self, peer_public_key): + + buf = self._backend._ffi.new("unsigned char[]", self._key_size_bytes) + pub_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_key(peer_public_key._dh_cdata, pub_key, + self._backend._ffi.NULL) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + res = self._backend._lib.DH_compute_key( + buf, + pub_key[0], + self._dh_cdata + ) + + if res == -1: + errors = self._backend._consume_errors() + return _handle_dh_compute_key_error(errors, self._backend) + else: + self._backend.openssl_assert(res >= 1) + + key = self._backend._ffi.buffer(buf)[:res] + pad = self._key_size_bytes - len(key) + + if pad > 0: + key = (b"\x00" * pad) + key + + return key + + def public_key(self): + dh_cdata = self._backend._lib.DHparams_dup(self._dh_cdata) + self._backend.openssl_assert(dh_cdata != self._backend._ffi.NULL) + dh_cdata = self._backend._ffi.gc( + dh_cdata, self._backend._lib.DH_free + ) + + pub_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_key(self._dh_cdata, + pub_key, self._backend._ffi.NULL) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + pub_key_dup = self._backend._lib.BN_dup(pub_key[0]) + self._backend.openssl_assert(pub_key_dup != self._backend._ffi.NULL) + + res = self._backend._lib.DH_set0_key(dh_cdata, + pub_key_dup, + self._backend._ffi.NULL) + self._backend.openssl_assert(res == 1) + + return _DHPublicKey(self._backend, dh_cdata) + + def parameters(self): + return _dh_cdata_to_parameters(self._dh_cdata, self._backend) + + +@utils.register_interface(dh.DHPublicKeyWithSerialization) +class _DHPublicKey(object): + def __init__(self, backend, dh_cdata): + self._backend = backend + self._dh_cdata = dh_cdata + self._key_size_bits = _get_dh_num_bits(self._backend, self._dh_cdata) + + @property + def key_size(self): + return self._key_size_bits + + def public_numbers(self): + p = self._backend._ffi.new("BIGNUM **") + g = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_pqg(self._dh_cdata, + p, self._backend._ffi.NULL, g) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) + pub_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_key(self._dh_cdata, + pub_key, self._backend._ffi.NULL) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + return dh.DHPublicNumbers( + parameter_numbers=dh.DHParameterNumbers( + p=self._backend._bn_to_int(p[0]), + g=self._backend._bn_to_int(g[0]) + ), + y=self._backend._bn_to_int(pub_key[0]) + ) + + def parameters(self): + return _dh_cdata_to_parameters(self._dh_cdata, self._backend) diff --git a/src/cryptography/hazmat/primitives/asymmetric/dh.py b/src/cryptography/hazmat/primitives/asymmetric/dh.py index 12d53eed..ec044ddd 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dh.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dh.py @@ -11,6 +11,10 @@ import six from cryptography import utils +def generate_parameters(generator, key_size, backend): + return backend.generate_dh_parameters(generator, key_size) + + class DHPrivateNumbers(object): def __init__(self, x, public_numbers): if not isinstance(x, six.integer_types): @@ -35,6 +39,9 @@ class DHPrivateNumbers(object): def __ne__(self, other): return not self == other + def private_key(self, backend): + return backend.load_dh_private_numbers(self) + public_numbers = utils.read_only_property("_public_numbers") x = utils.read_only_property("_x") @@ -63,6 +70,9 @@ class DHPublicNumbers(object): def __ne__(self, other): return not self == other + def public_key(self, backend): + return backend.load_dh_public_numbers(self) + y = utils.read_only_property("_y") parameter_numbers = utils.read_only_property("_parameter_numbers") @@ -75,6 +85,9 @@ class DHParameterNumbers(object): ): raise TypeError("p and g must be integers") + if g not in (2, 5): + raise ValueError("DH generator must be 2 or 5") + self._p = p self._g = g @@ -90,6 +103,9 @@ class DHParameterNumbers(object): def __ne__(self, other): return not self == other + def parameters(self, backend): + return backend.load_dh_parameter_numbers(self) + p = utils.read_only_property("_p") g = utils.read_only_property("_g") @@ -141,6 +157,13 @@ class DHPrivateKeyWithSerialization(DHPrivateKey): Returns a DHPrivateNumbers. """ + @abc.abstractmethod + def exchange(self, peer_public_key): + """ + Given peer's DHPublicKey, carry out the key exchange and + return shared key as bytes. + """ + @six.add_metaclass(abc.ABCMeta) class DHPublicKey(object): diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py index ea08e17b..bd806731 100644 --- a/tests/hazmat/backends/test_multibackend.py +++ b/tests/hazmat/backends/test_multibackend.py @@ -11,9 +11,10 @@ from cryptography.exceptions import ( UnsupportedAlgorithm, _Reasons ) from cryptography.hazmat.backends.interfaces import ( - CMACBackend, CipherBackend, DERSerializationBackend, DSABackend, - EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, - PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend + CMACBackend, CipherBackend, DERSerializationBackend, DHBackend, + DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, + PBKDF2HMACBackend, PEMSerializationBackend, RSABackend, ScryptBackend, + X509Backend ) from cryptography.hazmat.backends.multibackend import MultiBackend from cryptography.hazmat.primitives import cmac, hashes, hmac @@ -244,6 +245,30 @@ class DummyX509Backend(object): pass +@utils.register_interface(DHBackend) +class DummyDHBackend(object): + def generate_dh_parameters(self, generator, key_size): + pass + + def load_dh_parameter_numbers(self, numbers): + pass + + def generate_dh_private_key(self, parameters): + pass + + def load_dh_private_numbers(self, numbers): + pass + + def load_dh_public_numbers(self, numbers): + pass + + def generate_dh_private_key_and_parameters(self, generator, key_size): + pass + + def dh_parameters_supported(self, p, g): + pass + + @utils.register_interface(ScryptBackend) class DummyScryptBackend(object): def derive_scrypt(self, key_material, salt, length, n, r, p): @@ -587,6 +612,33 @@ class TestMultiBackend(object): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): backend.x509_name_bytes(object()) + def test_dh_backend(self): + backend = MultiBackend([DummyDHBackend()]) + + backend.generate_dh_parameters(2, 512) + backend.load_dh_parameter_numbers(object()) + backend.generate_dh_private_key(object()) + backend.load_dh_private_numbers(object()) + backend.load_dh_public_numbers(object()) + backend.generate_dh_private_key_and_parameters(2, 512) + backend.dh_parameters_supported(2, 3) + + backend = MultiBackend([DummyBackend()]) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.generate_dh_parameters(2, 512) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.load_dh_parameter_numbers(object()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.generate_dh_private_key(object()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.load_dh_private_numbers(object()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.load_dh_public_numbers(object()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.generate_dh_private_key_and_parameters(2, 512) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.dh_parameters_supported(2, 3) + def test_scrypt(self): backend = MultiBackend([DummyScryptBackend()]) backend.derive_scrypt(b"key", b"salt", 1, 1, 1, 1) diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index d8869de9..ba724bf0 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -4,22 +4,27 @@ from __future__ import absolute_import, division, print_function +import os + import pytest +from cryptography.hazmat.backends.interfaces import DHBackend from cryptography.hazmat.primitives.asymmetric import dh +from cryptography.utils import bit_length, int_from_bytes +from ...utils import load_nist_vectors, load_vectors_from_file def test_dh_parameternumbers(): params = dh.DHParameterNumbers( - 65537, 3 + 65537, 2 ) assert params.p == 65537 - assert params.g == 3 + assert params.g == 2 with pytest.raises(TypeError): dh.DHParameterNumbers( - None, 3 + None, 2 ) with pytest.raises(TypeError): @@ -32,10 +37,15 @@ def test_dh_parameternumbers(): None, None ) + with pytest.raises(ValueError): + dh.DHParameterNumbers( + 65537, 7 + ) + def test_dh_numbers(): params = dh.DHParameterNumbers( - 65537, 3 + 65537, 2 ) public = dh.DHPublicNumbers( @@ -74,14 +84,14 @@ def test_dh_numbers(): def test_dh_parameter_numbers_equality(): - assert dh.DHParameterNumbers(65537, 3) == dh.DHParameterNumbers(65537, 3) - assert dh.DHParameterNumbers(6, 3) != dh.DHParameterNumbers(65537, 3) - assert dh.DHParameterNumbers(65537, 0) != dh.DHParameterNumbers(65537, 3) - assert dh.DHParameterNumbers(65537, 0) != object() + assert dh.DHParameterNumbers(65537, 2) == dh.DHParameterNumbers(65537, 2) + assert dh.DHParameterNumbers(6, 2) != dh.DHParameterNumbers(65537, 2) + assert dh.DHParameterNumbers(65537, 5) != dh.DHParameterNumbers(65537, 2) + assert dh.DHParameterNumbers(65537, 2) != object() def test_dh_private_numbers_equality(): - params = dh.DHParameterNumbers(65537, 3) + params = dh.DHParameterNumbers(65537, 2) public = dh.DHPublicNumbers(1, params) private = dh.DHPrivateNumbers(2, public) @@ -89,16 +99,189 @@ def test_dh_private_numbers_equality(): assert private != dh.DHPrivateNumbers(0, public) assert private != dh.DHPrivateNumbers(2, dh.DHPublicNumbers(0, params)) assert private != dh.DHPrivateNumbers( - 2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 0)) + 2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 5)) ) assert private != object() def test_dh_public_numbers_equality(): - params = dh.DHParameterNumbers(65537, 3) + params = dh.DHParameterNumbers(65537, 2) public = dh.DHPublicNumbers(1, params) assert public == dh.DHPublicNumbers(1, params) assert public != dh.DHPublicNumbers(0, params) - assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 0)) + assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 5)) assert public != object() + + +@pytest.mark.requires_backend_interface(interface=DHBackend) +class TestDH(object): + def test_small_key_generate_dh(self, backend): + with pytest.raises(ValueError): + dh.generate_parameters(2, 511, backend) + + def test_unsupported_generator_generate_dh(self, backend): + with pytest.raises(ValueError): + dh.generate_parameters(7, 512, backend) + + def test_dh_parameters_supported(self, backend): + assert backend.dh_parameters_supported(23, 5) + assert not backend.dh_parameters_supported(23, 18) + + def test_convert_to_numbers(self, backend): + parameters = backend.generate_dh_private_key_and_parameters(2, 512) + + private = parameters.private_numbers() + + p = private.public_numbers.parameter_numbers.p + g = private.public_numbers.parameter_numbers.g + + params = dh.DHParameterNumbers(p, g) + public = dh.DHPublicNumbers(1, params) + private = dh.DHPrivateNumbers(2, public) + + deserialized_params = params.parameters(backend) + deserialized_public = public.public_key(backend) + deserialized_private = private.private_key(backend) + + assert isinstance(deserialized_params, + dh.DHParametersWithSerialization) + assert isinstance(deserialized_public, + dh.DHPublicKeyWithSerialization) + assert isinstance(deserialized_private, + dh.DHPrivateKeyWithSerialization) + + def test_numbers_unsupported_parameters(self, backend): + params = dh.DHParameterNumbers(23, 2) + public = dh.DHPublicNumbers(1, params) + private = dh.DHPrivateNumbers(2, public) + + with pytest.raises(ValueError): + private.private_key(backend) + + def test_generate_dh(self, backend): + generator = 2 + key_size = 512 + + parameters = dh.generate_parameters(generator, key_size, backend) + assert isinstance(parameters, dh.DHParameters) + + key = parameters.generate_private_key() + assert isinstance(key, dh.DHPrivateKey) + assert key.key_size == key_size + + public = key.public_key() + assert isinstance(public, dh.DHPublicKey) + assert public.key_size == key_size + + assert isinstance(parameters, dh.DHParametersWithSerialization) + parameter_numbers = parameters.parameter_numbers() + assert isinstance(parameter_numbers, dh.DHParameterNumbers) + assert bit_length(parameter_numbers.p) == key_size + + assert isinstance(public, dh.DHPublicKeyWithSerialization) + assert isinstance(public.public_numbers(), dh.DHPublicNumbers) + assert isinstance(public.parameters(), dh.DHParameters) + + assert isinstance(key, dh.DHPrivateKeyWithSerialization) + assert isinstance(key.private_numbers(), dh.DHPrivateNumbers) + assert isinstance(key.parameters(), dh.DHParameters) + + def test_exchange(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + assert isinstance(parameters, dh.DHParameters) + + key1 = parameters.generate_private_key() + key2 = parameters.generate_private_key() + + symkey1 = key1.exchange(key2.public_key()) + assert symkey1 + assert len(symkey1) == 512 // 8 + + symkey2 = key2.exchange(key1.public_key()) + assert symkey1 == symkey2 + + def test_exchange_algorithm(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + + key1 = parameters.generate_private_key() + key2 = parameters.generate_private_key() + + shared_key_bytes = key2.exchange(key1.public_key()) + symkey = int_from_bytes(shared_key_bytes, 'big') + + symkey_manual = pow(key1.public_key().public_numbers().y, + key2.private_numbers().x, + parameters.parameter_numbers().p) + + assert symkey == symkey_manual + + def test_symmetric_key_padding(self, backend): + """ + This test has specific parameters that produce a symmetric key + In length 63 bytes instead 64. We make sure here that we add + padding to the key. + """ + p = int("11859949538425015739337467917303613431031019140213666" + "129025407300654026585086345323066284800963463204246390" + "256567934582260424238844463330887962689642467123") + g = 2 + y = int("32155788395534640648739966373159697798396966919821525" + "72238852825117261342483718574508213761865276905503199" + "969908098203345481366464874759377454476688391248") + x = int("409364065449673443397833358558926598469347813468816037" + "268451847116982490733450463194921405069999008617231539" + "7147035896687401350877308899732826446337707128") + parameters = dh.DHParameterNumbers(p, g) + public = dh.DHPublicNumbers(y, parameters) + private = dh.DHPrivateNumbers(x, public) + key = private.private_key(backend) + symkey = key.exchange(public.public_key(backend)) + assert len(symkey) == 512 // 8 + assert symkey[:1] == b'\x00' + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "bad_exchange.txt"), + load_nist_vectors)) + def test_bad_exchange(self, backend, vector): + parameters1 = dh.DHParameterNumbers(int(vector["p1"]), + int(vector["g"])) + public1 = dh.DHPublicNumbers(int(vector["y1"]), parameters1) + private1 = dh.DHPrivateNumbers(int(vector["x1"]), public1) + key1 = private1.private_key(backend) + pub_key1 = key1.public_key() + + parameters2 = dh.DHParameterNumbers(int(vector["p2"]), + int(vector["g"])) + public2 = dh.DHPublicNumbers(int(vector["y2"]), parameters2) + private2 = dh.DHPrivateNumbers(int(vector["x2"]), public2) + key2 = private2.private_key(backend) + pub_key2 = key2.public_key() + + if pub_key2.public_numbers().y >= parameters1.p: + with pytest.raises(ValueError): + key1.exchange(pub_key2) + else: + symkey1 = key1.exchange(pub_key2) + assert symkey1 + + symkey2 = key2.exchange(pub_key1) + + assert symkey1 != symkey2 + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "vec.txt"), + load_nist_vectors)) + def test_dh_vectors(self, backend, vector): + parameters = dh.DHParameterNumbers(int(vector["p"]), + int(vector["g"])) + public = dh.DHPublicNumbers(int(vector["y"]), parameters) + private = dh.DHPrivateNumbers(int(vector["x"]), public) + key = private.private_key(backend) + symkey = key.exchange(public.public_key(backend)) + + assert int_from_bytes(symkey, 'big') == int(vector["k"], 16) diff --git a/vectors/cryptography_vectors/asymmetric/DH/bad_exchange.txt b/vectors/cryptography_vectors/asymmetric/DH/bad_exchange.txt new file mode 100644 index 00000000..c511ce3d --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/DH/bad_exchange.txt @@ -0,0 +1,22 @@ +# These are pairs of DH vectors that an exchange between them will result in an error. + +# This pair of vector will result in ValueError as the exchange. That is because Y2 > P1. +COUNT = 0 +G = 2 +P1 = 6982449264326893170076800712455263291469349829090516222210222668518102590295128975951345863513171140301173103075603894877550567268976511279764959149528443 +X1 = 4801532380147672323646734353223620022391392516566390315327913029035641903575640872665137366552076226566411004476255204566867879551828094289388660251782046 +Y1 = 821604160094281686420547900781786073619010872682627891698497892534032059614878030140061466421083272893526509531465029773964398063523169243002396742005311 +P2 = 11080956217961768232353507616608271011094948914260968400815080208586343913901873894476784420098894723332990692686674731674801476861138954727825965908678667 +X2 = 5711420683916244371478141649351505929365347129399136675372566081882611630063783016816795794055507751345814825076698378102524000583805069503046990368030659 +Y2 = 10250982112542385918017381009520473894895926030138509814084625750094204966649199270766083680909922864972425983868488727218698464441106269212018923589462134 + +# This pair of vector exchange will result in two different symmetric keys. +# That is because they are not generated from the same parameters. +COUNT = 1 +G = 2 +P1 = 12900013872746249964533588299424217034480441280987975922107615584280973803458959936181179967371408335732783334903224087948303561132833270378314994720245923 +X1 = 6071003937964305313717247598692016437334013449238587402845410045585785082471492220556199473955398532734458121110746407155944589178844230676809305984951446 +Y1 = 7991113599078333875847794507844575617505022450446398197075320323186801365513151014983321390753605753306200081479128423955529753566986512451925943417370573 +P2 = 8230910504630293046781610423436023785570058813983426764127680163386079097481556242295408030406562540441574901920011043362099679138268556410914150106172603 +X2 = 5094233318592725701435923671489794134941722294242428364605342252104678038235605245715640898911801472906659807096135548788171923289152060442445002898139883 +Y2 = 3153667608586790035405826466812972271982421015353174890600325108812327867424805742934205854052751840807361048259220540363086632109209018127565889223022306 diff --git a/vectors/cryptography_vectors/asymmetric/DH/vec.txt b/vectors/cryptography_vectors/asymmetric/DH/vec.txt new file mode 100644 index 00000000..4ee8e903 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/DH/vec.txt @@ -0,0 +1,37 @@ +# This vectors are taken from https://raw.githubusercontent.com/randombit/botan/master/src/tests/data/pubkey/dh.vec. +# converted to fit NIST format. + +COUNT = 0 +P = 58458002095536094658683755258523362961421200751439456159756164191494576279467 +G = 2 +X = 46205663093589612668746163860870963912226379131190812163519349848291472898748 +Y = 26821400572298074358375073922715498403273583367617402781946773132088456286733 +K = 5D9A64F9E54B011381308CF462C207CB0DB7630EAB026E06E5B893041207DBD8 + +COUNT = 1 +P = 7080941971697125115953429172307253449997092375902849066092516886443770423993013931939664664691599157495586618571486777257251370592538466283944520569755243 +G = 2 +X = 558545918073450953822828294657166871085534488182588754070944297274702844791239790350137385125663944726718987047123606999344398260535747142299581818644894 +Y = 5414004572904348025296832268296927639985617596751048805496391472266617693297043686410755975231136915922983393130810278045355076974349430254905203521469281 +K = 65F79BCC47862E02DDE775B8FC7B1D0CBA094B753E502D49A4468687681F178CEE98016210E136E80255FC8FDECC4D38D91EBC82C94B17652BDC7569AC383F39 + +COUNT = 2 +P = 13136945886549419892672364204240698856868140920222753719410945199521492957942501101571382423757107983263697942121022398653040312294171805755003420937008819 +G = 2 +X = 7234683127321298509925294581253140833264699266098978220501763131351358687985896274862103003239800546011982186510097828911316488529426006616714526458066271 +Y = 2279687933022692843565305738851942847753122181957871349712298819158246913703861338250576252594546190393887657989733709994119777023140405518896999362296894 +K = 290BF0265BAAD40A5853BAF3CC18B315982FF282C82D7332DA6A8826E789330D875F29F57D9A622DB7DDF156CF4440EDB8455BC32859FA2A178D42528FA64136 + +COUNT = 3 +P = 7837101158566379575244981793821373232935960937791570311529087011989828139360157566031864344629391921990623396775060696298931110532512473525227009216442663 +G = 5 +X = 2271743810121848480269660476736588602223609425319496369070326649477151605521404690612976189919437998885287608828699462183553427369647044489290359586354073 +Y = 5553605535786829473948867606958960228544449485744361474243970893441441245322015477973193059017330938119851955120496598438033407790780406867070511553809237 +K = 4ED3B2ED6B3E8446D8E1C3D2BCA00FB939464874BB6750BDE5F5DACFE9F819597E96F6B1DBA27C785553383AE87188019734A160B6BA5396760B88EC1F2A0E92 + +COUNT = 4 +P = 94601366105683233785857165617633883930888659900865798821537131281240413334589084848277586190796291421413056657355624982032535320546697702506255545831695999765620345337665948695835489391652130862575428732385880123143529399201847515289798598538556585982541405034303884158851145992650670977252891826481077576283 +G = 2 +X = 23484534159498465809072519330053257189446469650913804186985945596423260246286292600333967669376208639922768407894768298151128017428601886623219847603288857771541399565808620552396892534905511436715248032341612931260211253869255846963169818664325386727815821240629227196043893200670349600641190073530521475769 +Y = 5432307605192951130143195594341230106472453193740817014833299044466758796406314885816769577417181043800208414512454882219387455875605089197474590288847925182956692434090736024405561167965732783350054200615740891235826401928590081691818434389717991597889828540215480852496077254876423767132564403274562077989 +K = 2B6D9504C1D7ACAD9652CF79A6A0630EA9D19C197E908E992BC318BE2867FAE3C1AD5BE83E6E4AA1CBE0347774038F20ACD790DEA82B2A5862BF21FA4A1AB464AE985CA8AC4E5076AD0843E144C1305759BA047446A3A7F2426BE0C724F269009B54447B6A970876E5E4C613356805014D6060039C081AFB046863CA9BE9C848 |