Skip to main content

Command Palette

Search for a command to run...

The 1976 Paper That Changed How We Keep Secrets on the Internet

Updated
9 min readView as Markdown
The 1976 Paper That Changed How We Keep Secrets on the Internet
S
DevRel at StackGen | Formerly at Deepfence ,Tenable , Accurics | AWS Community Builder also Docker Community Award Winner at Dockercon2020 | CyberSecurity Innovator of Year 2023 award by Bsides Bangalore | Docker/HashiCorp Meetup Organiser Bangalore & Co-Author of Learn Lightweight Kubernetes with k3s (2019) , Packt Publication & also run Non Profit CloudNativeFolks / CloudSecCorner Community To Empower Free Education reach out me twitterhttps://twitter.com/sangamtwts or just follow on GitHub -> https://github.com/sangam14 for Valuable Resources

I was reading an old cryptography paper recently:

“New Directions in Cryptography” — Whitfield Diffie and Martin Hellman, 1976.

It is almost 50 years old, but the problem it talks about feels surprisingly modern.

How can two people who have never met communicate securely over an untrusted network?

Today we take things like HTTPS, encrypted messaging, SSH and secure APIs for granted.

In 1976, this was a much harder problem.

The paper proposed a completely different way of thinking about cryptography.

Imagine Rahul wants to talk to Priya

Rahul lives in Bengaluru.

Priya lives in Pune.

They want to communicate privately.

There is one obvious solution:

Give both of them the same secret key.

Something like:

secret = 482793

Rahul can use it to encrypt his messages.

Priya can use the same secret to decrypt them.

Simple.

Except there is a big problem.

How does Rahul send that secret to Priya securely?

If he emails it, someone could read the email.

If he sends it through a network, someone could intercept the traffic.

If he has to physically meet Priya first, secure communication over the internet suddenly becomes much less useful.

This is the key-distribution problem.

Diffie and Hellman asked a different question:

What if we don't send the secret at all?

Create the secret instead of sending it

Rahul and Priya first agree on some public information.

Everyone can see it.

Then Rahul chooses a private number.

Priya chooses another private number.

They each calculate a public value and exchange only those public values.

Finally, both sides combine:

their private value
+
the other person's public value

and arrive at the same secret.

Conceptually:

Rahul                         Priya

private = a                   private = b
    |                             |
    v                             v
public = A                   public = B
    |                             |
    |----------- A -------------> |
    | <---------- B ------------- |
    |                             |
    v                             v

calculate secret             calculate secret

             same result

The shared secret itself never travels over the network.

That is the beautiful part.

A tiny mathematical example

Let's use small numbers.

Rahul and Priya publicly agree on:

p = 23
g = 5

Rahul privately chooses:

a = 6

Priya privately chooses:

b = 15

Rahul calculates:

A = 5⁶ mod 23
A = 8

Priya calculates:

B = 5¹⁵ mod 23
B = 19

They exchange 8 and 19.

Rahul calculates:

19⁶ mod 23 = 2

Priya calculates:

8¹⁵ mod 23 = 2

Both get:

Shared Secret = 2

Neither Rahul nor Priya ever sent 2.

Let's write that in Python

The same example can be expressed in a few lines of code.

# Public values
p = 23
g = 5

# Private values
rahul_private = 6
priya_private = 15

# Generate public values
rahul_public = pow(g, rahul_private, p)
priya_public = pow(g, priya_private, p)

print("Rahul public:", rahul_public)
print("Priya public:", priya_public)

# Calculate shared secrets
rahul_shared = pow(priya_public, rahul_private, p)
priya_shared = pow(rahul_public, priya_private, p)

print("Rahul shared secret:", rahul_shared)
print("Priya shared secret:", priya_shared)

assert rahul_shared == priya_shared

Output:

Rahul public: 8
Priya public: 19

Rahul shared secret: 2
Priya shared secret: 2

This line is doing most of the interesting work:

pow(g, private_value, p)

Python's three-argument pow() calculates:

g^private_value mod p

without first creating one enormous intermediate number.

So:

pow(5, 6, 23)

means:

5⁶ mod 23

which gives:

8

What can an attacker see?

Suppose Vikram is monitoring the connection.

He can see:

p = 23
g = 5

Rahul public = 8
Priya public = 19

In code, his view might look like this:

p = 23
g = 5

rahul_public = 8
priya_public = 19

What he does not directly know is:

rahul_private = 6
priya_private = 15

With tiny numbers, Vikram could simply brute-force the answer.

For example:

p = 23
g = 5
rahul_public = 8

for possible_private in range(1, p):
    result = pow(g, possible_private, p)

    if result == rahul_public:
        print("Found Rahul's private value:", possible_private)
        break

This quickly discovers:

Found Rahul's private value: 6

So our tiny example is not secure.

It exists only to make the mathematics understandable.

Real cryptography uses carefully designed groups and values so large that this kind of search becomes computationally impractical.

That is an important lesson:

Never use toy cryptography examples as production cryptography.

What does the math actually look like?

Rahul creates:

A = gᵃ mod p

Priya creates:

B = gᵇ mod p

Rahul receives B and calculates:

Bᵃ mod p

which is:

(gᵇ)ᵃ mod p

or:

gᵃᵇ mod p

Priya receives A and calculates:

Aᵇ mod p

which becomes:

(gᵃ)ᵇ mod p

or:

gᵃᵇ mod p

Both arrive at the same mathematical result.

