-
-
Notifications
You must be signed in to change notification settings - Fork 487
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
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 0b2d8ab
Fix formatting error in references
vincentmacri 2655712
Style and doc date
vincentmacri 73bffc6
Fix annotations and add missing check
vincentmacri c183db2
References
vincentmacri cc513e7
Missing tests
vincentmacri 5ee6d0a
Formatting
vincentmacri 4cb006f
Cleanup and tests
vincentmacri 16d3d98
Rename file
vincentmacri f0a8ea0
LaTeX doctest
vincentmacri bacebe9
Style fix
vincentmacri b12addd
Fix doc formatting
vincentmacri 6f7b5e2
Apply suggestions from code review
vincentmacri f7b0744
Formatting for when referring to the DiffieHellman class rather than …
vincentmacri 6b86174
Add missing test
vincentmacri a97866a
Formatting and type annotation fixes
vincentmacri babeb3c
String formatting
vincentmacri dd098a1
Cleanup pass
vincentmacri 42159fc
Line length fixes
vincentmacri 6ab21e9
Merge branch 'develop' into pke
vincentmacri 6298553
Use more standard wording
vincentmacri 7801f9d
Clearer wording
vincentmacri 7790037
Merge branch 'develop' into pke
vincentmacri f5491a2
Merge branch 'develop' into pke
vincentmacri f74d913
Apply suggestions from code review
vincentmacri 474e09d
Add reference to RFC
vincentmacri 441fdfc
Refactoring/cleanup
vincentmacri d58532b
Import tweaks
vincentmacri cdb0962
Fix tests
vincentmacri e11ad4e
Doc formatting and date update
vincentmacri 5726da8
Formatting consistency
vincentmacri fc1a4dc
Use catalog file
vincentmacri File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See comment on |
||
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}') |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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
orkey_exchange.DiffieHellman
if that's preferred though.There was a problem hiding this comment.
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, butKeyExchangeScheme
requires an import. I don't think an abstract base class needs to be exposed to users without them explicitly importing it.There was a problem hiding this comment.
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)There was a problem hiding this comment.
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?There was a problem hiding this comment.
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 undercrypto.key_exchange
, so you'd docrypto.key_exchange.DiffieHellman
. We could then in the future move the other cryptography code tocrypto.[something]
as well and take things likeSubstitutionCryptosystem
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 (socrypto.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 likecrypto.pke.RSA
,crypto.key_exchange.RSA
, andcrypto.signing.RSA
would be better thancrypto.RSAEncryption
,crypto.RSAKeyExchange
, andcrypto.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 undercrypto
would be too much.There was a problem hiding this comment.
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 notcrypto.DiffieHellman
), but again I don't have a strong opinion.Lol, it happens :)
There was a problem hiding this comment.
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 thecrypto
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 dofrom a.b.c import x as y.z
. Basically above I was advocating to changefrom sage.crypto.all import *
toimport sage.crypto.all as crypto
in theall.py
file insrc/sage
which currently onlystats
does (butstats
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 thecrypto
module. But if you have other suggestions I'm open.There was a problem hiding this comment.
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.)