Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add framework for key exchange schemes and Diffie-Hellman #38374

Merged
merged 32 commits into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
62787b2
Initial work on public key exchange in Sage
vincentmacri Jul 15, 2024
0b2d8ab
Fix formatting error in references
vincentmacri Jul 15, 2024
2655712
Style and doc date
vincentmacri Jul 15, 2024
73bffc6
Fix annotations and add missing check
vincentmacri Jul 16, 2024
c183db2
References
vincentmacri Jul 16, 2024
cc513e7
Missing tests
vincentmacri Jul 16, 2024
5ee6d0a
Formatting
vincentmacri Jul 16, 2024
4cb006f
Cleanup and tests
vincentmacri Jul 16, 2024
16d3d98
Rename file
vincentmacri Jul 16, 2024
f0a8ea0
LaTeX doctest
vincentmacri Jul 16, 2024
bacebe9
Style fix
vincentmacri Jul 16, 2024
b12addd
Fix doc formatting
vincentmacri Jul 16, 2024
6f7b5e2
Apply suggestions from code review
vincentmacri Jul 17, 2024
f7b0744
Formatting for when referring to the DiffieHellman class rather than …
vincentmacri Jul 17, 2024
6b86174
Add missing test
vincentmacri Jul 17, 2024
a97866a
Formatting and type annotation fixes
vincentmacri Jul 17, 2024
babeb3c
String formatting
vincentmacri Jul 17, 2024
dd098a1
Cleanup pass
vincentmacri Jul 17, 2024
42159fc
Line length fixes
vincentmacri Jul 17, 2024
6ab21e9
Merge branch 'develop' into pke
vincentmacri Jul 17, 2024
6298553
Use more standard wording
vincentmacri Jul 18, 2024
7801f9d
Clearer wording
vincentmacri Jul 18, 2024
7790037
Merge branch 'develop' into pke
vincentmacri Jul 22, 2024
f5491a2
Merge branch 'develop' into pke
vincentmacri Jul 26, 2024
f74d913
Apply suggestions from code review
vincentmacri Jul 30, 2024
474e09d
Add reference to RFC
vincentmacri Jul 30, 2024
441fdfc
Refactoring/cleanup
vincentmacri Jul 30, 2024
d58532b
Import tweaks
vincentmacri Jul 30, 2024
cdb0962
Fix tests
vincentmacri Jul 30, 2024
e11ad4e
Doc formatting and date update
vincentmacri Jul 30, 2024
5726da8
Formatting consistency
vincentmacri Jul 30, 2024
fc1a4dc
Use catalog file
vincentmacri Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/doc/en/reference/cryptography/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ Cryptography
sage/crypto/lattice
sage/crypto/lwe

sage/crypto/key_exchange/catalog
sage/crypto/key_exchange/key_exchange_scheme
sage/crypto/key_exchange/diffie_hellman

.. include:: ../footer.txt
9 changes: 9 additions & 0 deletions src/doc/en/reference/references/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3846,6 +3846,10 @@ REFERENCES:
\M. Grötschel, \L Lovász, *Handbook of combinatorics*,
Vol. 1, Chapter 18, 1995

.. [KK2003] T. Kivinen and M. Kojo. *More Modular Exponential (MODP)
Diffie-Hellman groups for Internet Key Exchange (IKE)*, in RFC 3526.
Available at https://www.rfc-editor.org/rfc/rfc3526

.. [KKMMNN1992] S-J. Kang, M. Kashiwara, K. C. Misra, T. Miwa, T. Nakashima,
and A. Nakayashiki. *Affine crystals and vertex models*.
Int. J. Mod. Phys. A, **7** (suppl. 1A), (1992) pp. 449-484.
Expand Down Expand Up @@ -5352,6 +5356,11 @@ REFERENCES:
Mathematical Physics, Analysis and Geometry 1, 171-191 (1998).
:doi:`10.1023/A:1009724323513`

.. [PP2010] \C. Paar and J. Pelzl.
*Understanding Cryptography: A Textbook for Students and Practitioners*.
Springer Berlin, Heidelberg, 2010.
:doi:`10.1007/978-3-642-04101-3`

.. [PPW2013] \D. Perkinson, J. Perlman, and J. Wilmes.
*Primer for the algebraic geometry of sandpiles*.
Tropical and Non-Archimedean
Expand Down
4 changes: 4 additions & 0 deletions src/sage/crypto/all.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@
'lfsr_autocorrelation',
'lfsr_connection_polynomial',
])


import sage.crypto.key_exchange.catalog as key_exchange

