Skip to content
New issue

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

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

Already on GitHub? # to your account

Simplify diffie_hellman.py #12541

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 2 commits
Commits
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
97 changes: 31 additions & 66 deletions ciphers/diffie_hellman.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from binascii import hexlify
from hashlib import sha256
from os import urandom
"""
https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
"""

# RFC 3526 - More Modular Exponential (MODP) Diffie-Hellman groups for
# Internet Key Exchange (IKE) https://tools.ietf.org/html/rfc3526
import random

primes = {
"""
RFC 3526 - More Modular Exponential (MODP) Diffie-Hellman groups for
Internet Key Exchange (IKE) https://tools.ietf.org/html/rfc3526
"""
GROUPS = {
# 1536-bit
5: {
"prime": int(
Expand Down Expand Up @@ -181,50 +184,34 @@

class DiffieHellman:
"""
Class to represent the Diffie-Hellman key exchange protocol
Class to represent one party in the Diffie-Hellman key exchange protocol

Current minimum recommendation is 2048 bit
>>> group = GROUPS[14]
>>> prime, generator = group['prime'], group['generator']
>>> alice = DiffieHellman(prime, generator)

>>> alice = DiffieHellman()
>>> bob = DiffieHellman()
Both parties should agree on the same public parameters
>>> bob = DiffieHellman(alice.prime, alice.generator)

Comment on lines +200 to +201
Copy link
Member

@ruppysuppy ruppysuppy Jan 29, 2025

Choose a reason for hiding this comment

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

Are we generating Bob's data using Alice's prime? Isn't the prime supposed to be private?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only secret attribute is each self.__private_key, which does not necessarily have to be a prime number. In cryptography it is strange that more than one parameter must be kept secret.

>>> alice_private = alice.get_private_key()
>>> alice_public = alice.generate_public_key()
Alice sends Bob its public key,
>>> bob_shared = bob.generate_shared_key(alice.public_key)

>>> bob_private = bob.get_private_key()
>>> bob_public = bob.generate_public_key()
and the same vice versa:
>>> alice_shared = alice.generate_shared_key(bob.public_key)

>>> # generating shared key using the DH object
>>> alice_shared = alice.generate_shared_key(bob_public)
>>> bob_shared = bob.generate_shared_key(alice_public)

>>> assert alice_shared == bob_shared

>>> # generating shared key using static methods
>>> alice_shared = DiffieHellman.generate_shared_key_static(
... alice_private, bob_public
... )
>>> bob_shared = DiffieHellman.generate_shared_key_static(
... bob_private, alice_public
... )

>>> assert alice_shared == bob_shared
>>> alice_shared == bob_shared
True
"""

# Current minimum recommendation is 2048 bit (group 14)
def __init__(self, group: int = 14) -> None:
if group not in primes:
raise ValueError("Unsupported Group")
self.prime = primes[group]["prime"]
self.generator = primes[group]["generator"]
def __init__(self, prime: int, generator: int) -> None:
self.prime = prime
self.generator = generator
self.__private_key = random.getrandbits(256)

self.__private_key = int(hexlify(urandom(32)), base=16)

def get_private_key(self) -> str:
return hex(self.__private_key)[2:]

def generate_public_key(self) -> str:
public_key = pow(self.generator, self.__private_key, self.prime)
return hex(public_key)[2:]
@property
def public_key(self) -> int:
return pow(self.generator, self.__private_key, self.prime)

def is_valid_public_key(self, key: int) -> bool:
# check if the other public key is valid based on NIST SP800-56
Expand All @@ -233,32 +220,10 @@ def is_valid_public_key(self, key: int) -> bool:
and pow(key, (self.prime - 1) // 2, self.prime) == 1
)

def generate_shared_key(self, other_key_str: str) -> str:
other_key = int(other_key_str, base=16)
def generate_shared_key(self, other_key: int) -> int:
if not self.is_valid_public_key(other_key):
raise ValueError("Invalid public key")
shared_key = pow(other_key, self.__private_key, self.prime)
return sha256(str(shared_key).encode()).hexdigest()

@staticmethod
def is_valid_public_key_static(remote_public_key_str: int, prime: int) -> bool:
# check if the other public key is valid based on NIST SP800-56
return (
2 <= remote_public_key_str <= prime - 2
and pow(remote_public_key_str, (prime - 1) // 2, prime) == 1
)

@staticmethod
def generate_shared_key_static(
local_private_key_str: str, remote_public_key_str: str, group: int = 14
) -> str:
local_private_key = int(local_private_key_str, base=16)
remote_public_key = int(remote_public_key_str, base=16)
prime = primes[group]["prime"]
if not DiffieHellman.is_valid_public_key_static(remote_public_key, prime):
raise ValueError("Invalid public key")
shared_key = pow(remote_public_key, local_private_key, prime)
return sha256(str(shared_key).encode()).hexdigest()
return pow(other_key, self.__private_key, self.prime)


if __name__ == "__main__":
Expand Down
Loading