aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.rst2
-rw-r--r--docs/development/test-vectors.rst8
-rw-r--r--docs/hazmat/backends/interfaces.rst8
-rw-r--r--docs/hazmat/primitives/asymmetric/dh.rst163
-rw-r--r--docs/spelling_wordlist.txt1
-rw-r--r--src/cryptography/exceptions.py1
-rw-r--r--src/cryptography/hazmat/backends/interfaces.py18
-rw-r--r--src/cryptography/hazmat/backends/multibackend.py72
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py118
-rw-r--r--src/cryptography/hazmat/backends/openssl/dh.py182
-rw-r--r--src/cryptography/hazmat/primitives/asymmetric/dh.py23
-rw-r--r--tests/hazmat/backends/test_multibackend.py58
-rw-r--r--tests/hazmat/primitives/test_dh.py207
-rw-r--r--vectors/cryptography_vectors/asymmetric/DH/bad_exchange.txt22
-rw-r--r--vectors/cryptography_vectors/asymmetric/DH/vec.txt37
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