Thursday, September 14, 2017

Diffie-Hellman key exchange in Java

Diffie-Hellman key exchange is a method allows two parties that have no prior knowledge of each other to exchange a shared secret over a public (insecure) channel. This shared secret can then be used to derive a key for a symmetric cipher like AES (from high-level prospective, that's what happens when establishing a TLS connection).

Java supports Diffie-Hellman scheme via KeyAgreement class. Here is an example how Diffie-Hellman key exchange can be implemented with Java.

Diffie-Hellman scheme has two steps. In first step, we generate public and private values for Diffie-Hellman key exchange. The we need to send a public value to our partner. To do that, we just save public values in to a file. We also same a private value to a file, so then we can load it again.
In second step, we read a private value from a file. Then, we initialize KeyAgreement with the private value. Next, we read a public value generated by our partner. Finally, we complete key exchange with the public value, and generate a shared secret.

Here is how it can be done with Java:
package security.keyexchange;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.KeyAgreement;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPrivateKeySpec;
import javax.crypto.spec.DHPublicKeySpec;
/**
* Diffie-Hellman key exchange.
*
* Usage:
*
* javac -d classes DHKeyExchange.java
* java -cp classes security.keyexchange.DHKeyExchange bob init
* java -cp classes security.keyexchange.DHKeyExchange alice init
* java -cp classes security.keyexchange.DHKeyExchange bob complete alice
* java -cp classes security.keyexchange.DHKeyExchange alice complete bob
*/
public class DHKeyExchange {
// this is for bytesToHex() method below
private final static char[] HEX = "0123456789ABCDEF".toCharArray();
// let's use P and G values for ffdhe2048 group from TLS 1.3 spec
private static final BigInteger G = BigInteger.valueOf(2);
private static final BigInteger P = new BigInteger(
"FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" +
"D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" +
"7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" +
"2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" +
"984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" +
"30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" +
"B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" +
"0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" +
"9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" +
"3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" +
"886B423861285C97FFFFFFFFFFFFFFFF", 16);
// in Diffie-Hellman key exchange scheme we have two steps
private static enum Stage { init, complete }
/**
* Command line options:
* args[0] - stage (init or complete)
* args[1] - local user's name
* args[2] - remote user's name
*/
public static void main(String[] args) throws Exception {
String name = args[0];
Stage stage = Stage.valueOf(args[1]);
switch (stage) {
case init:
init(args[0]);
break;
case complete:
String from = args[2];
complete(name, from);
break;
default:
throw new RuntimeException("You should not be here!");
}
}
// first step:
// generate public and private values for Diffie-Hellman key exchange,
// and save them to files
private static void init(String name) throws Exception {
KeyPairGenerator gen = KeyPairGenerator.getInstance("DiffieHellman");
gen.initialize(new DHParameterSpec(P, G));
KeyPair keyPair = gen.generateKeyPair();
DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
Path privateFile = Paths.get(name + ".private");
try (OutputStream out = Files.newOutputStream(privateFile)) {
byte[] X = privateKey.getX().toByteArray();
out.write(X);
System.out.printf(">>> %s: private X saved to %s%n", name, privateFile);
}
Path publicFile = Paths.get(name + ".public");
try (OutputStream out = Files.newOutputStream(publicFile)) {
byte[] Y = publicKey.getY().toByteArray();
out.write(Y);
System.out.printf(">>> %s: public Y saved to %s:%n%s%n",
name, publicFile, bytesToHex(Y));
}
}
// second step:
// read a private value from a file
// initialize KeyAgreement with the private value
// read a public value from another person
// complete key exchange with the public value
// generate a shared secret
private static void complete(String name, String from) throws Exception {
KeyAgreement keyAgreement = KeyAgreement.getInstance("DiffieHellman");
KeyFactory keyFactory = KeyFactory.getInstance("DiffieHellman");
Path privateFile = Paths.get(name + ".private");
try (InputStream is = Files.newInputStream(privateFile)) {
byte[] X = new byte[(int) Files.size(privateFile)];
is.read(X);
BigInteger x = new BigInteger(X);
PrivateKey privateKey = keyFactory.generatePrivate(
new DHPrivateKeySpec(x, P, G));
keyAgreement.init(privateKey, new DHParameterSpec(P, G));
}
Path publicFile = Paths.get(from + ".public");
try (InputStream is = Files.newInputStream(publicFile)) {
byte[] Y = new byte[(int) Files.size(publicFile)];
is.read(Y);
BigInteger y = new BigInteger(Y);
PublicKey publicKey = keyFactory.generatePublic(
new DHPublicKeySpec(y, P, G));
keyAgreement.doPhase(publicKey, true);
}
byte[] secret = keyAgreement.generateSecret();
System.out.println(">>> shared secret: " + bytesToHex(secret));
}
// converts a byte array to a hex string
private static String bytesToHex(byte[] bytes) {
char[] result = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
result[j * 2] = HEX[v >>> 4];
result[j * 2 + 1] = HEX[v & 0x0F];
}
return new String(result);
}
}