del lazy_import
6 changes: 6 additions & 0 deletions src/sage/crypto/key_exchange/all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from sage.misc.lazy_import import lazy_import

lazy_import('sage.crypto.key_exchange.diffie_hellman', 'DiffieHellman')
lazy_import('sage.crypto.key_exchange.key_exchange_scheme', 'KeyExchangeScheme')

del lazy_import
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't think of this before, but seems like a good idea!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just copied from the crypto/public_key folder for this.

I'm now thinking I may have done this wrong (and maybe public_key did too but I think that's out of scope for this PR, public_key needs some refactoring I think). I'm going to do some more testing of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was doing it wrong. After the commit I just pushed, DiffieHellman can now be accessed without the user needing to explicitly import it.

This does raise the question of how we want the schemes to be accessed by the user. Most people probably won't use this feature (although I guess that applies to most things in Sage) so maybe it makes sense to access it as something like crypto.DiffieHellman, to avoid polluting the auto-suggest feature in some interfaces, but several of the existing symmetric ciphers are just accessed directly so I've done the same for consistency.

Happy to change it to something like crypto.DiffieHellman or key_exchange.DiffieHellman if that's preferred though.

Copy link
Contributor Author

@vincentmacri vincentmacri Jul 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now I have it so DiffieHellman is accessible without import, but KeyExchangeScheme requires an import. I don't think an abstract base class needs to be exposed to users without them explicitly importing it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my PR of a similar nature, I created a catalog file, see here. (Suggested by Travis, another Sage contributor)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having key_exchange in global scope (I think?) is a bit questionable. Do you think it's a good idea?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's less questionable than SubstitutionCryptosystem being global scope (which it currently is).

I don't think it's too different than how distributions is global scope in the PR you linked. The alternative (which maybe is better) would be to access the key exchange schemes under crypto.key_exchange, so you'd do crypto.key_exchange.DiffieHellman. We could then in the future move the other cryptography code to crypto.[something] as well and take things like SubstitutionCryptosystem out of global scope.

After writing that I've convinced myself that it should be changed to crypto.key_exchange.

And the reason I didn't consider putting it under crypto directly (so crypto.DiffieHellman) is some schemes (like RSA) have multiple uses. RSA can be used for public key encryption, key exchange, and cryptographic signing. I think having something like crypto.pke.RSA, crypto.key_exchange.RSA, and crypto.signing.RSA would be better than crypto.RSAEncryption, crypto.RSAKeyExchange, and crypto.RSASign. Especially because for discoverability, a user is more likely to want to know "what key exchange schemes are in Sage" than "what RSA variants are in Sage". Thinking about the future and how many cryptography schemes (public key encryption, key exchange, signing, symmetric ciphers, etc.) could be implemented in Sage, putting it all under crypto would be too much.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think crypto.key_exchange.DiffieHellman looks good (definitely not crypto.DiffieHellman), but again I don't have a strong opinion.

After writing that I've convinced myself that it should be changed to crypto.key_exchange.

Lol, it happens :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I realized that making it accessible under crypto.key_exchange would involve some major restructuring to the crypto module and I think that would be out of scope for this PR. Unless I'm forgetting some syntax, I don't think Python import statements let you do from a.b.c import x as y.z. Basically above I was advocating to change from sage.crypto.all import * to import sage.crypto.all as crypto in the all.py file in src/sage which currently only stats does (but stats still imports everything to global scope anyway).

For now I think it's fine to leave it as key_exchange in global scope. It's at least better than what we have for other stuff in the crypto module. But if you have other suggestions I'm open.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay if it's too much irrelevant stuff for this PR then it's okay to leave it (and optionally make a new issue that hopefully someone gets to.)

23 changes: 23 additions & 0 deletions src/sage/crypto/key_exchange/catalog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
Index of key exchange schemes

This catalogue includes implementations of key exchange schemes.

Let ``<tab>`` indicate pressing the :kbd:`Tab` key. So begin by typing
``key_exchange.<tab>`` to the see the currently implemented key exchange
schemes.

This catalogue includes the following key exchange schemes:

- :class:`sage.crypto.key_exchange.diffie_hellman.DiffieHellman`

To import these names into the global namespace, use::

sage: from sage.crypto.key_exchange.catalog import *
"""

from sage.misc.lazy_import import lazy_import

lazy_import('sage.crypto.key_exchange.diffie_hellman', 'DiffieHellman')

del lazy_import
325 changes: 325 additions & 0 deletions src/sage/crypto/key_exchange/diffie_hellman.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
r"""
Diffie-Hellman Key Exchange Scheme

This module contains a toy implementation of the Diffie-Hellman key exchange
scheme.

