Exchanging Secret Keys
CS 463
Lecture, Dr. Lawlor
Diffie–Hellman–Merkle
key exchange is a neat, simple way to exchange private keys
with a communication party. Like RSA, the basic operation is
modular exponentiation on large numbers.
- Pick a big prime number p. All arithmetic from
here is modulo this number. Unlike RSA, you publish this.
- Pick a base g, which for security needs to be a
"generator" of p. You publish this too.
- Your side picks a secret integer a. You send out
A = ga mod p.
- Their side picks a secret integer b. They send
out B = gb mod p.
- You compute S=Ba mod p.
They compute S=Ab mod p.
Both sides end up with the same S because (gb)a
= (ga)b .
- S is now a shared secret. There aren't any fast
algorithms known for the discrete
logarithm problem, so it's tough for attackers to figure
out
CAUTION: unlike RSA signatures, there is no authentication--you know
you're talking to somebody securely, but not who you're talking
to. You might have just established a shared secret with an
impostor!
Generators
Turns out that you always end up with the same secret for any g.
But poor choices for g (not generators) tend to end up with
a shared secret of... just 1! There's nothing magic
about the good values, so you can actually use
published values (theoretically this makes you somewhat more
vulnerable to a rainbow table style enumeration attack, but this is
still much better than converging to a one-digit key). See discussion
of problem here.
Example
Here's a trivial example, using the BigInteger class we used for
RSA.
// Generate shared public values:
BigInteger p=BigInteger::probablePrime(bits); // publish
BigInteger g=BigInteger::random(bits/2); // FIXME: find generator
// side A:
BigInteger a=BigInteger::random(bits/2);
BigInteger A=g.modPow(a,p);
// side B:
BigInteger b=BigInteger::random(bits/2);
BigInteger B=g.modPow(b,p);
// .. exchange A and B across network here ...
// Compute private keys
BigInteger SA=A.modPow(b,p); // on B side
BigInteger SB=B.modPow(a,p); // on A side
// Print results
std::cout<<"Prime p: "<<p<<"\n";
std::cout<<"Base g: "<<g<<"\n";
std::cout<<"g^a: "<<A<<"\n";
std::cout<<"g^b: "<<B<<"\n";
std::cout<<"Shared secret A^b: 0x"<<SA.hex()<<"\n";
std::cout<<"Shared secret B^a: 0x"<<SB.hex()<<"\n";
On my machine, for bit
sizes of 30, 100, and 200 (all too small for real security!), this
prints:
Prime p: 730547563
Base g: 4
g^a: 4096
g^b: 16777216
Shared secret A^b: 0x23533229
Shared secret B^a: 0x23533229
Prime p: 1146660275211349850274803697017
Base g: 35
g^a: 703152882437914391137998747813
g^b: 302066547797101321667785500657
Shared secret A^b: 0x14901821721579422765303341433
Shared secret B^a: 0x14901821721579422765303341433
Prime p: 1312914435623925242579913123095835705606131294279008317556477
Base g: 91
g^a: 260025110135259411029398066614928316766970763876919890714931
g^b: 1516449130501758165088248370527242650801
Shared secret A^b: 0x256764771091704047183548835673182476142676286467672266536118
Shared secret B^a: 0x256764771091704047183548835673182476142676286467672266536118
Yet a different seed
(9) results in a bad generator:
Prime p: 981325853
Base g: 7
g^a: 282475249
g^b: 1
Shared secret A^b: 0x1
Shared secret B^a: 0x1
Try the code on your
machine: Zip, Tar-gzip. (License is
BSD. Also includes RSA, from last week.)