Diffie-Hellman Key Exchange, step-by-step
Tags: crypto
Published: 2021-09-15 01:19:23

# Diffie Hellman Key Exchange, Step-by-step



## Introduction  

> Cryptography is means to securely communicate in the presence of adversary.
> _- anonymous -_

In WW2, a machine called Enigma is created with sole purpose of securing communication between the army. It worked well, in fact really well that even using today computer it's still really difficult to break the encryption. That being said, it has some weakness. One of them is how to distribute encryption key across wide area. 
The machine use symmetric key which means the party communicating use the same key to encrypt and decrypt message. So it's like chicken egg problem, in order to secure message transmission, a key is needed. But how to securely exchange the key? 

To tackle this, Diffie-Hellman algorithm use an ingenious and simple technique. It exploits commutative property of power operation. Key is generated in several stteps, in which some parts are available to public. In this article, we will take closer look at how awesome this technique is.
<br>

## Overview

The key of DF key exchange is exploiting commutative property of power operation. That is:<br>

> (x ^ a) ^ b = (x ^ b) ^ a

We will see how this is working in a moment.

Diffie-Hellman algorithm consists of several steps:

- Decide on common value. We need __g__ as base value and __p__ for power factor and modulus.
- Each party generate their respective private key (__pk__).
- Each party also generate public key (__pb__) and send them to each other.
- From __p__, __pk__ and __pb__ a new secret key (__s__) is created. This key is identical for both party.

Here's the full code. We'll break it down step-by-step.

```python
import random


def new_pair(p, g):
    pk = private_key(p)
    pb = public_key(pk, p, g)
    return pk, pb


def private_key(p):
    k = 0
    while k <= 1:
        g = random.randint(2, 50000)
        k = (g ** p) % p
    return k


def public_key(pk, p, g):
    return (g ** pk) % p


def secret_key(pk, pb_r, p):
    return (pb_r ** pk) % p


def my_test():
    g = 2
    p = 19
    pk1, pb1 = new_pair(p, g)
    pk2, pb2 = new_pair(p, g)
    print("keypair 1:", pk1, pb1)
    print("keypair 2:", pk2, pb2)
    s1 = secret_key(pk1, pb2, p)
    s2 = secret_key(pk2, pb1, p)
    print("s", s1, s2)
```  

Alright.. Let's get started:

### Step 1: Decide on common key

In the first step, both party need to decide on common key. Normally this key is a very large prime number. But in this article a small prime number is sufficient.
We create a helper class to hold our test value.

```python
class DFTest:
    def __init__(self, g, p, pk1, pk2, pb1, pb2, s):
        # Generator
        self.g = g
        # common pb_ry, modulus
        self.p = p
        # private keys
        self.pk1 = pk1
        self.pk2 = pk2
        # public keys
        self.pb1 = pb1
        self.pb2 = pb2
        # Secret key
        self.s = s
```  

Evaluate above code and then we create our test variable:

```python
>>> tc = DFTest(5, 23, 6, 15, 8, 19, 2)
>>>
```  

### Step 2: Generate private key

This private key is only known and kept by one of the party. We will use this key later to generate the final secret key.

```python
def private_key(p):
    k = 0
    while k <= 1:
        g = random.randint(2, 50000)
        k = (g ** p) % p
    return k
```  

OK, what happened here? Well, we take a random value __g__, power it by factor of __p__ and calculate it 's modulus by __p__. The __g__ value is randomly generated to make it harder for attacker to predict. 
We also do not want value 0 or 1 because you want some value that can be multiplied into larger ones.<br><br>

Let's test in the console:

```  python
>>> private_key(tc.p)
15
>>> private_key(tc.p)
6
>>> private_key(tc.p)
8
>>> 
```  
	
Private key value is changing everytime it's called to make life harder for attacker.

### Step 3: Generate public key

We will now generate public key which will be send to other party.<br><br>

```python
def public_key(pk, p, g):
    return (g ** pk) % p
```  

Let's test this in the console

``` python
>>> public_key(tc.pk1, tc.p, tc.g)
8
>>> 
```  

### Step 4: Generate secret key

We now already have all the required keys to generate the final key, the secret key.

```python
def secret_key(pk, pb_r, p):
    return (pb_r ** pk) % p
```  
<br><br>

And test this:

```python
>>> s1 = secret_key(tc.pk1, tc.pb2, tc.p)
>>> s2=secret_key(tc.pk2, tc.pb1, tc.p)
>>> print(s1,s2)
2 2
>>> 
```  

### Putting it all together

Combining all of the above together, let's run my_test() several times:

```python
>>> my_test()
keypair 1: 17 10
keypair 2: 3 8
s 12 12
>>> my_test()
keypair 1: 17 10
keypair 2: 8 9
s 17 17
```

As you can see, even with constant value of __g__ and __p__, we get different private key and public key and identical secret key. This way, although some of the keys are in public area, the secret key is very well hidden.
But how is this possible? If we look closer and remove the modulus operator for a moment, we arrive at:<br><br>

> (g ^ pk1) ^ pb2 = (g ^ pk2) ^ pb1 

And the modulus part has role to make things harder to guess. If we have:

> x % 7 = 1

We have a almost infinite amount of possibilities for x.

## Summary

The beauty of DF key exchange is that it is able to generate secure secret key by exploiting simple mathematical concept. With DF, we can securely exchange keys and solve our initial problem.