Rahul: gᵃᵇ mod p

Priya: gᵃᵇ mod p

That is why the agreement works.

A more realistic programmatic example

In real software, we should not manually invent cryptographic parameters.

Instead, we use well-reviewed cryptographic libraries.

A modern approach is usually based on elliptic-curve Diffie–Hellman.

For example, Python's cryptography library supports X25519.

from cryptography.hazmat.primitives.asymmetric.x25519 import (
    X25519PrivateKey,
)

# Rahul creates a private key
rahul_private = X25519PrivateKey.generate()
rahul_public = rahul_private.public_key()

# Priya creates a private key
priya_private = X25519PrivateKey.generate()
priya_public = priya_private.public_key()

# Rahul derives the shared secret
rahul_shared = rahul_private.exchange(priya_public)

# Priya derives the shared secret
priya_shared = priya_private.exchange(rahul_public)

print(rahul_shared == priya_shared)

Output:

True

Again, both sides independently arrive at the same shared value.

Conceptually:

Rahul Private Key
       +
Priya Public Key
       |
       v
 Shared Secret


Priya Private Key
       +
Rahul Public Key
       |
       v
 Shared Secret

And both shared secrets are identical.

But don't use the raw shared secret directly

This is another important detail.

A real application normally does not take the raw Diffie–Hellman output and immediately use it as an AES key.

Instead, the shared material goes through a key derivation function.

Something like:

Diffie–Hellman
      |
      v
Raw Shared Secret
      |
      v
     KDF
      |
      v
Encryption Key

A common example is HKDF.

In Python:

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF

derived_key = HKDF(
    algorithm=hashes.SHA256(),
    length=32,
    salt=None,
    info=b"rahul-priya-session",
).derive(rahul_shared)

print(len(derived_key))

Output:

32

Now we have a 32-byte derived key that can be used by an appropriate symmetric encryption algorithm.

So the real flow looks closer to:

Rahul                         Priya
  |                             |
Generate key pair           Generate key pair
  |                             |
Exchange public keys
  | <-------------------------> |
  |                             |
Compute shared secret       Compute shared secret
  |                             |
  v                             v
            Same Secret
                 |
                 v
                HKDF
                 |
                 v
           Encryption Key

Why not just encrypt with Diffie–Hellman?

Because Diffie–Hellman is mainly about key agreement.

It answers:

How can Rahul and Priya establish shared secret material?

It does not directly answer:

How should they encrypt every message?

For the actual data, we generally use fast symmetric encryption.

Conceptually:

shared_secret = diffie_hellman()

encryption_key = derive_key(shared_secret)

encrypted_message = encrypt(
    encryption_key,
    b"Hello Priya"
)

So Diffie–Hellman solves one part of a larger secure communication protocol.

There is still a major problem

Our program has silently assumed that Rahul really received Priya's public key.

What if Vikram replaces it?

Imagine:

Rahul <------> Vikram <------> Priya

Vikram generates his own keys.

Rahul creates a shared secret with Vikram.

Priya creates another shared secret with Vikram.

Programmatically, it could look conceptually like:

Rahul:

Rahul private
+
Vikram public
=
Secret 1


Priya:

Priya private
+
Vikram public
=
Secret 2

Rahul still gets a valid secret.

Priya still gets a valid secret.

The mathematics works perfectly.

The problem is identity.

Rahul doesn't know that the public key belongs to Vikram instead of Priya.

This is why real protocols combine key agreement with authentication.

That may involve:

Digital certificates
Digital signatures
Pre-shared identities
Authenticated key exchange

This distinction is very important:

Cryptography can prove that a mathematical operation is correct without automatically proving who is on the other side of the network.

Public-key cryptography changed the model

Traditional symmetric cryptography looks roughly like:

Rahul ── shared secret ── Priya

Both sides already need the secret.

Public-key cryptography introduces a different model.

Priya can have:

Public Key
Private Key

Her public key can be shared widely.

Her private key stays private.

This allows systems to separate:

What can be public

from:

What absolutely must remain secret

That separation became one of the foundations of modern cryptography.

Why this matters for developers

As developers, we rarely implement Diffie–Hellman ourselves.

But we use systems based on these ideas constantly.

When you run:

ssh user@example.com

or open:

https://example.com

or use an encrypted communication protocol, there is often some form of cryptographic key establishment happening underneath.

Your application may look simple:

requests.get("https://example.com")

but underneath that one line is an entire cryptographic protocol doing things such as:

Key agreement
Authentication
Certificate validation
Key derivation
Symmetric encryption
Integrity checking

Most developers never see it.

And that's actually a sign that the infrastructure works well.

What I find most interesting

The mathematics is interesting.

But the bigger idea is even better.

The old question was:

How do I securely send you my secret?

Diffie and Hellman helped replace it with:

Can we create a secret together without transmitting that secret?

In Python, that idea eventually becomes something as small as:

rahul_shared = rahul_private.exchange(priya_public)

priya_shared = priya_private.exchange(rahul_public)

Two different machines.

Two different private keys.

Two public values travelling across an untrusted network.

And both sides arrive at the same secret.

That is a remarkably powerful idea.

Almost 50 years after the original paper, the implementation may have changed, the curves may have changed, and the protocols around it have become far more sophisticated.

But the central idea remains:

Two strangers can establish a secret without sending the secret itself.

And that idea still sits underneath a huge part of the secure internet we use every day.

14 views