AUTHORS:

- Vincent Macri (2024-07-30): initial version
"""
# ****************************************************************************
# Copyright (C) 2024 Vincent Macri <vincent.macri@ucalgary.ca>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# https://www.gnu.org/licenses/
# ****************************************************************************

from sage.misc.superseded import experimental

from sage.crypto.key_exchange.key_exchange_scheme import KeyExchangeScheme

from sage.arith.misc import is_prime
from sage.misc.prandom import randint
from sage.rings.integer import Integer
from sage.rings.finite_rings.finite_field_constructor import GF
from sage.rings.finite_rings.finite_field_prime_modn import \
FiniteField_prime_modn
from sage.rings.finite_rings.integer_mod import IntegerMod_abstract
from sage.structure.proof.proof import WithProof

from typing import Union

class DiffieHellman(KeyExchangeScheme):

@experimental(37305)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment on KeyExchangeScheme on why this is marked experimental.

def __init__(self, p: Integer, g: Union[Integer, IntegerMod_abstract],
vincentmacri marked this conversation as resolved.
Show resolved Hide resolved
proof: bool = True) -> None:
r"""
Create an instance of the Diffie-Hellman key exchange scheme using the
given prime ``p`` and base ``g``.

INPUT:

- ``p`` -- prime integer defining the field `\GF{p}` that the key
exchanges will be performed over, must be at least 5

- ``g`` -- base for the key exchange, (coerceable to) an element of
`\GF{p}` from `2` to `p - 2`

- ``proof`` -- (default: ``True``) whether to require a proof that
``p`` is prime. If ``False``, a probabilistic test can be used for
checking that ``p`` is prime. This should be set to ``False``
when using large (cryptographic size) primes, otherwise checking
primality will take too long.

.. WARNING::

This is a toy implementation for educational use only! Do not use
this implementation, or any cryptographic features of Sage, in any
setting where security is needed!

REFERENCES:

For more information, see Section 8.1 of [PP2010]_.

EXAMPLES::

sage: DH = key_exchange.DiffieHellman(13, 2)
doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation.
See https://github.com/sagemath/sage/issues/37305 for details.

This is an example of a full key exchange using a cryptographically
large prime. This is the prime from the 8192-bit MODP group in RFC 3526
(see [KK2003]_)::

sage: p = 2^8192 - 2^8128 - 1 + 2^64 * (round(2^8062 * pi) + 4743158)
sage: DH = key_exchange.DiffieHellman(p, 2, proof=False)
sage: alice_sk = DH.generate_secret_key()
sage: alice_pk = DH.generate_public_key(alice_sk)
sage: bob_sk = DH.generate_secret_key()
sage: bob_pk = DH.generate_public_key(bob_sk)
sage: alice_shared_secret = DH.compute_shared_secret(bob_pk, alice_sk)
sage: bob_shared_secret = DH.compute_shared_secret(alice_pk, bob_sk)
sage: alice_shared_secret == bob_shared_secret
True

TESTS::

sage: DH = key_exchange.DiffieHellman(3, 2)
Traceback (most recent call last):
...
ValueError: p must be at least 5

sage: DH = key_exchange.DiffieHellman(5, 0)
Traceback (most recent call last):
...
ValueError: g cannot be 0, 1, or p - 1 (mod p)

sage: DH = key_exchange.DiffieHellman(5, 1)
Traceback (most recent call last):
...
ValueError: g cannot be 0, 1, or p - 1 (mod p)

sage: DH = key_exchange.DiffieHellman(5, 4)
Traceback (most recent call last):
...
ValueError: g cannot be 0, 1, or p - 1 (mod p)
"""

if p < 5:
raise ValueError('p must be at least 5')

if proof:
# The modn implementation checks that ``p`` is prime
self._field = GF(p, impl='modn')
else:
with WithProof('arithmetic', False):
self._field = GF(p, impl='modn')

self._p = p
self._g = self._field(g)

# While these values won't cause mathematical problems, they do
# completely break the security of the Diffie-Hellman scheme.
# g = 0 makes every secret key and shared secret 0
# g = 1 makes every secret key and shared secret 1
# g = -1 makes every secret key and shared secret 1 or -1
if self._g == 0 or self._g == 1 or self._g == p - 1:
raise ValueError('g cannot be 0, 1, or p - 1 (mod p)')

def field(self) -> FiniteField_prime_modn:
"""
Return the field this ``DiffieHellman`` instance is working over.

EXAMPLES::

sage: DH = key_exchange.DiffieHellman(5, 2)
sage: DH.field()
Finite Field of size 5
"""
return self._field

def prime(self) -> Integer:
"""
Return the prime ``p`` for this ``DiffieHellman`` instance.

