-
-
Notifications
You must be signed in to change notification settings - Fork 740
Add framework for key exchange schemes and Diffie-Hellman #38374
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
Merged
Merged
Changes from 20 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 hidden or 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 hidden or 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 hidden or 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,4 @@ | ||
| from sage.misc.lazy_import import lazy_import | ||
|
|
||
| lazy_import('sage.crypto.key_exchange.diffie_hellman', 'DiffieHellman') | ||
| del lazy_import | ||
This file contains hidden or 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,341 @@ | ||
| r""" | ||
| Diffie-Hellman Public Key Exchange Scheme | ||
|
|
||
| This module contains a toy implementation of the Diffie-Hellman public key | ||
| exchange scheme. | ||
|
|
||
| AUTHORS: | ||
|
|
||
| - Vincent Macri (2024-07-17): initial version | ||
| """ | ||
| # **************************************************************************** | ||
| # Copyright (C) 2024 Vincent Macri <[email protected]> | ||
| # | ||
| # 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 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) | ||
|
Member
Author
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: | ||
| """ | ||
| 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 | ||
vincentmacri marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| - ``g`` -- base for the key exchange, (coerceable to) an element of | ||
| `\\GF{p}` from `2` to `p - 2` | ||
vincentmacri marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| - ``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 [PP2010]_, section 8.1. | ||
vincentmacri marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| EXAMPLES:: | ||
|
|
||
| sage: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = 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 | ||
| RFC3526:: | ||
vincentmacri marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| sage: p = 2^8192 - 2^8128 - 1 + 2^64 * (round(2^8062 * pi) + 4743158) | ||
| sage: DH = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = DiffieHellman(3, 2) | ||
| Traceback (most recent call last): | ||
| ... | ||
| ValueError: p must be at least 5 | ||
|
|
||
| sage: DH = DiffieHellman(5, 0) | ||
| Traceback (most recent call last): | ||
| ... | ||
| ValueError: g cannot be 0, 1, or p - 1 (mod p) | ||
|
|
||
| sage: DH = DiffieHellman(5, 1) | ||
| Traceback (most recent call last): | ||
| ... | ||
| ValueError: g cannot be 0, 1, or p - 1 (mod p) | ||
|
|
||
| sage: DH = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = DiffieHellman(47, 2) | ||
| sage: DH.subgroup_size() | ||
| 23 | ||
|
|
||
| This is an example of a ``DiffieHellman`` instance where the subgroup | ||
| size is `p - 1`:: | ||
|
|
||
| sage: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH1 = DiffieHellman(5, 2) | ||
| sage: DH2 = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH1 = DiffieHellman(7, 3) | ||
| sage: DH2 = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = 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: from sage.crypto.key_exchange.diffie_hellman import DiffieHellman | ||
| sage: DH = 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.
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_keyfolder for this.I'm now thinking I may have done this wrong (and maybe
public_keydid too but I think that's out of scope for this PR,public_keyneeds 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,
DiffieHellmancan 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.DiffieHellmanorkey_exchange.DiffieHellmanif that's preferred though.Uh oh!
There was an error while loading. Please reload this page.
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
DiffieHellmanis accessible without import, butKeyExchangeSchemerequires 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
catalogfile, 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_exchangein 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
SubstitutionCryptosystembeing global scope (which it currently is).I don't think it's too different than how
distributionsis 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 likeSubstitutionCryptosystemout 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
cryptodirectly (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.RSAwould 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 undercryptowould 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.DiffieHellmanlooks 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_exchangewould involve some major restructuring to thecryptomodule 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 cryptoin theall.pyfile insrc/sagewhich currently onlystatsdoes (butstatsstill imports everything to global scope anyway).For now I think it's fine to leave it as
key_exchangein global scope. It's at least better than what we have for other stuff in thecryptomodule. 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.)