And here is how it can be run:
#!/bin/bash

mkdir -p classes

# compile
${JAVA_HOME}/bin/javac -d classes src/security/keyexchange/DHKeyExchange.java

# Bob initializes a key exchange on his side
# public value is going to be saved to bob.public
${JAVA_HOME}/bin/java -cp classes security.keyexchange.DHKeyExchange bob init

# Alice initializes a key exchange on her side
# public value is going to be saved to alice.public
${JAVA_HOME}/bin/java -cp classes security.keyexchange.DHKeyExchange alice init

# Bob reads the public value from Alice, finalizes key exchange, and gets a shared secret
${JAVA_HOME}/bin/java -cp classes security.keyexchange.DHKeyExchange bob complete alice

# Alice reads the public value from Bob, finalizes key exchange, and gets a shared secret
${JAVA_HOME}/bin/java -cp classes security.keyexchange.DHKeyExchange alice complete bob

And finally, here is what the output looks like:
>>> bob: private X saved to bob.private
>>> bob: public  Y saved to bob.public:
00FA941A6BBDCE94440B9FB27A0615452520D4D0904142A23A505FD93925AA7DBC2B06830040C80E5427CF83BDDD508BE2035149F11D34785919C67497F269593F760F7C654AA948E9EAE050410DB196833AE95F9456488FD01B065D98144041CF7CBF05C77B2662212226188A9721A570F893A5C2BE2D89A2B2CB446AB555282DD47547D6E9F40A077440ACC33A5B6C945A84A956E4360FF34E7C0E6C54CDEED5489945BD6D9D58297DD72C239256704875A7560939C6105E2B85B35820AAE9A0603CD9EADFD6AF75BA4F4796DC074E0F92AEA5D99D7D777835C355BFB282229C597B70EA5754170DA3BA437882616064D7E65E9FB49F2BB54AE93454B45A46A6
>>> alice: private X saved to alice.private
>>> alice: public  Y saved to alice.public:
00A7779F9E667F965C51A023181886BB975EBB58268EA67774C1AB9684DE3C61E31A9DD55B704509D0F12EB753E6866AF6EEA96328C7A4AFB4B4676335C88689C2AF4616A044B030AB5AA1C17CDF949A95E36AB5E715D525F361657110DB88A52BE3DC977F692A1EECB23FB23213FA447B4C997AB651950BD1820769B72B587286F88C70675E2E113E95609F077915073CD895253019D47D45DF563B8C9CF107A8ADD343D015ECFEE96B64760F15A8E46DD2D00C961460175C19CB44DE063F57264F3B8B73E832AAA44EDBA9DB95D955066C7E3B44C8497D271F050C258033B364264987ACD7C72AEF59F04F0B6E616F10781FC9261DD94336253E60A195CF929B
>>> shared secret: 84464E64007F62CA9E2A458BD9A82E25554620D80ECFADA7FCE9641DD6EA0DF4F26E9E93D7D39D78A3FE1F971B699B5A9AFCB4E8E8B6E24B6E57E24B3639C11F8C8B82B6B93BAA9F370DF530D0D903F85CF2331E270291E9631D96DAF47EC5DAEC7A8EEC98AEA233B162F693BECB2ACBFB6994EFCF4036D57847E6387F86EBC9A9FBCA9F8A88F2FA23414BD23F4CC1ACDC27B1EBA14BF6912AAA1D4C02BC81E243F96B8F18B1DB98554ADFA03638242C553215F41A66635A003A0E0B01A0DB8EBC64D1661558EB128C5283B3DF6B0144CEF3D55371F3479978083E5B99DB3D7F9F5E42EDF62784A3490068C489081EEA8CB7F251C5CE56BAAD688A2623B077AC
>>> shared secret: 84464E64007F62CA9E2A458BD9A82E25554620D80ECFADA7FCE9641DD6EA0DF4F26E9E93D7D39D78A3FE1F971B699B5A9AFCB4E8E8B6E24B6E57E24B3639C11F8C8B82B6B93BAA9F370DF530D0D903F85CF2331E270291E9631D96DAF47EC5DAEC7A8EEC98AEA233B162F693BECB2ACBFB6994EFCF4036D57847E6387F86EBC9A9FBCA9F8A88F2FA23414BD23F4CC1ACDC27B1EBA14BF6912AAA1D4C02BC81E243F96B8F18B1DB98554ADFA03638242C553215F41A66635A003A0E0B01A0DB8EBC64D1661558EB128C5283B3DF6B0144CEF3D55371F3479978083E5B99DB3D7F9F5E42EDF62784A3490068C489081EEA8CB7F251C5CE56BAAD688A2623B077AC

You may notice that both Bob and Alice got the same shared secret.
By the way, here is a great explanation of the idea of Diffie-Hellman key exchange algorithm without math. You can even show it to your kids.

https://www.youtube.com/watch?v=YEBfamv-_do&feature=youtu.be&t=138

Enjoy!