EXAMPLES::

sage: DH = key_exchange.DiffieHellman(7, 3)
sage: DH.prime()
7
"""
return self._p

def generator(self) -> IntegerMod_abstract:
"""
Return the generator ``g`` for this ``DiffieHellman`` instance.

EXAMPLES::

sage: DH = key_exchange.DiffieHellman(7, 3)
sage: DH.generator()
3
"""
return self._g

def parameters(self) -> tuple[Integer, IntegerMod_abstract]:
"""
Get the parameters ``(p, g)`` for this ``DiffieHellman`` instance.

EXAMPLES::

sage: DH = key_exchange.DiffieHellman(7, 3)
sage: DH.parameters()
(7, 3)
"""
return (self._p, self._g)

def generate_secret_key(self) -> Integer:
"""
Generate a random Diffie-Hellman secret key.

TESTS:

sage: DH = key_exchange.DiffieHellman(7, 2)
sage: keys = [DH.generate_secret_key() for i in range(10)]
sage: all(2 <= i <= 5 for i in keys)
True
"""
return randint(2, self._p - 2)

def generate_public_key(self, secret_key: Integer) -> IntegerMod_abstract:
"""
Generate a Diffie-Hellman public key using the given secret key.

INPUT:

- ``secret_key`` -- the secret key to generate the public key with

EXAMPLES::

sage: DH = key_exchange.DiffieHellman(13, 2)
sage: DH.generate_public_key(4)
3
"""
return self._g**secret_key

def compute_shared_secret(self, pk: IntegerMod_abstract,
sk: Integer) -> IntegerMod_abstract:
"""
Compute the shared secret using the given public key and secret keys.

INPUT:

- ``pk`` -- public key

- ``sk`` -- secret key

EXAMPLES::

sage: DH = key_exchange.DiffieHellman(17, 3)
sage: DH.compute_shared_secret(13, 11)
4
"""
return self._field(pk**sk)

def subgroup_size(self) -> Integer:
"""
Calculates the size of the subgroup of `\\GF{p}` generated by
``self.generator()``.

EXAMPLES:

This is an example of a ``DiffieHellman`` instance where the subgroup
size is `(p - 1) / 2`::

sage: DH = key_exchange.DiffieHellman(47, 2)
sage: DH.subgroup_size()
23

This is an example of a ``DiffieHellman`` instance where the subgroup
size is `p - 1`::

sage: DH = key_exchange.DiffieHellman(47, 5)
sage: DH.subgroup_size()
46
"""
return self._g.multiplicative_order()

def __len__(self) -> int:
"""
Calculates the size of the subgroup of `\\GF{p}` generated by
``self.generator()``. This is a wrapper around `subgroup_size`.

TESTS::

sage: DH = key_exchange.DiffieHellman(53, 9)
sage: len(DH)
26
"""
return int(self.subgroup_size())

def __eq__(self, other) -> bool:
"""
Check if two ``DiffieHellman`` instances have the same parameter set.

TESTS::

sage: DH1 = key_exchange.DiffieHellman(5, 2)
sage: DH2 = key_exchange.DiffieHellman(5, 2)
sage: DH1 == DH2
True
sage: DH1 == 5
False
"""
if isinstance(other, DiffieHellman):
return self.parameters() == other.parameters()
return False

def __hash__(self) -> int:
"""
Compute the hash value of a ``DiffieHellman`` instance.

TESTS::

sage: DH1 = key_exchange.DiffieHellman(7, 3)
sage: DH2 = key_exchange.DiffieHellman(7, 3)
sage: s = set([DH1, DH2])
sage: len(s)
1
"""
return hash((self._p, self._g))

def _repr_(self) -> str:
"""
Get the string representation of the ``DiffieHellman`` instance.

TESTS::

sage: DH = key_exchange.DiffieHellman(7, 3)
sage: DH
Diffie-Hellman key exchange over Finite Field of size 7 with generator 3
"""
return ('Diffie-Hellman key exchange over '
f'{self._field} '
'with generator '
f'{self._g}')

def _latex_(self) -> str:
r"""
Get the LaTeX representation of the ``DiffieHellman`` instance.

TESTS::

sage: DH = key_exchange.DiffieHellman(7, 3)
sage: latex(DH)
\text{Diffie-Hellman key exchange over }\Bold{F}_{7}\text{ with generator }3
"""
return ('\\text{Diffie-Hellman key exchange over }'
f'{self._field._latex_()}'
'\\text{ with generator }'
f'{self._g}')
Loading
Loading